摘要:同樣的的框架中也有被稱為中間件概念。所以,整個工作流有兩種可能情形另外,這些中間件函數中部分函數需要對響應做出響應。擴展和在原來基礎上對和對象進行了功能擴展。除了對響應對象進行了拓展之
Express 框架的初衷是為了拓展 Node 內置模塊的功能提高開發效率。當你深入研究后就會發現,Express 其實是在 Node 內置的 HTTP 模塊上構建了一層抽象。理論上所有 Express 實現的功能,同樣可以使用純 Node 實現。
在本文中,我們將基于前面的 Node 內容去探究 Express 和 Node 之間的關系,其中包括:中間件和路由等概念。當然,這里只會進行一些綜述具體的細節會在后面帶來。
總的來說,Express 提供了 4 個主要特性:
與純 Node 中使用一個函數處理所有請求的代碼不同, Express 則使用“中間件棧”處理流。
路由與中間件類似,只有當你通過特定 HTTP 方法訪問特定 URL 時才會觸發處理函數的調用。
對 request 和 response 對象方法進行了拓展。
視圖模塊允許你動態渲染和改變 HTML 內容,并且使用其他語言編寫 HTML 。
中間件中間件是 Express 中最大的特性之一。中間件與原生的 Node 處理函數非常類似(接受一個請求并做出響應),但是與原生不同的是,中間件將處理過程進行劃分,并且使用多個函數構成一個完整的處理流程。
我們將會看到中間件在代碼中的各種應用。例如,首先使用一個中間件記錄所有的請求,接著在其他的中間件中設置 HTTP 頭部信息,然后繼續處理流程。雖然在一個“大函數”中也可以完成請求處理,但是將任務進行拆分為多個功能明確獨立的中間件明顯更符合軟件開發中的 SRP 規則。
中間件并不是 Express 特有,Python 的 Django 或者 PHP 的 Laravel 也有同樣的概念存在。同樣的 Ruby 的 Web 框架中也有被稱為 Rack 中間件概念。
現在我們就用 Express 中間件來重新實現 Hello World 應用。你將會發現只需幾行代碼就能完成開發,在提高效率的同時還消除了一些隱藏 bug。
Express 版 Hello World首先新建一個Express工程:新建一個文件夾并在其中新建 package.json 文件?;叵胍幌?package.json 的工作原理,其中完整的列出了該工程的依賴、項目名稱、作者等信息。我們新工程中的 package.json 大致如下:
{ ? "name": "hello-world", ? "author": "Your Name Here!", ? "private": true, ? "dependencies": {} }
接下來執行命令,安裝最新的 Express 并且將其保存到 package.json 中:
npm install express -sava
命令執行完成后,Express 會自動安裝到 node_modules 的文件下,并且會在 package.json 明確列出改依賴。此時 package.json 中的內容如下:
{ ? "name": "hello-world", ? "author": "Your Name Here!", ? "private": true, ? "dependencies": { ? "express": "^5.0.0" ? } }
接下來將下列代碼復制到 app.js 中:
var express = require("express");? var http = require("http"); var app = express();?? ? app.use(function(request, response) {? response.writeHead(200, { "Content-Type": "text/plain" });????? ? response.end("Hello, World!");? });? ? http.createServer(app).listen(3000);?
首先,我們依次引入了 Express 和 HTTP 模塊。
然后,使用 express() 方法創建變量 app ,該方法會返回一個請求處理函數閉包。這一點非常重要,因為它意味著我可以像之前一樣將其傳遞給 http.createServer 方法。
還記得前一章提到的原生 Node 請求處理嗎?它大致如下:
var app = http.createServer(function(request, response) { response.writeHead(200, { "Content-Type": "text/plain" }); ? response.end("Hello, world!"); });
兩段代碼非常相似,回調閉包都包含兩個參數并且響應也一樣。
最后,我們創建了一個服務并且啟動了它。http.createServer 接受的參數是一個函數,所以合理猜測 app 也只是一個函數,只不過該函數表示的是 Express 中一個完整的中間件處理流程。
中間件如何在高層工作在原生的 Node 代碼中,所有的 HTTP 請求處理都在一個函數中:
function requestHandler(request, response) { console.log("In comes a request to: " + request.url); response.end("Hello, world!"); }
如果抽象成流程圖的話,它看起來就像:
這并不是說在處理過程中不能調用其它函數,而是所有的請求響應都由該函數發送。
而中間件則使用一組中間件棧函數來處理這些請求,處理過程如下圖:
那么,接下來我們就有必要了解 Express 使用一組中間件函數的緣由,以及這些函數作用。
現在我們回顧一下前面用戶驗證的例子:只有驗證通過才會展示用戶的私密信息,與此同時每次訪問請求都要進行記錄。
在這個應用中存在三個中間件函數:請求記錄、用戶驗證、信息展示。中間件工作流為:先記錄每個請求,然后進行用戶驗證,驗證通過進行信息展示,最后對請求做出響應。所以,整個工作流有兩種可能情形:
另外,這些中間件函數中部分函數需要對響應做出響應。如果沒有做出任何響應的話,那么服務器會掛起請求而瀏覽器也會干等。
這樣做的好處就是,我們可以將應用進行拆分。而拆分后的組件不僅利于后期維護,并且組件之間還可以進行不同組合。
不做任何修改的中間件中間件函數可以對 request、response 進行修改,但它并不是必要操作。例如,前面的日志記錄中間件代碼:它只需要進行記錄操作。而一個不做任何修改,純功能性的中間函數代碼大致如下:
function myFunMiddleware(request, response, next) { ... nest(); }
因為中間件函數的執行是從上到下的。所以,加入純功能性的請求記錄中間件后,代碼如下:
var express = require("express"); var http = require("http"); var app = express(); // 日志記錄中間件 app.use(function(request, response, next) { console.log("In comes a " + request.method + " to " + request.url); next(); }); // 發送實際響應 app.use(function(request, response) { response.writeHead(200, { "Content-Type": "text/plain" }); response.end("Hello, world!"); }); http.createServer(app).listen(3000);修改 request、response 的中間件
并不是所有的中間件都和上面一樣,在部分中間件函數需要對 request、response 進行處理,尤其是后者。
下面我們來實現前面提到的驗證中間件函數。為了簡單起見,這里只允許當前分鐘數為偶數的情況通過驗證。那么,該中間件函數代碼大致如下:
app.use(function(request, response, next) { console.log("In comes a " + request.method + " to " + request.url); next(); }); app.use(function(request, response, next) { var minute = (new Date()).getMinutes(); // 如果在這個小時的第一分鐘訪問,那么調用next()繼續 if ((minute % 2) === 0) { next(); } else { // 如果沒有通過驗證,發送一個403的狀態碼并進行響應 response.statusCode = 403; response.end("Not authorized."); } }); app.use(function(request, response) { response.end("Secret info: the password is "swordfish"!"); // 發送密碼信息 });第三方中間件類庫
在大多數情況下,你正在嘗試的工作可能已經被人實現過了。也就是說,對于一些常用的功能社區中可能已經存在成熟的解決方案了。下面,我們就來介紹一些 Express 中常用的第三方模塊。
MORGAN:日志記錄中間件Morgan 是一個功能非常強大的日志中間件。它能對用戶的行為和請求時間進行記錄。而這對于分析異常行為和可能的站點崩潰來說非常有用。大多數時候 Morgan 也是 Express 中日志中間件的首選。
使用命令 npm install morgan --save 安裝該中間件,并修改 app.js 中的代碼:
var express = require("express"); var logger = require("morgan"); var http = require("http"); var app = express(); app.use(logger("short")); app.use(function(request, response){ response.writeHead(200, {"Content-Type": "text/plain"}); response.end("Hello, world!"); }); http.createServer(app).listen(3000);
再次訪問 http://localhost:3000 你就會看到 Morgan 記錄的日志了。
Express 的靜態文件中間件通過網絡發送靜態文件對 Web 應用來說是一個常見的需求場景。這些資源通常包括圖片資源、CSS 文件以及靜態 HTML 文件。但是一個簡單的文件發送行為其實代碼量很大,因為需要檢查大量的邊界情況以及性能問題的考量。而 Express 內置的 express.static 模塊能最大程度簡化工作。
假設現在需要對 public 文件夾提供文件服務,只需通過靜態文件中間件我們就能極大壓縮代碼量:
var express = require("express"); var path = require("path"); var http = require("http"); var app = express(); var publicPath = path.resolve(__dirname, "public"); app.use(express.static(publicPath)); app.use(function(request, response) { response.writeHead(200, { "Content-Type": "text/plain" }); response.end("Looks like you didn"t find a static file."); }); http.createServer(app).listen(3000);
現在,任何在 public 目錄下的靜態文件都能直接請求了,所以你可以將所有需要的文件的放在該目錄下。如果 public 文件夾中沒有任何匹配的文件存在,它將繼續執行下一個中間件并響應一段 沒有匹配的文件信息。
更多中間件為什么使用 path.resolve ?
之所以不直接使用 /public 是因為 Mac 和 Linux 中目錄為 /public 而 Windows 使用萬惡的反斜杠 public 。path.resolve 就是用來解決多平臺目錄路徑問題。
除此上面介紹的 Morgan 中間件和 Express 靜態中間之外,還有很多其他功能強大的中間件,例如:
connect-ratelimit:可以讓你控制每小時的連接數。如果某人向服務發起大量請求,那么可以直接返回錯誤停止處理這些請求。
helmet:可以添加 HTTP 頭部信息來應對一些網絡攻擊。具體內容會在后面關于安全的章節講到。
cookie-parses:用于解析瀏覽器中的 cookie 信息。
response-time:通過發送 X-Response-Time 信息,讓你能夠更好的調試應用的性能。
路由路由是一種將 URL 和 HTTP 方法映射到特定處理回調函數的技術。假設工程里有一個主頁,一個關于頁面以及一個 404 頁面,接下來看看路由是如何進行映射的:
var express = require("express"); var path = require("path"); var http = require("http"); var app = express(); // 像之前一樣設置靜態文件中間件。 // 所有的請求通過這個中間件,如果沒有文件被找到的話會繼續前進 var publicPath = path.resolve(__dirname, "public"); app.use(express.static(publicPath)); // 當請求根目錄的時候被調用 app.get("/", function(request, response) { response.end("Welcome to my homepage!"); }); // 當請求/about的時候被調用 app.get("/about", function(request, response) { response.end("Welcome to the about page!"); }); // 當請求/weather的時候被調用 app.get("/weather", function(request, response) { response.end("The current weather is NICE."); }); // 前面都不匹配,則路由錯誤。返回 404 頁面 app.use(function(request, response) { response.statusCode = 404; response.end("404"); }); http.createServer(app).listen(3000);
上面代碼中除了添加前面提到的中間件之外,后面三個 app.get 函數就是 Express 中強大的路由系統了。它們使用 app.post 來響應一個 POST 或者 PUT 等所有網絡請求。函數中第一個參數是一個路徑,例如 /about 或者 /weather 或者簡單的根目錄 / ,第二個參數是一個請求處理函數。該處理函數與之前的中間件工作方式一樣,唯一的區別就是調用時機。
除了固定路由形式外,它還可以匹配更復雜的路由(使用正則等方式):
// 指定“hello”為路由的固定部分 app.get("/hello/:who", function(request, response) { // :who 并不是固定住,它表示 URL 中傳遞過來的名字 response.end("Hello, " + request.params.who + "."); });
重啟服務并訪問 localhost:3000/hello/earth 等到的響應信息為:
Hello, earth
注意到如果你在 URL 后面插入多個 / 的話,例如:localhost:3000/hello/entire/earth 將會返回一個 404 錯誤。
你應該在日常生活中見過這種 URL 鏈接,特定的用戶能夠訪問特定的 URL 。例如,有一個用戶為 ExpressSuperHero ,那么他的個人信息頁面 URL 可能是:
https://mywebsite.com/users/E...
在 Express 中你可以通過這種通配方式簡化路由定義,而不必將所有用戶的特定路由都一一列舉出來。
官方文檔中還展示了一個使用正則表達式來進行復雜匹配的例子,并且你可以通過路由做更多其它的事情。不過這章中只需要知道路由概念就行了,更多的內容將會在第五章中深入講解。
擴展 request 和 responseExpress 在原來基礎上對 request 和 response 對象進行了功能擴展。你可以在官方文檔中找到所有細節內容,不過我們可以先來領略其中的一部分:
Express 提供的功能中 redirect 算一個非常棒的功能,使用方法如下:
response.redirect("/hello/world"); response.redirect("http://expressjs.com");
原生 Node 中并沒有重定向 redirect 方法。雖然我們也能夠使用原生代碼實現重定向功能,但明顯它的代碼量會更多。
另外,在 Express 中文件發送也變的更加簡單,只需一行代碼就能實現:
response.sendFile("path/to/cool_song.mp3")
與之前一樣,該功能的原生實現代碼也比較復雜。
除了對響應對象 response 進行了拓展之外,Express 也對請求對象 request 進行了拓展。例如:你可以通過 request.ip 獲取發送請求的機器 IP 地址或者通過 request.get 獲取 HTTP 頭部。
下面我們使用它實現 IP 黑名單功能,代碼如下:
var express = require("express"); var app = express(); var EVIL_IP = "123.45.67.89"; app.use(function(request, response, next) { if (request.ip === EVIL_IP) { response.status(401).send("Not allowed!"); } else { next(); } }); ...
這里使用到了 req.ip 以及 res.status() 和 res.send() ,而這些方法全都來自于 Express 的拓展。
理論上來說,我們只需要知道 Express 拓展了 request 和 response 并知道如何使用就行了,至于細節可以不去做了解。
上面的例子,只是 Express 所有拓展中的冰山一角,你可以在文檔中看到更多的示例。
視圖幾乎所有的網站內容都是基于 HTML 進行展示的,并且大多時候這些 HTML 內容都是動態生成的。你可能需要為當前登錄用戶提供特定歡迎頁或者需要在頁面中動態生成數據表。為了應對動態內容的渲染,社區中出現了大量的 Express 模版引擎,例如: EJS、Handlebars、Pug。
下面是 EJS 模版引擎使用示例:
var express = require("express"); var path = require("path"); var app = express(); // 告訴 Express 你的視圖存在于一個名為 views 的文件夾中 app.set("views", path.resolve(__dirname, "views")); // 告訴 Express 你將使用EJS模板引擎 app.set("view engine", "ejs");
在代碼中,首先我們導入了必要的模塊。然后設置了視圖文件所在的路徑。緊接著,我們將模版引擎設置為 EJS (文檔)。當然在使用 EJS 執行,我們還需要通過 npm install ejs --save 命令進行安裝。
安裝并設置好 EJS 引擎之后,接下里就是如何使用的問題了。
首先,我們在 views 文件夾下面創建一個 index.ejs 文件,并拷貝下面的內容:
Hello, world! <%= message %>
EJS 實質上是 HTML 的一個超集,所有 HTML 的語法都可以直接使用并且完全兼容。但是 EJS 對語法進行了部分拓展。 例如,你可以通過 <%= message %> 語法將傳遞過來的參數 message 插入到標簽中。
app.get("/", function(request, response) { response.render("index", { message: "Hey everyone! This is my webpage." }); });
Express 給 response 對象添加了一個名為 render 的方法。該方法在視圖目錄下查找第一個參數對應的模版視圖文件并將第二個參數傳遞給該模版文件。
下面是經過引擎渲染動態生成后的 HTML 文件內容:
實例:一個留言板的實現Hello, world! Hey everyone! This is my webpage.
最后這部分,我們將會使用到前面的技術來構建一個完整的留言板 web 程序。通過這個示例來加深對上面內容的掌握,該應用主要包含兩個頁面:
一個主頁:主要用于列出之前所有的留言
一個編輯頁面:用于編輯新的留言
準備工作首先,我們新建一個文件夾并新建項目,并復制下面內容到新建的 package.json 文件中:
{ "name": "express-guestbook", "private": true, "scripts": { "start": "node app" } }
你可以在文件中添加其他字段信息(例如作者或者版本),但是在本例中這并不是必要信息。接下來,我們安裝依賴文件,輸入命令:
npm install express morgan body-parser ejs --save
因為需要實現留言新建動作,所以這里需要使用 body-parser 對 POST 請求進行解析。
核心代碼準備工作完成后,接下來就創建 app.js 文件并復制下面的代碼:
var http = require("http"); var path = require("path"); var express = require("express"); var logger = require("morgan"); var bodyParser = require("body-parser"); var app = express(); // 設置引擎 app.set("views", path.resolve(__dirname, "views")); app.set("view engine", "ejs"); // 設置留言的全局變量 var entries = []; app.locals.entries = entries; // 使用 Morgan 進行日志記錄 app.use(logger("dev")); // 設置用戶表單提交動作信息的中間件,所有信息會保存在 req.body 里 app.use(bodyParser.urlencoded({ extended: false })); // 當訪問了網站根目錄,就渲染主頁(位于views/index.ejs) app.get("/", function(request, response) { response.render("index"); }); // 渲染“新留言”頁面(位于views/index.ejs)當get訪問這個URL的時候 app.get("/new-entry", function(request, response) { response.render("new-entry"); }); // POST 動作進行留言新建的路由處理 app.post("/new-entry", function(request, response) { // 如果用戶提交的表單沒有標題或者內容,則返回一個 400 的錯誤 if (!request.body.title || !request.body.body) { response.status(400).send("Entries must have a title and a body."); return; } // 添加新留言到 entries 中 entries.push({ title: request.body.title, content: request.body.body, published: new Date() }); // 重定向到主頁來查看你的新條目 response.redirect("/"); }); // 渲染404頁面,因為你請求了未知資源 app.use(function(request, response) { response.status(404).render("404"); }); // 在3000端口啟動服務器 http.createServer(app).listen(3000, function() { console.log("Guestbook app started on port 3000."); });新建視圖
最后我們需要將頁面的視圖文件補全,新建 views 文件夾,然后復制下面內容到新建 header.ejs 文件中:
Express Guestbook Express Guestbook Write in the guestbook
這里使用了 Twitter 的 Bootstrap 框架,當然你也可以進行任意替換。最重要的一點是,該文件會做為所有頁面的通用頭部。
接下來,在相同目錄下新建 footer.ejs 作為通用的 footer:
通用部分完成后,接下來就是 index、new-entry、404 頁面文件了。復制下面代碼到文件 views/index.ejs 中:
<% include header %> <% if (entries.length) { %> <% entries.forEach(function(entry) { %><% }) %> <% } else { %> No entries! Add one! <% } %> <% include footer %><%= entry.published %><%= entry.title %><%= entry.body %>
同時將下面的代碼復制到 views/new-entry.ejs 中
<% include header %><% include footer %>Write a new entry
最后就是 views/404.ejs 文件了:
<% include header %>404! Page not found.
<% include footer %>
所有的視圖文件都創建完成了,接下來就是運行服務了。
運行服務如果你現在就使用 npm start 拉起服務,然后訪問對應的 URL ,你就能見到下圖所示的場景了。
最后,我們回顧一下這個小項目的幾個關鍵點:
使用了一個中間件來記錄所有的請求,并且對不匹配的 URL 鏈接進行了 404 頁面響應。
在新建留言后,我們將頁面重定向到了主頁。
在該工程里使用了 EJS 作為 Express 的模版引擎。并使用它實現了 HTML 文件的動態渲染。
總結Express 基于 Node 進行了工程拓展,使得開發過程更為流暢高效。
Express 主要有四個部分構成。
Express 的請求處理流程可以由多個中間件進行構建。
Express 中流行的模版引擎為 EJS ,它能實現對 HTML 的動態渲染并且語法也更為友好。
原文地址
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/84855.html
摘要:下面介紹三種安裝方式,各位根據需求選擇的安裝與使用薦快速搭建開發環境以及加速多系統基于安裝安裝安裝成功后檢查安裝是否成功由于國外的源很不穩定需要講起轉為國內淘寶源,如果需要管理員權限那么加如果默認安裝的版本不是項目需要的版本,那么可 下面介紹三種安裝方式,各位根據需求選擇 · Node.js 的安裝與使用 【薦】 · 快速搭建 Node.js 開發環境以及加速 npm【多系統】 基于b...
摘要:多一個技能多一條出路,祝你在自學道路上越走越好,掌握自己的核心技能,不只是優秀,還要成為不可替代的人 NodeJs+Express+Mysql + Vuejs 項目實戰 最近準備寫一系列文章,全面講述如何基于NodeJs + Express + Mysql + Vuejs 從零開發前后端完全分離項目; 文筆及技術可能在某些方面欠佳,請您指正,共同學習進步 前端:Vuejs全家桶 后端:...
摘要:很基礎,不喜勿噴轉載注明出處爬蟲實戰項目之鏈家效果圖思路爬蟲究竟是怎么實現的通過訪問要爬取的網站地址,獲得該頁面的文檔內容,找到我們需要保存的數據,進一步查看數據所在的元素節點,他們在某方面一定是有規律的,遵循規律,操作,保存數據。 說明 作為一個前端界的小學生,一直想著自己做一些項目向全棧努力。愁人的是沒有后臺,搜羅之后且學會了nodejs和express寫成本地的接口給前端頁面調用...
摘要:很基礎,不喜勿噴轉載注明出處爬蟲實戰項目之鏈家效果圖思路爬蟲究竟是怎么實現的通過訪問要爬取的網站地址,獲得該頁面的文檔內容,找到我們需要保存的數據,進一步查看數據所在的元素節點,他們在某方面一定是有規律的,遵循規律,操作,保存數據。 說明 作為一個前端界的小學生,一直想著自己做一些項目向全棧努力。愁人的是沒有后臺,搜羅之后且學會了nodejs和express寫成本地的接口給前端頁面調用...
閱讀 2727·2021-11-22 15:22
閱讀 1631·2021-11-22 14:56
閱讀 3616·2021-09-22 15:12
閱讀 2403·2021-09-02 15:41
閱讀 2122·2021-08-27 16:26
閱讀 1113·2019-08-30 15:55
閱讀 2139·2019-08-29 17:30
閱讀 665·2019-08-29 16:26