一.路由
路由是对一个应用的终点(即URI)以及如何回应客户端请求的定义。
路由是由一个URI,一个HTTP请求方法(GET
、POST
等)与一个或多个终点的处理器的结合。
路由以下面的格式进行定义:app.METHOD(PATH, HANDLER),来解释下这几个名词的意思:
- app:是Express应用的实例
- METHOD:指的是HTTP请求的方法(包括GET、POST等)
- PATH:是服务器上的一个路径
- HANDLER:这是一个函数,当客户端请求的URL搭配路由的时候,就会执行这个函数。
下面是一个非常基础的路由例子:
var express = require('express');
var app = express();
//当对主页发送一个GET请求时,就会回复"hello world"
app.get('/', function(req, res) {
res.send('hello world');
});
1.路由方法
路由方法是来源于HTTP方法,隶属于express
实例对象。
以下是一个用GET
和POST
方法定义路由的例子,对应用的根路径进行请求。
// GET 方法的路由
app.get('/', function (req, res) {
res.send('GET request to the homepage');
});
// POST 方法的路由
app.post('/', function (req, res) {
res.send('POST request to the homepage');
});
Express支持以下的与HTTP方法对应的路由方法:get, post, put, head, delete, options, trace, copy, lock, mkcol, move, purge, propfind, proppatch, unlock, report, mkactivity, checkout, merge, m-search, notify, subscribe, unsubscribe, patch, search, and connect
注意:
对于那些被看作是无效的JavaScript变量名的路由方法,我们要使用方括号。例如,app['m-search']('/', function ...);
有一个特殊的路由方法,app.all()
,它并非来源于HTTP的方法。它被用来加载一个路径中的中间件,对于所有类型的请求方法。
在下面的例子中,无论是使用GET, POST, PUT, DELETE,
HTTP方法,还是使用在http
模块中其它任何HTTP方法来对 “/secret”路径发送请求,处理器总会被执行。
app.all('/secret', function (req, res, next) {
console.log('Accessing the secret section ...');
next();
});
2.路由路径
路由路径,与请求方法相结合,定义了可以发送请求的网络路径。路由路径可以是字符串,字符串模式,或者正则表达式。
Express使用 path-to-regexp(路径转化为正则表达式)来搭配路由路径。在它的文档中,你可以看到定义路由路径的所有可能性。 Express Route Tester 是一个测试Express基本的路由路径的便利工具,尽管它不支持模式匹配。
<1>路由路径是字符串:
// 搭配对根路径的请求
app.get('/', function (req, res) {
res.send('root');
});
//搭配对"/about"的请求
app.get('/about', function (req, res) {
res.send('about');
});
// 搭配对"/random.text"请求
app.get('/random.text', function (req, res) {
res.send('random.text');
});
<2>.路由路径是字符串模式:
// 搭配"acd" 和 "abcd"
app.get('/ab?cd', function(req, res) {
res.send('ab?cd');
});
// 搭配 "abcd", "abbcd", "abbbcd",等
app.get('/ab+cd', function(req, res) {
res.send('ab+cd');
});
// 搭配"abcd", "abxcd", "abRABDOMcd", "ab123cd",等
app.get('/ab*cd', function(req, res) {
res.send('ab*cd');
});
// 搭配 "/abe" 和 "/abcde"
app.get('/ab(cd)?e', function(req, res) {
res.send('ab(cd)?e');
});
<3>路由路径是正则表达式:
// 搭配包含`a`的任何路径
app.get(/a/, function(req, res) {
res.send('/a/');
});
// 搭配 "butterfly", "dragonfly"; 但是不搭配 "butterflyman", "dragonfly man",等
app.get(/.*fly$/, function(req, res) {
res.send('/.*fly$/');
});
3.路由处理器
你可以提供多个回调函数,就像中间件那样来处理一个请求。唯一的异常就是这些回调函数可能会执行next('route')
来避开还未执行的回调函数。你可以使用这种机制对一个路由强制加一个先决条件,如果你不想继续处理当前路由的话,可以将控制权交给接下来的路由。
路由处理器可以是一个函数,函数的数组,或者二者结合。如下面的例子:
<1>使用一个单独的回调函数来处理路由:
app.get('/example/a', function (req, res) {
res.send('Hello from A!');
});
<2>使用多个回调函数来处理路由(确保声明next()
)
app.get('/example/b', function (req, res, next) {
console.log('response will be sent by the next function ...');
next();
}, function (req, res) {
res.send('Hello from B!');
});
<3>使用函数数组来处理路由:
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]);
<4>使用回调函数数组和一个单独的回调函数来处理路由:
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('response will be sent by the next function ...');
next();
}, function (req, res) {
res.send('Hello from D!');
});
4.响应方法
以下表中response(res)
对象中的方法可以向客户端发送一个响应,中断请求、响应循环。如果路由处理器不调用任何方法的话,客户端将会处于挂起状态。
res.download()
:提供一个下载的文件res.end()
:结束响应进程res.json()
:发送一个JSON响应res.jsonp()
:发送一个支持JSONP的JSON响应res.redirect()
:重定向一个请求res.render()
:渲染一个视图模版res.send()
:发送一个包含各种类型数据的响应res.sendFile
:发送一个数据流res.sendStatus()
:设置响应状态码,将它转化为字符串作为响应体发送出去。
5.app.route()
一个路由路径的链式路由处理器可以通过app.route()
方法来创建,因为路径处在一个单独的位置,它有助于创建模块化的路由和减少多余性、错误性。想要了解更多关于路由的知识,请看 Router()文档
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');
});
6.express.Router
express.Router
这个类可以被用来创建模块化的路由处理器。Router
实例是一个完整的中间件和路由系统,因为这个,它被称为一个“mini-app”。
以下的例子,将一个Router创建成一个模块,在模块中加载了一个中间件,定义了一个路由,并将它设置在应用的一个路径上。
在应用目录中创建一个router文件birds.js
,内容如下:
var express = require('express');
var router = express.Router();
// 这个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;
然后,在应用中加载这个router
模块:
var birds = require('./birds');
...
app.use('/birds', birds);
现在,应用能够处理对/birds
和/birds/about
发送的请求,还能对于特定的路由调用timeLog
中间件。
二.使用中间件
Express这个web框架的特色就是routing(路由)与midware(中间件),自身的功能比较少。Express应用的本质是一系列中间件的调用。
中间件相当于一个函数,通过它,可以获得request对象 (req)
和response对象 (res)
,以及在应用的request-response
循环中的下一个中间件(通常用next
变量名表示)。
中间件可以做:
- 执行任意代码
- 对
request
和response
对象修改 - 结束
request-response
循环 - 调用栈中的其它中间件
如果当前的中间件没有结束request-response
循环的话,它必须要调用next()
将控制器交给下一个中间件,否则请求就会处于挂起的状态。
一个Express应用可以使用以下类型的中间件:
- 应用级别的中间件
- Router级别的中间件
- 错误处理的中间件
- 第三方中间件
你可以使用一个可选的安装路径来加载应用级别的中间件和Router级别的中间件。当然,你可以同时加载一系列中间件函数,在一个挂载点上创建一个中间件系统的子栈。
1.应用级别的中间件
使用app.use()
和app.METHOD()
将应用级别的中间件绑定到app
对象实例中。其中METHOD
是它要处理的HTTP请求方法,例如GET
、POST
等(不过名字要小写)。
var app = express();
// 没有路径的中间件,向应用发送的任何请求都会被执行
app.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// 中间件的路径 "/user/:id"; 对"/user/:id"以任何方法发送请求都会执行
app.use('/user/:id', function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// 一个路由和中间件(中间件系统),将会处理对"/user/:id"发送的GET请求
app.get('/user/:id', function (req, res, next) {
res.send('USER');
});
下面的例子,在同一路径的同一挂载点上加载了一系列中间件:
// 一个中间件子栈,将会打印出以任何方法类型对"/user/:id"请求的信息
app.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();
});
路由处理器允许你为同一条路径定义多个路由。下面的例子为对/user/:id
路径发送的请求定义了两个路由。第二个路由不会引起任何问题,但是它永远不会被执行,因为第一个路由结束了request-response
循环。
// 一个中间件子栈处理对"/user/:id"发送的GET请求
app.get('/user/:id', function (req, res, next) {
console.log('ID:', req.params.id);
next();
}, function (req, res, next) {
res.send('User Info');
});
// 处理"/user/:id",将打印出`user id`
app.get('/user/:id', function (req, res, next) {
res.end(req.params.id);
});
为了跳过来自router中间件栈中的中间件的剩下部分,调用next('route')
方法将控制器交给下一个路由。注意:next('route')
这个方法只对使用app.METHOD()
或router.METHOD()
加载的中间件其作用。
// 中间件子栈来处理对"/user/:id"发送的GET请求
app.get('/user/:id', function (req, res, next) {
// 如果用户id为0, 跳过剩下的部分,来到下一个路由
if (req.params.id == 0) next('route');
// 否则将控制器权交给这个栈中的下一个中间件
else next(); //
}, function (req, res, next) {
// 渲染"regular"页面
res.render('regular');
});
// 处理对" /user/:id"的请求,渲染"special"页面
app.get('/user/:id', function (req, res, next) {
res.render('special');
});
2.Router级别的中间件
Router级别的中间件与应用级别的中间件工作方式一样,只不过它被绑定到express.Router()
实例上。
var router = express.Router();
使用router.use()
和router.METHOD()
方法来加载Router级别的中间件。
以下的代码使用Router级别的中间件替代上面的应用级别的中间件系统。
var app = express();
var router = express.Router();
// 没有路径的中间件,对router发送的每条请求都会执行
router.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
//中间件子栈,处理以任何方法对"/user/:id"发送的请求,打印出请求信息
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();
});
// 中间件子栈处理对"/user/:id"发送的GET请求
router.get('/user/:id', function (req, res, next) {
//如果用户id为0, 跳过剩下的部分,来到下一个路由
if (req.params.id == 0) next('route');
// 否则将控制器权交给这个栈中的下一个中间件
else next(); //
}, function (req, res, next) {
// 渲染"regular"页面
res.render('regular');
});
// 处理对" /user/:id"的请求,渲染"special"页面
router.get('/user/:id', function (req, res, next) {
console.log(req.params.id);
res.render('special');
});
// 在应用上安装router
app.use('/', router);
3.处理错误的中间件
处理错误的中间件总是有四个参数,你必须要提供四个参数来把它标识为处理错误的中间件。即使你不需要next
对象,你也要在签名中保持它,否则的话,这个中间件会被当作是普通的中间件,而不能够处理错误。
定义处理错误的中间件和定义其它中间件一样,只不过它有四个参数,而不是三个,函数签名是(err, req, res, next)
。
app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});
更多关于处理错误的中间件的细节,请看错误处理
四.内置的中间件
在4.x版本中,Express不再依赖于Connect这个中间件,除了express.static
之外,之前包含在Express中的所有中间件现在都在单独的模块中,请看中间件列表
express.static(root, [options])
Express唯一内置的中间件是express.static
,它建立在serve-static基础上,负责对应用中的静态文件服务。
root
参数声明了静态资源所在的根目录。
optional
选项对象可以有以下的属性:
对以上出现的名称进行解释:
dotfiles
:linux下(mac下)有各种app,每个人会根据自己的喜好和习惯来设置(快捷键,变量等等),而dotfiles就是保存了这些自定义设置的文件。- -
以下的例子中,使用了带有详细options
对象的express.static
中间件。
var options = {
dotfiles: 'ignore',
etag: false,
extensions: ['htm', 'html'],
index: false,
maxAge: '1d',
redirect: false,
setHeaders: function (res, path, stat) {
res.set('x-timestamp', Date.now());
}
}
app.use(express.static('public', options));
每个应用可以拥有不止一个静态文件目录:
app.use(express.static('public'));
app.use(express.static('uploads'));
app.use(express.static('files'));
更多关于serve-static
和它的选项,请看serve-static文档
五.第三方中间件
使用第三方中间件来为Express应用增加功能。
安装Node模块来满足相应的功能,在应用中以应用或者router级别来加载它。
以下的例子展示了安装并加载了用于cookie解析的中间件cookie-parser
安装:
$ npm install cookie-parser
加载:
var express = require('express');
var app = express();
var cookieParser = require('cookie-parser');
// 加载`cookie-parser`中间件
app.use(cookieParser());
看一下第三方中间件,你会发现在Express中经常使用的部分第三方中间件。