深刻理解 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/

时间仓促,翻译错误在所不免,还请指正,转载还请注明。

相关文章
相关标签/搜索