本文针对那些对Node.js有一定了解的读者。假设你已经知道如何运行Node代码,使用npm安装依赖模块。但我保证,你并不需要是这方面的专家。本文针对的是Express 3.2.5版本,以介绍相关概念为主。

Express.js这么描述自己:”轻量灵活的Node.js Web应用框架”。它可以帮助你快速搭建web应用。如果你使用过Ruby里的Sinatra,那么相信你对这个也会很快就能熟悉。

和其他web框架一样,Express隐藏了代码背后的祕密,然后告诉你:”别担心,你不用去理解这个部分”。它来帮你解决这些问题,所以你不用去为这个而烦恼,只用将重心集中到代码上。换句话说,它有某些魔法!

Express的wiki里介绍了一些它的使用者,其中就有很多知名的公司: MySpace, Klout.

但是拥有魔力是需要付出代价的,你可能根本就不知道它的工作原理。正如驾驶一辆汽车,我可以很好的驾驭它但是可能不理解为什么汽车可以正常工作,但是我最好知道这些东西。如果车坏掉怎么办?如果你想最大程度的去发挥它的性能?如果你对知识有无限的渴望并想去弄清它?

那么我首先从理解Express的最底层-Node开始。

底层:Node HTTP服务器

Node中有HTTP模块, 它将搭建一个web服务器的过程抽象出来。你可以这样使用:

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

运行这个程序(假设文件名为 app.js ,运行 node app.js ),你会得到”Hello world!“ 在浏览器访问localhost:1337 ,你会得到同样的结果。你也可以尝试访问其他地址,如 localhost:1337/whatever ,结果仍然会一样。

分解以上代码来看。

第一行使用 require 函数引入Node内置模块 http 。然后存入名为 http 的变量中。如果你要了解更多关于require函数的知识,参考Nodejitsu的文档。

然后我们使用 http.createServer 将服务器保存至 app 变量。它将一个函数作为参数监听请求。稍后将会详细介绍它。

最后我们要做的就是告诉服务器监听来自1337端口的请求,之后输出结果。然后一切完成。

好的,回到request请求处理函数。这个函数相当重要。

request方法

在开始这个部分之前,我事先声明这里所涉及的HTTP相关知识与学习Express本身没有太大关係。如果你感兴趣,可以查看HTTP模块文档

任何时候我们向服务器发起请求,request方法将会被调用。如果你不信,你可以 console.log 将结果打印出来。你会发现每次请求一个页面时它都会出来。

request 是来自客户端的请求。在很多应用中,你可能会看到它的缩写 req 。仔细看代码。我们修改代码如下:

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

重启服务器并刷新 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. var http = require("http");
  2. http.createServer(function(req, res) {
  3. // Homepage
  4. if(req.url == "/") {
  5. res.writeHead(200, { "Content-Type": "text/html" });
  6. res.end("Welcome to the homepage!");
  7. }
  8. // About page
  9. else if (req.url == "/about") {
  10. res.writeHead(200, { "Content-Type": "text/html" });
  11. res.end("Welcome to the about page!");
  12. }
  13. // 404'd!
  14. else {
  15. res.writeHead(404, { "Content-Type": "text/plain" });
  16. res.end("404 error! File not found.");
  17. }
  18. }).listen(1337, "localhost");

你可以选择优化代码,让它变得更整洁。也可以向npm.org的那帮傢伙一样用原生的Node来编写。但是你也可以选择去创建一个框架。这就是Sencha所做的,并把这个框架称为 – Connect.

中间件: Connect

Connect是Nodejs的中间件。可能你现在还并不太理解什么是中间件(middleware),别担心,我马上会进行详细解释。

一段Connect代码

假如我们想要编写和上面一样的代码,但是这次我们要使用Connect.别忘记安装Connect模块(npm install)。完成之后,代码看起来非常相似。

  1. // 引入所需模塊
  2. var connect = require("connect");
  3. var http = require("http");
  4. // 建立app
  5. var app = connect();
  6. // 添加中间件
  7. app.use(function(request, response) {
  8. response.writeHead(200, { "Content-Type": "text/plain" });
  9. response.end("Hello world!\n");
  10. });
  11. // 启动应用
  12. 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. function myFunMiddleware(request, response, next) {
  2. // 对request和response作出相应操作
  3. // 操作完毕后返回next()即可转入下個中间件
  4. next();
  5. }

当我们啓动一个服务器,函数开始从顶部一直往下执行。如果你想输出函数的执行过程,添加一下代码:

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

如果你啓动应用并访问 localhost:1337,你会看到服务器可以log出相关信息。

有一点值得注意,任何可以在Node.js下执行的代码都可以在中间件执行。例如上面我们所使用的req.method 方法。

你当然可以编写自己的中间件,但是也不要错过Connect的一些很cool的第三方中间件。下面我们移除自己的log中间件,使用Connect内置方法。

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

跳转至浏览器并访问 localhost:1337 你会得到同样的结果。

很快有人就会想使用上面的中间件组合起来创建一个完整应用。代码如下:

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

“这个看起来不太好看!我要自己写框架!”

某些人看了Connect的代码之后觉得,“这个代码可以更简单”。于是他们创造了Express.(事实上他们好像直接盗用了Sinatra.)

最顶层: Express

文章进入第三部分,我们开始真正进入Express.

正如Connect拓展了Node, Express拓展Connect.代码的开始部分看起来和在Connect中非常类似:

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

结尾部分也一样:

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

中间部分纔是不一样的地方。Connect为我们提供了中间件,Express则为我们提供了另外三个优秀的特性: 路由分发,请求处理,视图渲染。首先从如有开始看。

特性一:路由

路由的功能就是处理不同的请求。在上面的很多例子中,我们分别有首页,关于和404页面。我们是通过 if 来判断并处理不同请求地址。

但是Express却可以做的更好。Express提供了”routing”这个东西,也就是我们所说的路由。我觉得可读性甚至比纯文字还要好。

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

简单的引入相关模块之后,我们立即调用 app.all处理所有请求。写法看起来也非常像中间件不是吗?

代码中的 app.get 就是Express提供的路由系统。也可以是 app.post 来处理POST请求,或者是PUT和任何的HTTP请求方式。第一个参数是路径,例如 /about 或者 /。第二个参数类似我们之前所见过的请求handler。引用Expess文档的内容:

这些请求handler和中间件一样,唯一的区别是这些回调函数会调用 next('route') 从而能够继续执行剩下的路由回调函数。这种机制

简单说来,它们和我们之前提过的中间件是一样,只不过是一些函数而已。

这些路由也可以更加灵活,看起来是这样:

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

重启服务器并在浏览器访问 localhost:1337/hello/animelover69 你会得到如下信息:

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

这些文档演示了如何使用正则表达式,可以使得路由更加灵活。如果只是单从概念理解来讲,我说的已经足够了。

但是还有更加值得我们去关注的。

特性二:请求处理 request handling

Express将你传入请求的handler传入request和response对象中。原先该有的还在,但是却加入了更多新的特性。API文档里有详细解释。下面让我们来看一些例子。

其中一个就是 redirect 方法。代码如下:

  1. response.redirect("/hello/anime");
  2. response.redirect("http://xvfeng.me");
  3. 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. // 启动Express
  2. var express = require("express");
  3. var app = express();
  4. // 設置view目錄
  5. app.set("views", __dirname + "/views");
  6. // 設置模板引擎
  7. app.set("view engine", "jade");

开头部分的代码和前面基本一样。之后我们指定视图文件所在目录。然后告诉Express我们要使用 Jade作为模板引擎。 Jade是一种模板语言。稍后将会详细介绍。

现在我们已经设置好了view.但是如何来使用它呢?

首先我们建立一个名为 index.jade 的文件并把它放入 views 目录。代码如下:

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

代码只是去掉了括号的HTML代码。如果你懂HTML那肯定也看得懂上面的代码。唯一有趣的是最后一样。 message 是一个变量。它是从哪里来的呢?马上告诉你。

我们需要从Express中渲染这个视图。代码如下:

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

Express为 response 对象添加了一个 render 方法。这个方法可以处理很多事情,但最主要的还是加载模板引擎和对应的视图文件,之后渲染成普通的HTML文档,例如这里的 index.jade.

最后一步(我觉得可能算是第一步)就是安装Jade,因为它本身并不是Express的一部分。添加至package.json 文件并使用 npm install 进行安装。

如果一起设置完毕,你会看到这个页面完整代码.

加分特性: 所有代码来自于Connect和Node

我需要再次提醒你的是Express建立与Connect和Node之上,这意味着所有的Connect中间件均可以在Express中使用。这个对与开发来讲帮助很大。例如:

  1. var express = require("express");
  2. var app = express();
  3. app.use(express.logger());  // 继承自Connect
  4. app.get("/", function(req, res) {
  5. res.send("fraser");
  6. });
  7. 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/

译文链接:Fraser Xu

深入理解 Express.js的更多相关文章

  1. 出去就餐并且理解Express.js的基本知识

    Going out to eat and understanding the basics of Express.js出去就餐并且理解Express.js的基本知识 原文:Going out to e ...

  2. 深入理解Express.js

    转自:http://xvfeng.me/posts/understanding-expressjs/ 英文原文更赞:http://evanhahn.com/understanding-express- ...

  3. 理解express中的中间件

    express是轻量灵活的node.js Web应用框架”.它可以帮助你快速搭建web应用.express是一个自身功能极简,完全是由**路由**和**中间件**构成的一个web开发框架,本质上说,一 ...

  4. node.js入门及express.js框架

    node.js介绍 javascript原本只是用来处理前端,Node使得javascript编写服务端程序成为可能.于是前端开发者也可以借此轻松进入后端开发领域.Node是基于Google的V8引擎 ...

  5. [译] 所有你需要知道的关于完全理解 Node.js 事件循环及其度量

    原文地址:All you need to know to really understand the Node.js Event Loop and its Metrics 原文作者:Daniel Kh ...

  6. 理解Express express.static 和 __direname 及 __firename的含义

    理解Express express.static 和 __direname 及 __firename的含义 一:理解 app.use(express.static(__direname + '/pub ...

  7. 手写Express.js源码

    上一篇文章我们讲了怎么用Node.js原生API来写一个web服务器,虽然代码比较丑,但是基本功能还是有的.但是一般我们不会直接用原生API来写,而是借助框架来做,比如本文要讲的Express.通过上 ...

  8. 《Pro Express.js》学习笔记——Express服务启动常规七步

    Express服务启动常规七步 1.       引用模块 var express=require('express'), compression=require('compression'), bo ...

  9. 《Pro Express.js》学习笔记——概述

    要学Node.js,先学Express.js. Express.js是Node.js官方推荐的基础框架. Express.js框架经过一系列的发展,已经到了4.x版本.新的版本解决了3.x之前版本的依 ...

随机推荐

  1. HDU 4616 Game(经典树形dp+最大权值和链)

    http://acm.hdu.edu.cn/showproblem.php?pid=4616 题意:给出一棵树,每个顶点有权值,还有存在陷阱,现在从任意一个顶点出发,并且每个顶点只能经过一次,如果经过 ...

  2. CMS收集器和G1收集器

    CMS收集器 CMS收集器是一种以获取最短回收停顿时间为目标的收集器.基于"标记-清除"算法实现,它的运作过程如下: 初始标记 并发标记 重新标记 并发清除 初始标记.从新标记这两 ...

  3. 【Python】xlrd,NotImplementedError-formatting_info=True not yet implemented

    前言 Python需要读取Excel(.xls..xlsx)时通常使用xlrd模块:如果要对其内容进行编辑的话稍稍有些麻烦,通常的做法是使用xlutils的copy模块对原文件进行复制,然后保存成新的 ...

  4. spark udf 初识初用

    直接上代码,详见注释 import org.apache.spark.sql.hive.HiveContext import org.apache.spark.{SparkContext, Spark ...

  5. CSS之旋转立方体

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  6. Leetcode 34

    //二分查找后,向两边扩展,二分错了两次,现在是对的.//还有就是vector可以用{}直接赋值很棒 class Solution { public: vector<int> search ...

  7. Reverse engineer powerdesigner link odbc

    Reverse engineer powerdesigner link odbc           Option Explicit ValidationMode = True Interactive ...

  8. DOM之概述

    body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; ...

  9. PostgreSQL脱敏示例

    mydb=# create table test_desensitization(id integer, name varchar(32), phone_num varchar(11)); CREAT ...

  10. es6 中的generator函数控制流程

    Generator函数跟普通函数的写法有非常大的区别: 一是,function关键字与函数名之间有一个星号: 二是,函数体内部使用yield语句,定义不同的内部状态(yield在英语里的意思就是“产出 ...