Contents
  1. 1. 前言
  2. 2. 概述
  3. 3. 三大核心组件
    1. 3.1. Middleware
      1. 3.1.1. Middleware 的定义
      2. 3.1.2. next()
      3. 3.1.3. 可配置的 Middleware
    2. 3.2. Router
      1. 3.2.1. Router 的定义
      2. 3.2.2. Router 的初始化方式和调用方式
        1. 3.2.2.1. next(‘router’)
    3. 3.3. App
      1. 3.3.1. 源码分析
      2. 3.3.2. 与 Router
  4. 4. 实战
    1. 4.1. Route Path
      1. 4.1.1. Route Paths
        1. 4.1.1.1. 常规匹配
        2. 4.1.1.2. Express 特有的 String Pattern
        3. 4.1.1.3. 常规正则表达式
      2. 4.1.2. Route Parameters
    2. 4.2. Router
      1. 4.2.1. Application-level
        1. 4.2.1.1. 基础用法
        2. 4.2.1.2. uri -> multi-handlers
        3. 4.2.1.3. uri -> multi-routers
        4. 4.2.1.4. 链式 Routers
      2. 4.2.2. Router-level
        1. 4.2.2.1. 直接定义 router 并注入 app
        2. 4.2.2.2. 将 router 模块化
      3. 4.2.3. 注意流转顺序
    3. 4.3. 实现类似切面的方法
  5. 5. 注意事项
    1. 5.1. Route Parameter
      1. 5.1.1. 模块化 Router 的参数设置
      2. 5.1.2. 参数后可直接接后缀
  6. 6. References

前言

本文是笔者所总结的有关 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

Contents
  1. 1. 前言
  2. 2. 概述
  3. 3. 三大核心组件
    1. 3.1. Middleware
      1. 3.1.1. Middleware 的定义
      2. 3.1.2. next()
      3. 3.1.3. 可配置的 Middleware
    2. 3.2. Router
      1. 3.2.1. Router 的定义
      2. 3.2.2. Router 的初始化方式和调用方式
        1. 3.2.2.1. next(‘router’)
    3. 3.3. App
      1. 3.3.1. 源码分析
      2. 3.3.2. 与 Router
  4. 4. 实战
    1. 4.1. Route Path
      1. 4.1.1. Route Paths
        1. 4.1.1.1. 常规匹配
        2. 4.1.1.2. Express 特有的 String Pattern
        3. 4.1.1.3. 常规正则表达式
      2. 4.1.2. Route Parameters
    2. 4.2. Router
      1. 4.2.1. Application-level
        1. 4.2.1.1. 基础用法
        2. 4.2.1.2. uri -> multi-handlers
        3. 4.2.1.3. uri -> multi-routers
        4. 4.2.1.4. 链式 Routers
      2. 4.2.2. Router-level
        1. 4.2.2.1. 直接定义 router 并注入 app
        2. 4.2.2.2. 将 router 模块化
      3. 4.2.3. 注意流转顺序
    3. 4.3. 实现类似切面的方法
  5. 5. 注意事项
    1. 5.1. Route Parameter
      1. 5.1.1. 模块化 Router 的参数设置
      2. 5.1.2. 参数后可直接接后缀
  6. 6. References