Nodejs Express 系列之一:Express 核心概念和使用细节

前言

本文是笔者所总结的有关 Nodejs Express 系列之一,

本文为作者原创作品,转载请注明出处;

概述

Nodejs 使得 javascript 通过异步的方式来构建服务器成为可能;最常见的 Server 就是 Web Server,我们可以使用原生的 Nodejs 来构建我们的 Web Server,但是需要自己去实现一系列的 Servlet 的规范,解析 HTTP Request 里面的 Header、Body 生成相应的 Response 以及实现 HTTP 规范等,这些都是繁琐且琐碎的事情;Nodejs 急需一款像 Tomcat、Jetty 这样的 Web Server 来实现 Http 和 Servlet 的规范,于是,Express 便呼之欲出;

由此,我们要知道的是,Express 的地位就好比 Tomcat、Jetty 之于 Java Web Server 的地位;不过 Express 的架构非常灵活,通过其灵活的 Router 的设计,可以非常容易的嵌入其它第三方的模块和应用,所以说,如果单纯的将其视为 Web Server 是不太合适的,它同时具备 Application Server 的特性,类似 Java 的 Spring 容器的特性,区别是,Spring 利用的是 IoC 的方式来构建应用服务器,而 Express 更多的是依赖于其 Router 的设计,一种类似于 Web Filter 的方式来构建其应用服务器,不具备 IoC 的特性,不过于笔者而言,Web Filter 反倒返璞归真,构建大多数的应用服务器往往就足够了;也因此,Express 反倒显得特别的轻巧;

本文不打算对 Node.js 的内容照着官网上照本宣科,相关的基础介绍和基础教程请参考官网 http://expressjs.com/ ,笔者撰写本文的重点是对其特性和内在逻辑进行抽象,以便在笔者的脑海里能够有一幅对 Express 的全景图;

三大核心组件

Express 的核心组件笔者用如下的一张思维导图总结了,
nodejs express mindmap design.png

其实归纳起来,Express 真实太简单了,它最核心的就是这样三大组件,Router、Middleware 和 App,通过这三大组件构建了其 Servlet 的所有特性;我们知道,Servlet 最关键的部分就是如何将请求的 URI 路由到对应的处理方法既 handler 上,通过 handler 对 URI 请求做相应的处理,并生成对应的 Response;这一部分是 Router 组件的主要职责了,它定义了 URI 到 Middleware 之间的路由关系,注意一个 Middleware 可以由多个 handlers 构成,也就是说一个 URI 可以被多个 handlers 处理;不过同样需要注意的是,同一个 URI 也可能被多个 Router 进行处理;下面,我们来看看各个组件的主要作用,

  • Middleware
    由一个或者多个 function 构成;它是构成 Router 实例的一部分;
  • Router
    Router 是 URI 和 Middleware 构成;一个 Middleware 由多个 handlers 既 functions 构成;注意两个特性
    • 一个 URI 可以交给一个或者多个 functions 处理,要注意的是,functions 是按照注册的顺序依次执行的,通过 function 的最后一个参数 next 方法 next() 来控制其流转,直到整个 request -> response 生命周期结束;
    • 一个 URI 可以交给一个或者多个 routers 处理,调用过程中,使用 next(‘router’) 自动的将处理交由下一个 router 进行处理;
  • App
    app 是一个 Express 实例,用来作为 http 模块构建 http server 的唯一参数,http server 是单例的,所以 app 在同一个 http server 实例的情况下也是单例的,而通过 app 实际上将 http server 转换成了 web server;所以,我们可以简单的认为,一个 app 就是一个 web server,对应了唯一的服务端口,而 router 构建了 uri 和 middleware (既 handlers) 之间的关系,那么剩下的就是,如何将 router 绑定到某个 app 上,在官方文档将该行为称之为 mount;

所以总结起来,Express 的核心逻辑便是,通过 Router 定义了 URI 和 Middlewares 既 functions 之间的路由关系,通过 app 将 Router 绑定(mount)到某个 web server 上;核心的逻辑便是如此了,只是 Express 的使用方式非常的灵活,该部分笔者将会在后续内容概括性的进行描述;

Middleware

Middleware 的定义

Middleware 由一个或者多个 handlers 所构成,作为某请求的回调函数;

  1. 单个 handler 的场景,

    1
    2
    3
    4
    5
    6
    7
    8
    const express = require('express')
    const app = express()

    app.get('/', function (req, res) {
    res.send('hello world')
    })

    app.listen(3000, () => console.log('Example app listening on port 3000!'))

    上面的代码 4 - 6 行便使用了一个 middleware 来处理 '/' 请求,可知这个 middleware 其实就是一个 function,

    1
    2
    3
    function (req, res) {
    res.send('hello world')
    }
  2. 多个 handlers 的场景,

    1
    2
    3
    4
    5
    6
    app.get('/user/:id', function (req, res, next) {
    console.log('ID:', req.params.id)
    next()
    }, function (req, res, next) {
    res.send('User Info')
    })

    这时,该 middleware 由多个 handlers 构成;

    注意 next() 的方法调用,表示跳转到下一个 handler 继续执行,如果不使用该方法,则不会跳转到下一个 handler 执行,

不过使用 Middleware 的场景往往比这个更复杂,这部分内容笔者将会在后续内容举例说明;

next()

在上一小节中我们看到了 next() 方法的作用,
在 request reponse cycle 的执行流程没有结束的情况下,将会顺序跳转到下一个 handler,并且如果当前 router 的 middleware 执行完成,但并没有 response,那么 next() 将会进入下一个 router 继续执行,看一个例子,

1
2
3
4
5
6
7
8
9
10
11
app.get('/user/:id', function (req, res, next) {
console.log('ID:', req.params.id)
next()
}, function (req, res, next) {
res.send('User Info')
next()
})

app.get('/user/:id', function (req, res, next) {
response.end('end')
})

这样,当第一个 GET ‘/user/:id’ router 执行完成以后,便会执行下一个 GET ‘/user/:id’ router 的 middleware;但是,如果,response end

1
2
3
4
5
6
7
8
9
10
app.get('/user/:id', function (req, res, next) {
console.log('ID:', req.params.id)
next()
}, function (req, res, next) {
res.end('User Info')
})

app.get('/user/:id', function (req, res, next) {
response.end('end')
})

或者,不再使用 next()

1
2
3
4
5
6
7
8
9
10
app.get('/user/:id', function (req, res, next) {
console.log('ID:', req.params.id)
next()
}, function (req, res, next) {
res.send('User Info')
})

app.get('/user/:id', function (req, res, next) {
response.end('end')
})

那么第二个 GET ‘/user/:id’ router 便永远不会再执行了

可配置的 Middleware

有些时候,我们期望通过参数传递的方式来自定义 Middleware,

my-middleware.js

1
2
3
4
5
6
module.exports = function(options) {
return function(req, res, next) {
// Implement the middleware function based on the options object
next()
}
}

自定义 middleware,

1
2
3
var mw = require('./my-middleware.js')

app.use(mw({ option1: '1', option2: '2' }))

Router

Router 的定义

有关 Router 的定义,官网的 API http://expressjs.com/en/4x/api.html#router 定义得非常的清楚,

A router object is an isolated instance of middleware and routes.

一个 router 对象由两部分组成,routes 和 middleware,

  • routes
    就是 request URI;
  • middleware
    一个或者多个 functions;

概括而言,Router 就是将 middleware 和相应的 uri 绑定 (mount) 起来,作用就是将某个 uri 路由到相应的 middleware,让 middleware 来处理请求;就像 MVC 中的 Servlet Dispatch 的功能;不过这里要注意的是,这里只是定义了 URI 与 Middleware 之间的映射关系,它并没有明确的指定该 Router 应该使用到哪个 App 实例上,如果要将该 Router 应用到某个 App 上,那么需要将 Router 作为调用参数绑定到某个 App 实例上;这样做的好处就是,Router 和 App 之间解耦了,同一个 Router 实例可以用在多个不同的 App 实例上,如何绑定参考下一章节的内容;

Router 的初始化方式和调用方式

如何定义一个 Router?两种方式

  1. 通过定义 Application-level Middleware 的方式定义,像这样,

    1
    2
    3
    app.get('/', function (req, res) {
    res.send('hello world')
    })
  2. 通过定义 Router-level Middleware 的方式定义,见后续章节内容

注意,如果要想调用流程在多个 Router 中进行流转,使用 next(‘router’),见下一小节内容,

next(‘router’)

如果需要提前的跳转出当前的 Middleware function stack,使用 next(‘route’),见下面这个例子,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
app.get('/user/:id', function (req, res, next) {
// if the user ID is 0, skip to the next route
if (req.params.id === '0') next('route')
// otherwise pass the control to the next middleware function in this stack
else next()
}, function (req, res, next) {
// render a regular page
res.render('regular')
})

// handler for the /user/:id path, which renders a special page
app.get('/user/:id', function (req, res, next) {
res.render('special')
})

代码第 3 行,如果 req.params.id === ‘0’,便会直接跳出当前的 middleware stack 并跳转至第二个 router;

App

源码分析

App 就是一个 Express 实例,

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

用来创建 Http Server 使用的,

1
app.listen(3000, () => console.log('Example app listening on port 3000!'))

我们便开启了一个监听在 3000 端口上的 Web Server 用来接收请求,app 其实正是用来构建 Node.js 的 http server 的必要参数,分析其源码可知,

1
2
3
4
app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};

从源码可知,http.createServer(this) 使用的正是 app 实例来初始化构建 http server 的,而 app 是 Express 的一个实例,Express 通过 exports 将其导出,

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
exports = module.exports = createApplication;

/**
- Create an express application.
*
- @return {Function}
- @api public
*/

function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};

mixin(app, EventEmitter.prototype, false);
mixin(app, proto, false);

// expose the prototype that will get set on requests
app.request = Object.create(req, {
app: { configurable: true, enumerable: true, writable: true, value: app }
})

// expose the prototype that will get set on responses
app.response = Object.create(res, {
app: { configurable: true, enumerable: true, writable: true, value: app }
})

app.init();
return app;
}

可以看到 app 正好是一个 function(req, res, next) 对象,用来作为构建 http server 的必要参数,而 http server 是单例的,所以 app 在一个 web server 中同样是单例的;不过,当笔者分析到这里的时候,突然想到,其实通过构建多个 app 实例,可以非常容易的构建出多个 web server,通过 router 可以将一些公共的 uri <-> request/response 进行抽象,比如说,用户的身份认证、授权等操作,可以通过 Router 抽象出来,并供多个 app 实例使用,将来在架构设计的时候可以充分的考虑这种模式;

与 Router

本小节简单的介绍如何将 Router 作用到 App 实例上;

  1. 直接使用 app 方法,比如通过

    1
    2
    3
    app.get('/', function (req, res) {
    res.send('Hello World!')
    })

    这样,就相当于将一个 '/' 请求的 Router 实例直接作用到了 app 上;这种方式在 Express 中有专门的命名,叫做 Application-level Middleware

  2. 将 Router 实例注入 app 实例,看如下的代码片段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var router = express.Router()

    // handler for the /user/:id path, which renders a special page
    router.get('/user/:id', function (req, res, next) {
    console.log(req.params.id)
    res.render('special')
    })

    // mount the router on the app
    app.use('/', router)

    这样,我们将 router 实例注入了 app,当访问请求 /user/:id 的时候,便会调用由 Router 实例所映射的 middleware,

    1
    2
    3
    4
    function (req, res, next) {
    console.log(req.params.id)
    res.render('special')
    }

    这种方式在 Express 中有专门的命名,叫做 Router-level Middleware

如果是专属于某个 app 的 Router,笔者推荐使用 Application-level Middleware 的方式直接将某个 Router 作用到该 app 上即可,但如果是某个通用的 Router,比如用户登录、认证和授权流程,笔者推荐使用 Router-level Middleware 的方式;这两部分的详细分析内容参看后续 Router 的章节;

实战

本章节并非是教大家如何写一个简单的 Express 用例,Express 的入门实例可以参考官网的 Hello World 实例,由于官网上的内容过于的零散,所以笔者本章节主要通过实例的方式,将 Express 核心组件之间的细节给串清楚,以至于能够使笔者的脑海中能够有一个清晰的概念图景;

Route Path

Route Paths

常规匹配

  1. 匹配根路径

    1
    2
    3
    app.get('/', function (req, res) {
    res.send('root')
    })
  2. 匹配 /bout

    1
    2
    3
    app.get('/about', function (req, res) {
    res.send('about')
    })
  3. 匹配 /random.text

    1
    2
    3
    app.get('/random.text', function (req, res) {
    res.send('random.text')
    })

Express 特有的 String Pattern

String Pattern 是 Express 特有的匹配模式,

  1. 匹配 acd 和 abcd.

    1
    2
    3
    app.get('/ab?cd', function (req, res) {
    res.send('ab?cd')
    })

    表示 b 可以出现 0 或者 1 次;

  2. 匹配 abcd, abbcd, abbbcd …

    1
    2
    3
    app.get('/ab+cd', function (req, res) {
    res.send('ab+cd')
    })

    表示 b 最少出现一次;

  3. 匹配 abcd, abxcd, abRANDOMcd, ab123cd…

    1
    2
    3
    app.get('/ab*cd', function (req, res) {
    res.send('ab*cd')
    })

    * 号表示可以匹配任意字符;

  4. 匹配 /abe 和 /abcde.

    1
    2
    3
    app.get('/ab(cd)?e', function (req, res) {
    res.send('ab(cd)?e')
    })

    表示 cd 出现 0 次或者 1次;

常规正则表达式

  1. 只要路径中出现了“a”,便会匹配

    1
    2
    3
    app.get(/a/, function (req, res) {
    res.send('/a/')
    })

    注意写法,表达式不使用引号;

  2. 匹配 butterfly、dragonfly…
    1
    2
    3
    app.get(/.*fly$/, function (req, res) {
    res.send('/.*fly$/')
    })

Route Parameters

Route 参数表示的就是 URI 参数,按照 URI 参数的标准,URI 参数既是 URL 路径的一部分,比如,

1
2
3
Route path: /users/:userId/books/:bookId
Request URL: http://localhost:3000/users/34/books/8989
req.params: { "userId": "34", "bookId": "8989" }

  • Request URL
    访问的完整路径
  • Route path: /users/:userId/books/:bookId
    要特别注意,Route path 在 Express 中表示的就是除开 Domain 以外的其它部分,其中的 :userId 和 :bookId 就表示 URI 参数;
  • req.params
    Express 会自动将 Route 参数进行解析成 json 对象并存储在 req.params 对象中;

Router

有前面的描述可知,Router 主要分为两种类型,一种是 Application-level,另外一种是 Router-level;注意,这两种并非是两种不同类型的 Router,相反,它们都是 Routers,

Application-level

将 Router 直接应用到 app 实例上,这种使用的方式叫做 Application-level;先来看看在 app 上是如何定义 router 的,来看一个简单的例子,

1
2
3
app.get('/', function (req, res) {
res.send('GET request to the homepage')
})

这样便在 application-level 上定义了一个 router;比如,我们继续定义,

1
2
3
4
5
6
7
app.get('/', function (req, res) {
res.send('GET request to the homepage')
})

app.get('/', function (req, res) {
res.send('GET request')
})

这样是,实际上,我们针对请求路径 ‘/‘ 定义了两个 router,但第二个 router 永远不会被执行到;更详细的分析参考后续章节,

基础用法

  1. 使用常规的 http methods 来定义,来看一个最简单的例子,

    1
    2
    3
    4
    // GET method route
    app.get('/', function (req, res) {
    res.send('GET request to the homepage')
    })

    上面这个例子做了两件事情,

    • 创建一个 Router 实例,定义了如下的两件事情,
      1. 路由是由访问路径 ‘/‘ 到 function(req, res) 的映射过程;
      2. http method 是 get
    • 将 Router 实例绑定到 app 实例上,这里直接使用 app.get() 方法定义了 Router 并实现了绑定;这种方式虽然便捷,但是 Router 就只能使用在该 app 上了;

    同样,我们可以定义一个 post method Router

    1
    2
    3
    4
    // POST method route
    app.post('/', function (req, res) {
    res.send('POST request to the homepage')
    })

    通过下面的这种方式 app.all() 可以对所有 http methods 进行定义,

    1
    2
    3
    4
    app.all('/secret', function (req, res, next) {
    console.log('Accessing the secret section ...')
    next() // pass control to the next handler
    })
  2. 使用 app.use 的方式,

    1. 不 mount path 同时也不 mount method

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      var express = require('express')
      var app = express()

      var myLogger = function (req, res, next) {
      console.log('LOGGED')
      next()
      }

      app.use(myLogger)

      app.get('/', function (req, res) {
      res.send('Hello World!')
      })

      app.listen(3000)

      上面这个例子表示,在后续发生任何 app router 调用之前,都会调用 myLogger 输出 ‘LOGGED’,注意,这里的 app.use 并没有 mount path 和 method,所以,myLogger 中间件将会在任意的 URI 和任意的 Request Method 的情况下调用;

    2. mount path

      1
      2
      3
      4
      app.use('/user/:id', function (req, res, next) {
      console.log('Request Type:', req.method)
      next()
      })

      这种方式等同于使用

      1
      2
      3
      app.all('/user/:id', function (req, res, next){
      ...
      });
    3. 集成第三方中间件,比如集成 cookie-parser

      1
      2
      3
      4
      5
      6
      var express = require('express')
      var app = express()
      var cookieParser = require('cookie-parser')

      // load the cookie-parsing middleware
      app.use(cookieParser())

uri -> multi-handlers

常规的例子

一个 middleware 可以由多个 handlers 构成,

1
2
3
4
5
6
app.get('/example/b', function (req, res, next) {
console.log('the response will be sent by the next function ...')
next()
}, function (req, res) {
res.send('Hello from B!')
})

上面的 next() 表示 request 流程并没有结束,将会继续执行下一个 handler 既下一个 callback function,注意,如果上面的 next() 方法漏掉了,将不会触发后续的 handler 并且也没有任何的 response;上述的定义方式可以简化为使用数组的方式,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var cb0 = function (req, res, next) {
console.log('CB0')
next()
}

var cb1 = function (req, res, next) {
console.log('CB1')
next()
}

var cb2 = function (req, res) {
res.send('Hello from C!')
}

app.get('/example/c', [cb0, cb1, cb2])

还可以使用数组和方法混用的方式,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var cb0 = function (req, res, next) {
console.log('CB0')
next()
}

var cb1 = function (req, res, next) {
console.log('CB1')
next()
}

app.get('/example/d', [cb0, cb1], function (req, res, next) {
console.log('the response will be sent by the next function ...')
next()
}, function (req, res) {
res.send('Hello from D!')
})

无效的 router

1
2
3
4
5
6
7
8
9
10
11
app.get('/user/:id', function (req, res, next) {
console.log('ID:', req.params.id)
next()
}, function (req, res, next) {
res.send('User Info')
})

// handler for the /user/:id path, which prints the user ID
app.get('/user/:id', function (req, res, next) {
res.end(req.params.id)
})

这里要注意的是,因为第一个 router 执行完以后,没有继续调用 next() 或者 next(‘route’),因此第二个 router 永远不会被执行;

uri -> multi-routers

针对同一个 http request 的 URI 请求可以定义多个 multi-router

1
2
3
4
5
6
7
8
9
10
11
app.get('/user/:id', function (req, res, next) {
console.log('ID:', req.params.id)
next()
}, function (req, res, next) {
res.send('User Info')
})

// handler for the /user/:id path, which prints the user ID
app.get('/user/:id', function (req, res, next) {
res.end(req.params.id)
})

链式 Routers

可以通过 app.route() 方法将某个 URI 相关的 http 方法定义在一起,

1
2
3
4
5
6
7
8
9
10
app.route('/book')
.get(function (req, res) {
res.send('Get a random book')
})
.post(function (req, res) {
res.send('Add a book')
})
.put(function (req, res) {
res.send('Update the book')
})

Router-level

1
var router = express.Router()

通过 express.Router() 初始化一个 router 实例,然后为其填充相应的 URI 和 handlers,

直接定义 router 并注入 app

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
36
37
var app = express()
var router = express.Router()

// a middleware function with no mount path. This code is executed for every request to the router
router.use(function (req, res, next) {
console.log('Time:', Date.now())
next()
})

// a middleware sub-stack shows request info for any type of HTTP request to the /user/:id path
router.use('/user/:id', function (req, res, next) {
console.log('Request URL:', req.originalUrl)
next()
}, function (req, res, next) {
console.log('Request Type:', req.method)
next()
})

// a middleware sub-stack that handles GET requests to the /user/:id path
router.get('/user/:id', function (req, res, next) {
// if the user ID is 0, skip to the next router
if (req.params.id === '0') next('route')
// otherwise pass control to the next middleware function in this stack
else next()
}, function (req, res, next) {
// render a regular page
res.render('regular')
})

// handler for the /user/:id path, which renders a special page
router.get('/user/:id', function (req, res, next) {
console.log(req.params.id)
res.render('special')
})

// mount the router on the app
app.use('/', router)
  • 该 router 的 URI 将会绑定到相对路径 ‘/‘ 上,因此,当 request URI === ‘/user/:id’,便会调用该 router;
  • router.use(uri, handlers)
    为任何 http method 的 uri 注册 handlers;
  • router.get(uri, handlers)
    为 GET http method 的 uri 注册 handlers;

将 router 模块化

假设 app.js 的路径为 /app/app.js,birds.js 的路径为 /app/birds.js,在同一个包路径下,

bird.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var express = require('express')
var router = express.Router()

// middleware that is specific to this router
router.use(function timeLog (req, res, next) {
console.log('Time: ', Date.now())
next()
})
// define the home page route
router.get('/', function (req, res) {
res.send('Birds home page')
})
// define the about route
router.get('/about', function (req, res) {
res.send('About birds')
})

module.exports = router

app.js

1
2
3
4
5
var birds = require('./birds')

// ...

app.use('/birds', birds)

通过 app.use() 指定了 URI 的相对路径 ‘/birds’ 来引用 birds 模块,针对 app 实例的 web server,通过 URI /birds/birds/about 可以访问到 birds 模块中所暴露的 routers;

所以,这里要特别特别注意的是,也是在开发过程中特别容易出错的地方,需要记住的是,app 中定义的访问路径,/birds

1
app.use('/birds', birds)

与 Router 模块中所定义的访问路径,'/''/about'

1
2
3
4
5
6
7
8
// define the home page route
router.get('/', function (req, res) {
res.send('Birds home page')
})
...
router.get('/about', function (req, res) {
res.send('About birds')
})

之间是拼接的关系;

  • 如果期望由 app 来全权控制访问路径,那么在 Router 中可以不指定访问路径,既是使用 '/' 即可;
  • 同样,如果不期望由 app 来指定令,而是全权由 Router 自己来控制访问路径,那么 app 可以使用如下方式定义,既不指定访问路径,

    1
    app.use(birds)

    然后,由 router 来制定访问路径,

    1
    2
    3
    4
    5
    6
    7
    router.get('/birds/', function (req, res) {
    res.send('Birds home page')
    })
    ...
    router.get('/birds/about', function (req, res) {
    res.send('About birds')
    })

注意流转顺序

默认情况下,URI 是按照 Router 所配置的顺序依次执行的,比如,我们定义有下面这样的 Routers,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
app.use('/', index);
app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});

// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};

// render the error page
res.status(err.status || 500);
res.render('error');
});

此时,若我们想添加一个新的 URI /manager,如果像这样添加,将它添加到末尾,

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
app.use('/', index);
app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});

// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};

// render the error page
res.status(err.status || 500);
res.render('error');
});

app.get("/manager", function(req, res, next) {
console.log("get index");
res.render('index', { title: 'Express' });
});

当访问 /manager 则会得到 404 Resource Not Found 的错误,原因是 Router 是按照上述代码的顺序进行匹配的,当匹配完了 / 和 /users 以后,便会匹配 404 的错误反馈,因此,虽然这里添加了 /manager URI 的 Router 但是却执行不到这里;因此,正确的添加方式是,将它添加到 /users 请求之后,像这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
app.use('/', index);
app.use('/users', users);
app.get("/manager", function(req, res, next) {
console.log("get index");
res.render('index', { title: 'Express' });
});

// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});

// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};

// render the error page
res.status(err.status || 500);
res.render('error');
});

实现类似切面的方法

TODO,我要监控每一个方法执行的时间;

注意事项

本章节笔者就自己开发过程中遇到的问题进行汇总,

Route Parameter

模块化 Router 的参数设置

如果使用模块化的 Router 的方式来定义,那么 Route Parameter 必须设置在 Router 模块上,否则不生效,如下,

app.js

1
2
3
4
...
var product = require('./routes/product')
...
app.use('/product/:productId', product);

product.js

1
2
3
4
router.get('/', function(req, res, next) {
logger.debug('the params: %s', Object.keys(req.params).length)
res.render('product', { title: 'Product' });
});

访问 localhost:3000/product/111,结果这个时候,req.params 中没有任何参数输出,也就是说,上面的这种方式,Express 不会解析任何参数化 URI 的值;经过测试,发现必须使用如下的方式中的一种,才可以顺利的解析出相关的 URI 参数,

  1. 将 URI 路径全部由 Router 自己来配置
    app.js

    1
    app.use(product);

    product.js

    1
    2
    3
    4
    router.get('/product/:productId', function(req, res, next) {
    logger.debug('the params: %s', Object.keys(req.params).length)
    res.render('product', { title: 'Product' });
    });
  2. 或者参数部分由 Router 来配置
    app.js

    1
    app.use('/product', product);

    product.js

    1
    2
    3
    4
    router.get('/:productId', function(req, res, next) {
    logger.debug('the params: %s', Object.keys(req.params).length)
    res.render('product', { title: 'Product' });
    });

参数后可直接接后缀

有时候我们需要给参数化的 URI 加上后缀,且该后缀必须直接添加到参数的后面,比如想下面这个链接那样,

1
/product/111.html

那么我们是否可以通过参数化的 URI 来进行配置呢?比如像这样,

1
/product/:productId.html

答案是可行的,可以顺利的通过 req.params 解析到结果;

References

expressjs.com: http://expressjs.com/en/guide/writing-middleware.html
webpack: https://zhuanlan.zhihu.com/p/20782320