深刻理解 Express.js

原文出處: evanhahn.com   譯文出處: Fraser Xu。歡迎加入技術翻譯小組

javascript

本文針對那些對Node.js有必定了解的讀者。假設你已經知道如何運行Node代碼,使用npm安裝依賴模塊。但我保證,你並不須要是這方面的專家。本文針對的是Express 3.2.5版本,以介紹相關概念爲主。css

Express.js這麼描述本身:」輕量靈活的node.js Web應用框架」。它能夠幫助你快速搭建web應用。若是你使用過Ruby裏的Sinatra,那麼相信你對這個也會很快就能熟悉。html

和其餘web框架同樣,Express隱藏了代碼背後的祕密,而後告訴你:」別擔憂,你不用去理解這個部分」。它來幫你解決這些問題,因此你不用去爲這個而煩惱,只用將重心集中到代碼上。換句話說,它有某些魔法!java

Express的wiki裏介紹了一些它的使用者,其中就有不少知名的公司: MySpace, Klout.node

可是擁有魔力是須要付出代價的,你可能根本就不知道它的工做原理。正如駕駛一輛汽車,我能夠很好的駕馭它可是可能不理解爲何汽車能夠正常工做,可是我最好知道這些東西。若是車壞掉怎麼辦?若是你想最大程度的去發揮它的性能?若是你對知識有無限的渴望並想去弄清它?git

那麼我首先從理解Express的最底層-Node開始。github

底層:Node HTTP服務器

Node中有HTTP模塊, 它將搭建一個web服務器的過程抽象出來。你能夠這樣使用:web

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 引入所需模塊
var http = require( "http" );
 
// 創建服務器
var app = http.createServer( function (request, response) {
     response.writeHead(200, {
         "Content-Type" : "text/plain"   
     });
     response.end( "Hello world!\n" );
});
 
// 啓動服務器
app.listen(1337, "localhost" );
console.log( "Server running at http://localhost:1337/" );

運行這個程序(假設文件名爲 app.js ,運行 node app.js ),你會獲得」Hello world!「 在瀏覽器訪問localhost:1337 ,你會獲得一樣的結果。你也能夠嘗試訪問其餘地址,如 localhost:1337/whatever ,結果仍然會同樣。正則表達式

分解以上代碼來看。express

第一行使用 require 函數引入Node內置模塊 http 。而後存入名爲 http 的變量中。若是你要了解更多關於require函數的知識,參考Nodejitsu的文檔。

而後咱們使用 http.createServer 將服務器保存至 app 變量。它將一個函數做爲參數監聽請求。稍後將會詳細介紹它。

最後咱們要作的就是告訴服務器監聽來自1337端口的請求,以後輸出結果。而後一切完成。

好的,回到request請求處理函數。這個函數至關重要。

request方法

在開始這個部分以前,我事先聲明這裏所涉及的HTTP相關知識與學習Express自己沒有太大關係。若是你感興趣,能夠查看HTTP模塊文檔

任什麼時候候咱們向服務器發起請求,request方法將會被調用。若是你不信,你能夠 console.log 將結果打印出來。你會發現每次請求一個頁面時它都會出來。

request 是來自客戶端的請求。在不少應用中,你可能會看到它的縮寫 req 。仔細看代碼。咱們修改代碼以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
var app = http.createServer( function (request, response) {
 
     // 建立answer變量
     var answer = "" ;
     answer += "Request URL: " + request.url + "\n" ;
     answer += "Request type: " + request.method + "\n" ;
     answer += "Request headers: " + JSON.stringify(request.headers) + "\n" ;
 
     // 返回結果
     response.writeHead(200, { "Content-Type" : "text/plain" });
     response.end(answer);
 
});

重啓服務器並刷新 localhsot:1337 .你會發現,每次訪問一個URL,就會發起一次GET請求,並會獲得一堆相似用戶代理或者一些其餘的更加複雜的HTTP相關信息。若是你訪問 localhost:1337/what_is_fraser, 你會看到request的地址發生了變化。若是你使用不一樣的瀏覽器訪問,用戶代理也會跟着改變,若是你使用POST請求,request的方法也很改變。

response 是另一個部分。正如 request 被縮寫爲 req ,response 一樣被簡寫爲 res 。每次response你都會獲得對應的返回結果,以後你即可以經過調用 response.end 來結束。實際上最終你仍是要執行這個方法的, 甚至在node的文檔裏也是這麼描述的。這個方法完成了真正的數據傳輸部分。你能夠創建一個服務器並不調用 req.end 方法,它就會永遠存在。

在你返回結果以前,你也能夠填寫一下header頭部部分。咱們的例子裏是這麼寫的:

1
response.writeHead(200, { "Content-Type" : "text/plain" });

這個步驟主要完成兩件事情。第一,發送HTTP狀態碼,表示請求成功。其次,它設置了返回的頭部信息。這裏表示咱們要返回的是純文本格式的內容。咱們也能夠返回相似JSON或者HTML格式的內容。

未完待續。。。

// 接上回

看了上面的以後,你可能會立馬開始利用它來寫api了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var http = require( "http" );
 
http.createServer( function (req, res) {
 
     // Homepage
     if (req.url == "/" ) {
         res.writeHead(200, { "Content-Type" : "text/html" });
         res.end( "Welcome to the homepage!" );
     }
 
     // About page
     else if (req.url == "/about" ) {
         res.writeHead(200, { "Content-Type" : "text/html" });
         res.end( "Welcome to the about page!" );
     }
 
     // 404'd!
     else {
         res.writeHead(404, { "Content-Type" : "text/plain" });
         res.end( "404 error! File not found." );
     }
 
}).listen(1337, "localhost" );

你能夠選擇優化代碼,讓它變得更整潔。也能夠向npm.org的那幫傢伙同樣用原生的Node來編寫。可是你也能夠選擇去建立一個框架。這就是Sencha所作的,並把這個框架稱爲 – Connect.

中間件: Connect

Connect是Nodejs的中間件。可能你如今還並不太理解什麼是中間件(middleware),別擔憂,我立刻會進行詳細解釋。

一段Connect代碼

假如咱們想要編寫和上面同樣的代碼,可是此次咱們要使用Connect.別忘記安裝Connect模塊(npm install)。完成以後,代碼看起來很是類似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 引入所需模塊
var connect = require( "connect" );
var http = require( "http" );
 
// 創建app
var app = connect();
 
// 添加中間件
app.use( function (request, response) {
     response.writeHead(200, { "Content-Type" : "text/plain" });
     response.end( "Hello world!\n" );
});
 
// 啓動應用
http.createServer(app).listen(1337);

下面分解這段代碼來看。

首先咱們分別引入了Connect和Node HTTP模塊。

接下來和以前同樣聲明 app 變量,可是在建立服務器時,咱們調用了 connect().這有是如何工做的?

咱們添加了一箇中間件,實際上就是一個函數。傳入 app.use ,幾乎和上面使用request方法寫法同樣。實際上代碼是從上面粘貼過來的。

以後咱們創建並啓動服務器。 http.createServer 接收函數做爲參數。沒錯,app 實際上也是一個函數。這是一個Connect提供的函數,它會查找代碼並自上而下執行。

(你可能會看見其餘人使用 app.listen(1337), 這實際上只是將 http.createServer 返回一個promise對象。 再Connect和Express中都是同樣的原理。)

接下來解釋什麼是中間件(middleware).

什麼是中間件?

首先推薦閱讀Stephen Sugden對於Connect中間件的描述,比我講的更好。若是你不喜歡個人解釋,那就去看看。

還記得以前的request方法?每一箇中間件都是一個handler.依次傳入request, response, next三個參數。

一個最基本的中間件結構以下:

1
2
3
4
5
function myFunMiddleware(request, response, next) {
     // 對request和response做出相應操做
     // 操做完畢後返回next()便可轉入下個中間件
     next();
}

當咱們啓動一個服務器,函數開始從頂部一直往下執行。若是你想輸出函數的執行過程,添加一下代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var connect = require( "connect" );
var http = require( "http" );
var app = connect();
 
// log中間件
app.use( function (request, response, next) {
     console.log( "In comes a " + request.method + " to " + request.url);
     next();
});
 
// 返回"hello world"
app.use( function (request, response, next) {
     response.writeHead(200, { "Content-Type" : "text/plain" });
     response.end( "Hello World!\n" );
});
 
http.createServer(app).listen(1337);

若是你啓動應用並訪問 localhost:1337,你會看到服務器能夠log出相關信息。

有一點值得注意,任何能夠在Node.js下執行的代碼均可以在中間件執行。例如上面咱們所使用的req.method 方法。

你固然能夠編寫本身的中間件,可是也不要錯過Connect的一些很cool的第三方中間件。下面咱們移除本身的log中間件,使用Connect內置方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
var connect = require( "connect" );
var http = require( "http" );
var app = connect();
 
app.use(connect.logger());
// 一個有趣的事實:connect.logger返回一個函數
 
app.use( function (request, response) {
     response.writeHead(200, { "Content-Type" : "text/plain" });
     response.end( "Hello world!\n" );
});
 
http.createServer(app).listen(1337);

跳轉至瀏覽器並訪問 localhost:1337 你會獲得一樣的結果。

很快有人就會想使用上面的中間件組合起來建立一個完整應用。代碼以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var connect = require( "connect" );
var http = require( "http" );
var app = connect();
 
app.use(connect.logger());
 
// Homepage
app.use( function (request, response, next) {
     if (request.url == "/" ) {
         response.writeHead(200, { "Content-Type" : "text/plain" });
         response.end( "Welcome to the homepage!\n" );
         // The middleware stops here.
     } else {
         next();
     }
});
 
// About page
app.use( function (request, response, next) {
     if (request.url == "/about" ) {
         response.writeHead(200, { "Content-Type" : "text/plain" });
         response.end( "Welcome to the about page!\n" );
         // The middleware stops here.
     } else {
         next();
     }
});
 
// 404'd!
app.use( function (request, response) {
     response.writeHead(404, { "Content-Type" : "text/plain" });
     response.end( "404 error!\n" );
});
 
http.createServer(app).listen(1337);

「這個看起來不太好看!我要本身寫框架!」

某些人看了Connect的代碼以後以爲,「這個代碼能夠更簡單」。因而他們創造了Express.(事實上他們好像直接盜用了Sinatra.)

最頂層: Express

文章進入第三部分,咱們開始真正進入Express.

正如Connect拓展了Node, Express拓展Connect.代碼的開始部分看起來和在Connect中很是相似:

1
2
3
var express = require( "express" );
var http = require( "http" );
var app = express();

結尾部分也同樣:

1
http.createServer(app).listen(1337);

中間部分纔是不同的地方。Connect爲咱們提供了中間件,Express則爲咱們提供了另外三個優秀的特性: 路由分發,請求處理,視圖渲染。首先從若有開始看。

特性一:路由

路由的功能就是處理不一樣的請求。在上面的不少例子中,咱們分別有首頁,關於和404頁面。咱們是經過 if 來判斷並處理不一樣請求地址。

可是Express卻能夠作的更好。Express提供了」routing」這個東西,也就是咱們所說的路由。我以爲可讀性甚至比純文字還要好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var express = require( "express" );
var http = require( "http" );
var app = express();
 
app.all( "*" , function (request, response, next) {
     response.writeHead(404, { "Content-Type" : "text/plain" });
     next();
});
 
app.get( "/" , function (request, response) {
     response.end( "Welcome to the homepage!" );
});
 
app.get( "/about" , function (request, response) {
     response.end( "Welcome to the about page!" );
});
 
app.get( "*" , function (request, response) {
     response.end( "404!" );
});
 
http.createServer(app).listen(1337);

簡單的引入相關模塊以後,咱們當即調用 app.all處理全部請求。寫法看起來也很是像中間件不是嗎?

代碼中的 app.get 就是Express提供的路由系統。也能夠是 app.post 來處理POST請求,或者是PUT和任何的HTTP請求方式。第一個參數是路徑,例如 /about 或者 /。第二個參數相似咱們以前所見過的請求handler。引用Expess文檔的內容:

這些請求handler和中間件同樣,惟一的區別是這些回調函數會調用 next('route') 從而可以繼續執行剩下的路由回調函數。這種機制

簡單說來,它們和咱們以前提過的中間件是同樣,只不過是一些函數而已。

這些路由也能夠更加靈活,看起來是這樣:

1
2
3
app.get( "/hello/:who" , function (req, res) {
     res.end( "Hello, " + req.params.who + "." );   
});

重啓服務器並在瀏覽器訪問 localhost:1337/hello/animelover69 你會獲得以下信息:

1
<code>Hello, animelover69. </code>

這些文檔演示瞭如何使用正則表達式,可使得路由更加靈活。若是隻是單從概念理解來說,我說的已經足夠了。

可是還有更加值得咱們去關注的。

特性二:請求處理 request handling

Express將你傳入請求的handler傳入request和response對象中。原先該有的還在,可是卻加入了更多新的特性。API文檔裏有詳細解釋。下面讓咱們來看一些例子。

其中一個就是 redirect 方法。代碼以下:

1
2
3
response.redirect( "/hello/anime" );
response.redirect( "http://xvfeng.me" );
response.redirect(301, "http://xvfeng.me" ); // HTTP 301狀態碼

以上代碼既不屬於原生Node代碼也不是來自與Connect,而是Express中自身添加的。它加入了一些例如sendFile,讓你傳輸整個文件等功能:

1
response.sendFile( "/path/to/anime.mp4" );

request對象還有一些很cool的屬性,例如 request.ip 能夠獲取IP地址, request.files 上傳文件等。

理論上來說,咱們要知道的東西也不是太多,Express作的只是拓展了request和response對象而已。Express所提供的方法,請參考API文檔.

特性三:視圖

Express能夠渲染視圖。代碼以下:

1
2
3
4
5
6
7
8
9
// 啓動Express
var express = require( "express" );
var app = express();
 
// 設置view目錄
app.set( "views" , __dirname + "/views" );
 
// 設置模板引擎
app.set( "view engine" , "jade" );

開頭部分的代碼和前面基本同樣。以後咱們指定視圖文件所在目錄。而後告訴Express咱們要使用 Jade做爲模板引擎。 Jade是一種模板語言。稍後將會詳細介紹。

如今咱們已經設置好了view.可是如何來使用它呢?

首先咱們創建一個名爲 index.jade 的文件並把它放入 views 目錄。代碼以下:

1
2
3
4
5
doctype 5
html
   body
     h1 Hello, world!
     p= message

代碼只是去掉了括號的HTML代碼。若是你懂HTML那確定也看得懂上面的代碼。惟一有趣的是最後同樣。 message 是一個變量。它是從哪裏來的呢?立刻告訴你。

咱們須要從Express中渲染這個視圖。代碼以下:

1
2
3
app.get( "/" , function (request, response) {
     response.render( "index" , { message: "I love anime" });   
});

Express爲 response 對象添加了一個 render 方法。這個方法能夠處理不少事情,但最主要的仍是加載模板引擎和對應的視圖文件,以後渲染成普通的HTML文檔,例如這裏的 index.jade.

最後一步(我以爲可能算是第一步)就是安裝Jade,由於它自己並非Express的一部分。添加至package.json 文件並使用 npm install 進行安裝。

若是一塊兒設置完畢,你會看到這個頁面完整代碼.

加分特性: 全部代碼來自於Connect和Node

我須要再次提醒你的是Express創建與Connect和Node之上,這意味着全部的Connect中間件都可以在Express中使用。這個對與開發來說幫助很大。例如:

1
2
3
4
5
6
7
8
9
10
var express = require( "express" );
var app = express();
 
app.use(express.logger());  // 繼承自Connect
 
app.get( "/" , function (req, res) {
     res.send( "fraser" );   
});
 
app.listen(1337);

若是說你從這篇文章中學到了一點什麼,就是這一點。

實戰

本文的大部份內容都是理論,可是下面我將教你如何使用它來作一點你想作的東西。我不想說的過於具體。

你能夠將Express安裝到系統全局,從而能夠在命令行使用它。它能夠幫助你迅速的完成代碼組織並啓動應用。使用npm安裝:

1
<code> # 安裝時可能須要加 `sudo` npm install -g express </code>

若是你須要幫助,輸入 express --help 。它加入一些可選參數。例如,若是你想使用EJS模板引擎,LESS做爲CSS引擎。應用的名稱爲」myApp」.輸入如下命令:

1
<code>express --ejs --css less myApp </code>

這裏會自動生成不少文件。進入項目目錄,並使用 npm install 安裝依賴包,以後即可以使用 node app啓動應用!我建議你詳細的查看項目結構和代碼。它可能還算不上一個真正的應用,可是我以爲它對於初學者來說仍是頗有幫助的。

項目Github目錄下也有一些頗有幫助的文檔。

一些補充

  • 若是你也和我同樣喜歡使用CoffeeScript,好消息是Express完美支持CoffeeScript.你甚至不須要編譯它。這樣你只用 coffee app.coffee 便可啓動應用。我在個人其餘項目中也是這麼作的。
  • 在我看到 app.use(app.router) 的時候我很疑惑: Express不是一直在使用router嗎?簡單回答是app.router 是Express的路由中間件,在你定義路由的時候被直接添加到項目中。若是你須要在加載其餘文件以前應用,也能夠直接引入它。關於這麼作的緣由,請參考StackOverflow的這個答桉.
  • 本文是針對Express 3,而在第四版的規劃中又會有不少大的改動。最明顯的是,Experss可能要將會分解成一些小的模塊,並吸取Connect的一些特性。這個雖然還在計劃中,可是也值得一看。

若是這個還不能知足你?你確定是個變態!你很快就會變成像一個癮君子,半睜着眼,耗盡你最後一點精力,寫着苦逼的代碼。

正如Rails成爲使用Ruby創建網頁應用的王者同樣,我以爲Express也會成爲Node中的主流。可是和Rails不同,Express更加底層。彷佛尚未一個真正意義上的高級Node庫。我以爲可能會發生改變。(譯者注:這點我不一樣意,Node的不少思想來自與Unix哲學,強調的是一個Module只解決一個問題,而不是成爲一個複雜的庫。不少Rails的開發者轉向Node,就是由於Rails正在逐漸變得臃腫,不易自定義,且效率逐漸下降。)。

這裏我就再也不多談。已經又不少很基於Express創建了新的東西,Expess的維基裏有列舉。若是你以爲好能夠隨意使用它們,若是你喜歡從底層作起,你也能夠只選擇Express。無論是哪種,好好利用它吧。

原文地址:http://evanhahn.com/understanding-express-js/

時間倉促,翻譯錯誤在所不免,還請指正,轉載還請註明。

相關文章
相關標籤/搜索