express学习
本文档基于express4.x版本
express Hello World
mkdir hello && cd hello && npm install express
新建
app.js
内容如下var express = require('express'); var app = express(); app.get('/', function(req, res){ res.send('hello world'); }); app.listen(3000);
-
node app.js
,然后浏览器访问http://localhost:8888
可以看到hello world
express应用模板
可以使用express工具生成应用程序框架,
npm install express-generator -g
-
express -h
显示选项 -
express myapp
在当前目录创建名为myapp
的express程序 -
cd myapp && npm install
安装所需模块 -
set DEBUG=myapp & node ./bin/www
运行app - 浏览器访问
http://localhost:3000
路由基础
路由指确定程序如何针对特定请求进行反应,这个请求是一个URI和它的HTTP请求方法。
每一个路由有一个处理函数,当路由匹配的时候执行。
路由定义格式为:app.VERB(PATH, HANDLER)
,其中app
是一个express实例,VERB
是一个HTTP请求方法,PATH
是服务器上的一个路径,HANDLER
是用于处理请求的函数。
一下代码展示app的基本路由
// respond with "Hello World" on the homepage
app.get('/', function (req, res) {
res.send('Hello World');
});
// accept POST request on the homepage
app.post('/', function (req, res) {
res.send('Got a POST request');
});
// accept PUT request at /user
app.put('/user', function (req, res) {
res.send('got a PUT request at /user');
});
// accept DELETE request at /user
app.delete('/user', function (req, res) {
res.send('got a DELETE request at /user');
});
使用中间件
express程序实质上是一系列的中间件调用。
中间件是一个可以访问请求、相应对象和下一个中间件的函数,中间件功能有:
- 执行任意代码
- 修改请求和响应对象
- 结束请求-响应周期
- 调用栈上的下一个中间件
如果当前中间件不结束请求-响应周期,它必须调用next()
将控制传递给下一个中间件,否则请求将会悬挂。
中间件有一个可选的mount路径,中间件可以再应用程序级别或者路由级别进行加载。同样,一系列的中间件函数可以同时加载,这就在mount点创建了一个中间件子栈。
express应用程序可以使用一下类型的中间件:
- 应用程序级别
- 路由级别
- built-in中间件
- 第三方中间件
应用程序级别中间件
应用程序级别中间件绑定在express示例上,使用app.use()
或者app.VERB()
var app = express();
// a middleware with no mount path;
// gets executed for every request to the app
app.use(function (req, res, next) {
console.log('Time: ', Date.now());
next();
});
// a middleware mounted on /user/:id;
// will be executed for any type of HTTP request to /user/:id
app.use('/user/:id', function (req, res, next) {
console.log('Request Type: ', req.method);
next();
});
// a route and its handler function (middleware system)
// which handles GET requests to /user/:id
app.get('/user/:id', function (req, res, next) {
res.send('user');
});
下面代码在mount点加载一系列中间件
// a middleware sub-stack which prints request info
// for any type of HTTP request to /user/:id
app.use('/user/:id', function (req, res, next) {
console.log('request url: ', req.originUrl);
next();
}, function (req, res, next) {
console.log('request type: ', req.method);
next();
});
路由处理程序,作为中间件系统的一部分,使得为同一个路径定义多个路由成为可能。下面的例子中,为GET /user/:id
定义了两个路由。第二个路由没有任何语法问题,但是永远得不到调用,因为第一个路由终止了请求-响应循环。
// a middleware sub-stack which handles GET requests to /user/:id
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 /user/:id which prints the user id
app.get('/user/:id', function (req, res, next) {
res.end(req.params.id);
});
如果想要跳过路由中间件栈剩下的中间件,调用next('route')
会将控制权转交给下一个路由。需要注意的是:next('route')
值能在使用app.VERB()
或者router.VERB()
加载的中间件中使用。
// a middleware sub-stack which handles
// GET requests to /user/:id
app.get('/user/:id', function (req, res, next) {
// if user id is 0, skip to the next route
if (req.params.id === 0) {
next('route');
}
// else pass the control to the next middleware in this stack
else {
next();
}
}, function (req, res, next) {
res.render('regular');
});
// handler for /user/:id which renders a special page
app.get('/user/:id', function (req, res, next) {
res.render('special');
});
路由级别中间件
路由级别中间件与应用程序级别中间件工作一样,唯一的区别是他们bound toexpress.Router()
的实例
var router = express.Router();
路由级别中间件通过router.use()
或者router.VERB()
加载。
前面应用程序级别的中间件可以使用路由级别代替:
var app = express();
var router = express.Router();
// a middleware with no mount path,
// get 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 /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.methos);
next();
});
// a middle ware sub-stack which handles GET requests to /user/:id
router.get('/user/:id', function (req, res, next) {
// if user id is 0, skip to the next router
if (req.params.id === 0) {
next('route');
}
// else pass the control to the next middleware in this stack
else {
next();
}
}, function (req, res, next) {
res.render('regular');
});
// handler for /user/:id 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);
自带中间件
express4.X之后不再依赖于Connect。除了自带express.static
之外,所有其他中间件都放到单独模块中。查看中间件列表
express.static(root, [options])
express.static
基于serve-static模块,负责为express应用程序提供静态资源服务。
root
参数为静态资源存放的根目录。
可选的options
对象可以有以下属性:
-
dotfiles
控制点文件服务,可选值为allow
,deny
,’ignore’默认为ignore
-
etag
控制etag生成,默认为true
-
extensions
设置文件后缀名补充,默认为false
-
index
设置目录访问的返回,默认为index.html
,设置为false
可以禁止目录访问 -
lastModified
根据文件修改时间设置Last-Modified
报头,默认true
-
maxAge
设置Cache-Control报头的缓存控制时间,单位为毫秒,默认为0 -
redirect
当路径名是目录时,重定向到包含结尾/
的目录,默认true
-
setHeaders
函数用于为文件设置HTTP头
以下是例子:
var options = {
dotfiles: 'ignore',
etag: false,
extensions: ['htm', 'html'],
index: false,
maxAge: '1d',
redirect: false,
setHeaders: function (req, res, 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查看详细选项
第三方中间件
express是一个路由和中间件web框架,自身包含很少功能,可以通过第三方中间件添加所需功能。
安装所需模块并且在应用程序级别或者路由级别加载。
下面的例子使用cookie-parser
来解析cookie
npm install cookie-parser
var express = require('express');
var app = express();
var cookieParser = require('cookie-parser');
app.use(cookieParser());
错误处理
异常处理中间件与其他中间件相比的区别是函数参数为四个(err, req, res, next)
app.use(function (err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});
尽管不是强制的,通常来说错误处理中间件定义在最后:
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
app.use(bodyParser());
app.use(methodOverride());
app.use(function (err, req, res, next) {
// logic
});
错误处理中间件返回的内容可以使任意的,比如HTML错误页面,简单的消息,JSON等等。
为了方便组织,可能需要定义多个错误处理中间件,比如你可能想为XHR请求定义错误处理程序。
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
app.use(bodyParser());
app.use(methodOverride());
app.use(logErros);
app.use(clientErrorHandler);
app.use(errorHandler);
其中logErrors将请求和错误信息输出到stderr
function logErrors(err, req, res, next) {
console.error(err.stack);
next(err);
}
clientErrorHandler定义如下:
function clientErrorHandler(err, req, res, next) {
if (req.xhr) {
res.status(500)
.send({
error: 'Something blew up'
});
}
else {
next(err);
}
}
最后的errorHandler捕获所有错误
function errorHandler(err, req, res, next) {
res.status(500);
res.render('error', {
error: err
});
}
调试express应用程序
express使用debug模块来log路由匹配,中间件使用,应用粗模式等。
debug
类似于console.log
但是不同的是发布版本时不需要注释debug日志语句,通过环境变量DEBUG
调节日志输出
查看express内部日志的方法:启动app时设置DEBUG
环境变量为express:*
DEBUG=express:* node index.js
如果只想输出路由信息,设置DEBUG=express:router
,如果只想查看应用程序相关log,设置DEBUG=express:application
更多debug信息,查看debug guide
使用模板引擎
在express渲染模板文件之前,必须执行以下设置
- views,模板文件存放目录,如
app.set('views', './views')
- view engine,使用的模板引擎,如
app.set('view engine', 'jade')
然后安装对应模板引擎的package
npm install jade --save
兼容于express的模板引擎会暴露一个__express(filePath, options, callback)
的接口,用于提供给res.render()
调用来渲染模板
一些模板引擎不遵守这个约定consolidate.js可以为流行的node模板引擎创建转换,这样就可以与express无缝衔接。
模板引擎设置好之后,不必显式指定模板引擎或者加载模块,express会自动加载
app.engine('jade', require('jade').__express);
在视图目录下创建一个jade模板文件index.jade
html
head
title!= title
body
h1!= message
然后创建一个路由渲染index.jade
app.get('/', function (req, res) {
res.render('index', {
title: 'hey',
message: 'Hello there!'
});
});
此时访问主页即可看到欢迎界面,查看如何开发express模板引擎可以学习更多知识
express4.X API
Application
express()
创建express应用程序
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('hello world');
});
app.listen(3000);
settings
express应用程序选项可以通过app.set()
进行设置,通过app.get()
读取以下是支持的选项
trust proxy
说明app位于front-facing proxy之后,X-Forwarded-*
报头可以用来确定客户端连接和IP地址。需要注意的是X-Forwarded-*
报头很容易造假,所以检测到的IP地址并不可靠。trust proxy默认是禁止的。当激活时,express尝试获取通过front-facing proxy连接的客户端IP地址。
req.ips
属性包含客户端连接中的IP地址。一下只可以用于设置:Type Value Boolean 如果是true,express假设客户端IP地址是X-Forwarded-*中最左的值。如果false,express认为直接连接客户端,req.connection.remoteAddress是客户端地址,默认值为false。 IP 地址 可信任的ip地址,子网或者IP地址数组。以下是预定义子网名字
- loopback – 127.0.0.1/8,::1/128
- linklocal – 169.254.0.0/16,fe80::/10
- uniquelocal – 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7
IP地址可以如下设置
app.set('trust proxy', 'loopback'); // specify a single subnet app.set('trust proxy', 'loopback, 123.123.123.123'); // subnet and an address app.set('trust proxy', 'loopback, linklocal, uniquelocal'); app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
When specified, the IP addresses or the subnets are excluded from the address determination process, and the untrusted IP address nearest to the application server is determined as the client’s IP address.
Number Trust the nth hop from the front-facing proxy server as the client. Function 自定义信任实现。只有你知道自己在做什么的时候才这样做 app.set('trust proxy', function (ip) { if (ip === '127.0.0.1' || ip === '123.123.123.123') { return true; // trusted IPs } else { return false; } });
查看proxy-addr更多信息。
env
环境模式,默认为process.env.NODE_ENV
(NODE_ENV环境变量)或者development
-
subdomain offset
The number of dot-separated parts of the host to remove to access subdomain, two by default. -
jsonp callback name
修改json回调名字?callback=
-
json replacer
JSON replacer callback, 默认为null -
json spaces
设置为JSON缩进,默认禁止 -
case sensitive routing
enable区分大小写,默认不区分,即/Foo
与/foo
一样 -
strict routing
enable严格路由,默认情况下/foo
和/foo/
认为相同 -
view cache
设置模板编译缓存,发布情况下默认激活 -
view engine
引擎默认后缀名 -
views
视图目录路径,默认为process.cwd() + '/views'
-
query parser
query解析器,simple
或者extended
,默认为extended
,简单query解析器基于node的原生解析器querystring
,扩展解析器基于qs -
x-powered-by
激活X-Powered-By: Express
HTTP报头,默认激活 etag
设置ETag响应头Type Value Boolean true允许强ETag。这是默认设置。false禁止ETag String 如果是`strong`使用强ETag。如果是`weak`使用弱ETag Function 自定义ETag实现。只有你知道在做什么才使用它 app.set('etag', function (body, encoding) { return generateHash(body, encoding); });
app.set(name, value)
为程序name设置value
app.set('title', 'My site');
app.get('title'); // My site
app.get(name)
获取设置的name值
app.get('title'); // undefined
app.set('title', 'My site');
app.get('title'); // My site
app.enable(name)
设置name属性为true
app.enable('trust proxy');
app.get('trust proxy'); // true
app.enabled(name)
检查name是否激活
app.enabled('trust proxy'); // false
app.enable('trust proxy');
app.enabled('trust proxy'); // true
app.disable(name)
设置name值为false
app.disable('trust proxy');
app.get('trust proxy'); // false
app.disabled(name)
检查name是否禁止
app.disabled('trust proxy'); // true
app.enable('trust proxy');
app.disabled('trust proxy'); // false
app.use([path], [function…], function)
在path路径Mount中间件函数。如果path没有设置,默认为/
.
中间件mount到path之后当请求路径的起始部分匹配了path就会执行中间件。
由于path默认值为/
所以默认情况下所有的请求都会执行中间件
// this middleware will be executed for every request to the app
app.use(function (req, res, next) {
console.log('Time: %d', Date.now());
next();
});
匹配的中间件函数会顺序执行,所以中间件配置顺序很重要
// this middleware will not allow the request to go beyond it
app.use(function (req, res, nex) {
res.send('hello world');
});
// request will never reach this route
app.get('/', function (req, res) {
res.send('Welcome');
});
path
可以使一个字符串,一个模式,一种正则表达式或者包含三种模式描述路径的数组。
Type | Example |
---|---|
Path |
|
Path Pattern |
|
Regular Expression |
|
Array |
|
function
可以使一个中间件函数,或者一系列中间件函数,或者包含中间件函数的数组或者他们的组合。由于路由和程序都实现了中间件接口,可以再其他地方像使用中间件那样使用。
Usage | Example |
---|---|
Single Middleware | 中间件函数可以定义,并且在本地mount
路由也是合法的中间件
express应用程序也是合法中间件
|
Series of Middleware | 可以再一个mount路径使用多个中间件
|
Array | 将多个中间件组装到数组中,如果数组形式的中间件是第一个参数或者唯一参数,需要制定mount路径
|
combination |
|
以下是一些使用express.static中间件的例子。
从public
目录提供静态文件服务
// GET /styles.css etc
app.use(express.static(__dirname + '/public'));
将中间件mount到/static
路径,这样只有请求路径以/static
开头才响应。
// GET /static/style.css etc.
app.use('/static', express.static(__dirname + '/public'));
禁止静态文静请求log:将log中间件配置到静态文件中间件之后
app.use(express.static(__dirname + '/public'));
app.use(logger());
在多个不同目录提供静态文件服务,放在最前面的具有最高优先级
app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/files'));
app.use(express.static(__dirname + '/uploads'));
app.engine(ext, callback)
为指定后缀名模板指定模板引擎回调。
默认情况,express使用require()
根据文件扩展名加载模板引擎。例如,如果渲染foo.jade
,express会内部执行以下操作,并且将回调进行缓存以提高性能。
app.engine('jade', require('jade').__express);
对于没有提供.__express
接口的引擎,需要定义映射,比如下面的例子
app.engine('html', require('ejs').renderFile);
在上面的例子中EJS提供.renderFile()
方法实现express所需要的(path, options, callback)
函数签名
app.param([name], callback)
映射逻辑到路由参数。例如当:user
在路由路径中出现的时候,可以通过为路由提供req.user
映射用户逻辑,或者针对参数进行验证。
注意:
- param回调函数本地化到定义的路由。不会被mount的app或者路由继承。因此app上定义的param回调只有当路由参数在app路由上定义的时候才触发
- param回调在请求-响应周期中只会调用一次,即使这个参数匹配多个路由
app.param('id', function (req, res, next, id) {
console.log('Called only once');
next();
});
app.get('/user/:id', function (req, res, next) {
console.log('although this matches');
next();
});
app.get('/user/:id', function (req, res) {
console.log('and this matches too');
res.end();
});
一下代码展示callback的用法,这很大程度上类似于中间件。代码根据参数尝试获取用户信息,如果成功,赋值给req.user
,失败调用next(err)
app.param('user', function (req, res, next, id) {
User.find(id, function (err, user) {
if (err) {
next(err);
}
else if (user) {
req.user = user;
next();
}
else {
next(new Error('failed to load user'));
}
});
});
app.METHOD(path, [callback…], callback)
app.METHOD()
方法为express程序提供路由功能,其中METHOD为HTTP方法或增强方法,使用小写,例如app.get()
,app.post()
,app.patch()
。
express支持的方法有:
- 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
- connect
对于不能转化为合法JavaScript变量的方法,使用中括号访问法如:
app['m-search']('/', function () {});
可以设置多个回调,他们都相同,就像中间件一样工作。唯一的不同时这些回调可以通过调用next('route')
跳过剩余的路由回调函数。这种方法可以用于执行条件路由,然后将控制传递给后续路由。
下面的代码展示最简单的路由定义。express将路径字符串转换为正则表达式,用于处理可能的请求。查询字符串不参与匹配。例如GET /
可以匹配GET /?name=tobi
app.get('/', function (req, res) {
res.send('hello world');
});
可以使用正则表达式对路径执行更多限制。例如以下代码可以匹配GET /commits/71dbb9c
,也可以匹配GET /commits/71dbb9c..4c084f9
app.get(/^\/commits\/(\w+)(?:\.\.(\w+))?$/, function(req, res){
var from = req.params[0];
var to = req.params[1] || 'HEAD';
res.send('commit range ' + from + '..' + to);
});
可以传递多个回调。有助于中间件复用
app.get('/user/:id', user.load, function () {
// ...
});
如果有多个通用中间件,可以使用route API中的all
var middleware = [loadForum, loadThread];
app.route('/forum/:fid/thread/:tid')
.all(loadForum)
.all(loadThread)
.get(function () {})
.post(function () {});
app.all(path, [callback..], callback)
这个方法类似于app.METHOD()
不同的是,它可以匹配所有HTTP方法。
这个方法对于执行全局逻辑或者针对满足特定前缀的路径非常有用。例如如果你将以下路由放在所有路由定义之前,这就会强制所有路径访问都进行验证,并且自动加载用户。需要注意的是这种回调不应该终止请求,loadUser
执行操作之后调用next()
传递控制权给下一个中间件。
app.all('*', requireAuthentication, loadUser);
与下面等价
app.all('*', requireAuthentication);
app.all('*', loadUser);
另一种用法是对特定路径执行指定任务如:
app.all('/api/*', requireAuthentication);
app.route(path)
返回一个路由实例,可以用户处理HTTP请求。使用app.route()
是避免多次重复路径的推荐方法
var app = express();
app.route('/events')
.all(function(req, res, next) {
// runs for all HTTP verbs first
// think of it as route specific middleware
})
.get(function (req, res, next) {
// res.json(...);
}).post(function (req, res, next) {
// may be add a new event..
});
app.locals
用于设置提供给程序所有模板的变量。对于为模板提供辅助函数和app-level数据非常有用
app.locals.title = 'My App';
app.locals.strftime = require('strftime');
app.locals.email = 'me@myapp.com';
app.locals
是一个JavaScript对象。其属性会被设置为app局部变量
app.locals.title
// => 'My App'
app.locals.email
// => 'me@myapp.com'
默认情况下。express值暴露一个app-level级别变量:settings
app.set('title', 'My App');
// use settings.title in a view
app.render(view, [options], callback)
使用回调函数渲染view
,这是app-level的res.render()
app.render('email', function (err, html) {
// ...
});
app.render('email', {name: 'Tibi'}, function (err, html) {
// ...
});
app.listen()
在指定主机,端口绑定并监听。这个方法与node的http.Server#listen()相同
var express = require('express');
var app = express();
app.listen(3000);
express()
返回的app
是一个JavaScript Function
,可以传递给node的HTTP服务器作为回调来处理请求。这样就支持同时提供同一份代码支持HTTP和HTTPS版本的app。
var express = require('express');
var https= require('https');
var http = require('http');
var app = express();
http.createServer(app).listen(80);
https.createServer(options, app).listen(443);
The app.listen() method is a convenience method for the following (if you wish to use HTTPS or provide both, use the technique above):
app.listen = function () {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
app.path()
返回app路径
var app = express();
var blog = express();
var blogAdmin = express();
app.use('/blog', blog);
blog.use('/admin', blogAdmin);
console.log(app.path()); // ''
console.log(blog.path()); // '/blog'
console.log(blogAdmin.path()); // '/blog/admin'
当app具有复杂的mount时。这个方法将变得复杂,一次推荐使用req.baseUrl
来获取路径
app.mountpath
返回子app mount的路径或者模式
var admin = express();
admin.get('/', function (req, res) {
console.log(admin.mountpath); // /admin
res.send('Admin Homepage');
});
app.use('/admin', admin); // mount the sub app
这个属性与req.baseUrl
类似,不同的是app.mountpath
返回模式。
如果子app在多个路径mustache上进行了mount。app.mountpath
返回所有模式的列表
var admin = express();
admin.get('/', function (req, res) {
console.log(admin.mountpath); // ['/adm*n', '/manager']
res.send('Admin Homepage');
});
var secret = express();
secret.get('/', function (req, res) {
console.log(secret.mountpath); // /secr*t
res.send('Admin Secret');
});
admin.use('/secr*t', secret);
app.use(['/adm*n', '/manager'], admin);
app.on(‘mount’, callback(parent))
在子app上监听mount
事件,当它被父app mount的时候触发。父app作为参数传递给回调函数。
var admin = express();
admin.on('mount', function (parent) {
console.log('Admin Mounted');
console.log(parent); // refers to the parent app
});
admin.get('/', function (req, res) {
res.send('Admin Homepage');
});
app.use('/admin', admin);
Request
req.params
这个属性是包含路由参数映射的对象,例如如果使用/user/:name
进行路由,req.params.name
上就设置了name
属性。req.params
默认值为{}
// GET /user/tj
req.params.name
// => 'tj'
当路由定义使用正则表达式时,捕获分组将出现在req.params[N]
数组对应元素,其中N是底n个捕获组。这个规则可以使用在未知的匹配情况下,如/file/*
// GET /file/js/jquery.js
req.params[0];
// => 'js/jquery.js'
req.query
保存query字符串解析后的数据,默认为{}
// GET /search?q=tobi+ferret
req.query.q;
// => 'tobi ferret'
// GET /shoes?order=desc&shoe[color]=blue^shoe[type]=converse
req.query.order
// => 'desc'
req.query.shoe.color
// => 'blue'
req.query.shoe.type
// => 'converse'
req.body
包含请求体解析后数据的键值对。默认为undefined
,通过使用解析中间件渲染后得到对应值,常见中间件为body-parser和multer
下面的例子展示使用body-parser中间件来渲染req.body
var app = require('express')();
var bodyParser = require('body-parser');
var multer = require('multer');
app.use(bodyParser.json()); // for parsing application/json
app.use(bodyParser.urlencoded({extended: true})); // for parsing application/x-www-from-urlencoded
app.use(multer()); // for parsing multipart/form-data
app.post('/', function (req, res) {
console.log(req.body);
res.json(req.body);
});
req.param(name, [defaultValue])
返回name
对应的参数值。
// ?name=tobi
req.param('name');
// => 'tobi'
// POST name=tobi
req.param('name');
// => 'tobi'
// user/tobi for /user/:name
req.param('name');
// => 'tobi'
查询顺序如下:
- req.params
- req.body
- req.query
可以指定defaultValue
,当参数名没有查找到的时候的默认值。
通常来说,最好的方法是直接查询对应对象寻找值。
req.route
返回当前匹配的Route
app.get('/user/:id?', function userIdHandler(req, res) {
console.log(req.route);
res.send('GET');
});
输出结果类似:
{ path: '/user/:id?',
stack:
[ { handle: [Function: userIdHandler],
name: 'userIdHandler',
params: undefined,
path: undefined,
keys: [],
regexp: /^\/?$/i,
method: 'get' } ],
methods: { get: true } }
req.cookies
需要cookieParser()
中间件来解析,生成用户代理包含cookie的键值对,默认为{}
。
// cookie: name=tj
req.cookies.name
// => 'tj'
参考cookie-parser更多信息。
req.signedCookies
使用cookieParser(secret)
渲染对象。 It contains signed cookies sent by the user-agent, unsigned and ready for use. Signed cookies reside in a different object to show developer intent; otherwise, a malicious attack could be placed on req.cookie values (which are easy to spoof). Note that signing a cookie does not make it “hidden” or encrypted; this simply prevents tampering (because the secret used to sign is private). If no signed cookies are sent, it defaults to {}.
// Cookie: user=tobi.CP7AWaXDfAKIRfH49dQzKJx7sKzzSoPq7/AcBBRVwlI3
req.signedCookies.user
// => "tobi"
参考cookie-parser查询更多信息。
req.get(field)
读取不区分大小写的请求头首部。Referrer
和Referer
等价。
req.get('Content-Type');
// => 'text/plain'
req.get('content-type');
// => 'text/plain'
req.get('something');
// => undefined
此方法等价于req.header(field)
req.accepts(types)
检查给定types
是否可接受(acceptable),当可以接受的时候返回最佳匹配项,如果不匹配,返回undefined
(此时可以使用406 Not Acceptable
)。
type
值可能是单个MIME type字符串如application/json
,或者扩展名json
,逗号分隔列表,或者数组。当列表或者数组设置时,函数返回最佳匹配项。
// Accept: text/html
req.accepts('html');
// => 'html'
// Accept: text/*, application/json
req.accepts('html');
// => 'html'
req.accepts('text/html');
// => 'text/html'
req.accepts('json, text');
// => 'json'
req.accepts('application/json');
// => 'application/json'
// Accept: text/*, application/json
req.accepts('image/png');
req.accepts('png');
// => undefined
// Accept: text/*; q=.5, application/json
req.accepts(['html', 'json']);
req.accepts('html, json');
// => 'json'
查看accepts寻找更多信息。
req.acceptsCharsets(charset,…)
检测制定charset是否可接受
req.acceptsLanguages(lang,…)
检测制定lang是否可接受
req.acceptsEncodings(encoding,…)
检测指定encoding是否可接受
req.is(type)
检测请求的Content-Type
是否匹配指定的MIME类型。
// with Content-Type: text/html; charset=utf-8
req.is('html');
req.is('text/html');
req.is('text/*');
// => true
// when Content-Type is application/json
req.is('json');
req.is('application/json');
req.is('application/*');
// => true
req.is('html');
// => false
查看type-is寻找更多信息。
req.ip
返回远程地址(或者trust proxy
激活时,upstream address)
req.ip;
// => '127.0.0.1'
req.ips
当trust proxy
为true
,解析X-Forwarded-For
获取ip地址列表,否则值为空数组。
req.path
返回请求URL的pathname
// example.com/users?sort=desc
req.path
// => '/users'
req.hostname
返回Host头包含的hostname值
// Host: example.com:3000
req.hostname
// => example.com
req.fresh
检测请求是否新鲜。(Last-Modified
或者ETag
是否匹配)
req.fresh
// => true
查看fresh寻找更多信息
req.stale
检查请求是否stales
(Last-Modified
和ETag
是否匹配)
req.stale
// => true
req.xhr
检查X-Requested-With
查看是否通过XMLHttpRequest
发送
req.xhr
// => true
req.protocol
Return the protocol string “http” or “https” when requested with TLS. If the “trust proxy” setting is enabled, the “X-Forwarded-Proto” header field will be trusted. If you’re running behind a reverse proxy that supplies https for you, this may be enabled.
req.protocol
// => http
req.secure
Check if a TLS connection is established. This is a short-hand for:
'https' == req.protocol;
req.subdomains
返回包含子域名的数组
// Host: tobi.ferrets.example.com
req.subdomains
// => ['ferrets', 'tobi']
req.originalUrl
包含原始的请求url,req.url
包含的是去掉mount之后的路径
// GET /search?q=something
req.originalUrl
// => /search?q=something
req.baseUrl
This property refers to the URL path, on which a router instance was mounted.
var greet = express.Router();
greet.get('jp', function (req, res) {
console.log(req.baseUrl); // /greet
res.send('Konichiwa!');
});
app.use('/greet', greet); // load the router on '/greet'
如果mount使用的是模式。取值的时候获取的不是模式,是具体值
// load the router on '/gre+t' and '/hel{2}o'
app.use(['/gre+t', '/hel{2}o'], greet);
请求/greet/jp
时,baseUrl为/greet,请求/hello/jp时baseUrl为/hello
req.baseUrl is similar to the mountpath property of the app object, except app.mountpath returns the matched path pattern(s).
Response
res.status(code)
node的res.statusCode
等价。用于设置HTTP响应状态码。
res.status(403).end();
res.status(400).send('Bad Request');
res.status(404).sendFile('/absolute/path/to/404.png');
res.set(field, [value])
设置响应头field值,或者传递对象设置多个field。
res.set('Content-Type', 'text/plain');
res.set({
'Content-Type': 'text/plain',
'Content-Length': '123',
'ETag': '12345'
});
与res.header(field, [value])
等价。
res.get(field)
读取不区分大小写的响应头field
res.get('Content-Type');
// => 'text/plain'
res.cookie(name, value, [options])
设置cookiename
为value
。value
可以是字符串或者对象,path选项默认值为/
res.cookie('name', 'tobi', {
domain: '.example.com',
path: '/admin',
secure: true
});
res.cookie('rememberme', '1', {
expires: new Date(Date.now() + 900000),
httpOnly: true
});
maxAge
选项用于设置expires
相对于当前时间的毫秒数。
res.cookie('rememberme', '1', {
maxAge: 900000,
httpOnly: true
});
也可以传递对象,最终序列为JSON,然后bodyParser()
自动解析
res.cookie('cart', {
items: [1, 2, 3]
});
res.cookie('cart', {
items: [1, 2, 3],
}, {
maxAge: 900000
});
这种方法也可以设置signed cookie。只需要设置signed
选项。When given res.cookie() will use the secret passed to cookieParser(secret) to sign the value.
res.cookie('name', 'tobi', {
signed: true
});
然后通过访问req.signedCookie
获取信息
res.clearCookie(name, [options])
清除cookie name对应的值,path默认为/
res.cookie('name', 'tobi', {
path: '/admin'
});
res.clearCookie('name', {
path: '/admin'
});
res.redirect([status], url)
express将设置的url字符串直接设置到
Location
头部,不进行验证。浏览器读取该头部并进行重定向
设置重定向url,状态码默认为302.
res.redirect('/foo/bar');
res.redirect('http://example.com');
res.redirect(301, 'http://example.com');
res.redirect('../login');
可以通过设置一个完整的url重定向到不同的网站
res.redirect('http://google.com');
重定向可以相对于主机的跟路径,例如当前在http://example.com/admin/post/new
设置重定向url为/admin
会重定向到http://example.com/admin
res.redirect('/admin');
重定向可以相对于当前URL,从http://example.com/blog/admin/
(注意末尾的斜线)重定向到post/new
得到的最终地址为http://example.com/blog/admin/post/new
res.redirect('post/new');
如果没有后面的斜线http://example.com/blog/admin
那么post/new
会重定向到http://example.com/blog/post/new
如果对以上行为感到困惑,可以将路径看做目录和文件,这样就好理解了。
同时也支持路径的相对重定向。http://example.com/admin/post/new
可以通过以下方法重定向到http://example.com/admin/post
res.redirect('..');
也可以通过设置重定向实现后退效果。express使用referer进行设置,如果没有这个值,使用默认值/
res.redirect('back');
res.location
设置location报头
res.location('/foo/bar');
res.location('foo/bar');
res.location('http://example.com');
res.location('../login');
res.location('back');
这里的url规则和res.redirect()
相同
res.send([body])
发送响应
res.send(new Buffer('whoop'));
res.send({some: 'json'});
res.send('<p>some html</p>');
res.status(404).send('sorry, can not find it');
res.status(500).send({error: 'something blew up'});
这个函数对于非流式响应会自动设置Content-Length
(没有在其他地方设置时)并且提供HTTP缓存支持。
发送Buffer时,如果其他地方没有设置,Content-Type
设置为application/octet-stream
res.set('Content-Type', 'text/html');
res.send(new Buffer('<p>some html</p>'));
返回字符串时Content-Type
设置为text/html
res.send('<p>some html</p>');
返回数组或者对象时,默认设置JSON
res.send({user: 'tobi'});
res.send([1, 2, 3]);
res.json([body])
返回json响应,参数为对象或者数组时与res.send()
等价。对于其他情况下使用这个方法可以明确设置对应报头。
res.json(null);
res.json({user: 'tobi'});
res.status(500).json({error: 'message'});
res.jsonp([body])
返回一个JSONP响应。使用方法与res.json()
相同,不同的是返回结果会加上回调函数名字以支持JSONP,默认函数名为callback
,可以通过app.set('jsonp callback name', 'aaa')
设置与请求约定的回调名参数。
res.jsonp(null);
res.jsonp({
user: 'tobi'
});
res.status(500)
.jsonp(
{
error: 'message'
});
// ?callback=foo
res.jsonp({user: 'tobi'});
// => foo({'user': 'tobi'})
app.set('jsonp callback name', 'cb');
// ?cb=foo
res.status(500).jsonp({error: 'message'});
// => foo({'error': 'message'})
res.type(type)
设置Content-Type
,根据后缀名查询对应值,如果包含/
那直接设置为对应结果。
res.type('.html');
res.type('html');
res.type('json');
res.type('application/json');
res.type('png');
res.format(object)
太长不想翻了查看res.format(object)
res.attachment([filename])
设置Content-Disposition
报头为attachment
,如果指定了filename,Content-Type
也会根据扩展名进行设置,同时Content-Disposition
的filename=参数也会设置。
res.attachment();
// Content-Disposition: attachment
res.attachment('path/to/logo.png');
// Content-Disposition: attachment; filename="logo.png"
// Content-Type: image/png
res.sendFile(path, [options], [fn])
res.sendFile
需要express版本高于4.8.0
res.sendStatus(statusCode)
设置HTTP状态码并且将响应体设置为对应描述字符
res.sendStatus(200);
// equivalent to res.status(200.send('OK'))
res.sendStatus(403);
// equivalent to res.status(403).send('Forbidden');
res.sendStatus(404);
// equivalent to res.status(404).send('Not Found')
如果设置了位置状态码。HTTP状态码也会设置为指定值,描述字符串也于指定值相同
res.sendStatus(2000);
// res.status(2000).send('2000');
res.download(path, [filename], [fn])
设置下载res.download()
res.links(links)
设置Link
响应头
res.links({
next: 'http://api.example.com/users?page=2',
last: 'http://api.example.com/users?page=5'
});
结果
Link: <http://api.example.com/users?page=2>; rel="next",
<http://api.example.com/users?page=5>; rel="last"
res.locals
设置本地变量,只能在请求/响应周期内的视图渲染可见。具体操作与app.locals相同
这个对象在暴露请求级别信息时非常有用,如请求路径名,验证信息,用户设置等。
app.use(function (req, res, next) {
res.locals.user = req.user;
res.locals.authenticated = !req.user.anonymous;
next();
});
res.render(view, [locals], callback)
渲染指定的视图,出现错误时内部调用next(err)
。提供的回调函数会产地错误和渲染后的字符串作为参数,此时不会自动发送响应。
res.render('index', function (err, html) {
// ...
});
res.render('user', {name: 'Tobi'}, function (err, html) {
// ...
});
res.vary(field)
在响应报头没有设置的情况下添加该报头。
res.vary('User-Agent').render('docs');
res.end([data], [encoding])
继承自node的http.serverResponse
,用于结束响应。唯一推荐的用处是不发送数据而快速结束响应。如果想要发送数据,推荐使用res.send()
,res.json()
等。
res.end();
res.status(404).end();
res.headersSent
用于检测HTTP投是否已经发送
app.get('/', function (req, res) {
console.log(res.headersSent); // false
res.send('OK');
console.log(res.headersSent); // true
});
Router
路由是Router或者中间件的实例。路由可以看做微型应用,智能用于执行中间件和路由函数。每一个express应用程序有一个内置的app router。
路由表现上与中间件相似,可以在app或者其他路由上通过.user()
进行使用。
一下方法创建一个路由:
var router = express.Router([options]);
可选的选项可以修改路由行为
-
caseSensitive
强制区分大小写。默认不区分,/Foo
与/foo
一样 -
strict
激活严格路由,默认情况下/foo
和/foo/
认为相同 -
mergeParams
,默认值为false
确保来自父路由的req.params
得到保留。如果父路由和子路由有参数冲突。子路由参数优先级更高。
// invoked for any requrests passed to this router
router.use(function (req, res, next) {
// .. some logic here.. like any other middleware
next();
});
// will handle any request that ends in /events
// depends on where the router is user()
router.get('/events', function (req, res, next) {
// ..
});
可以将路由设置到对应的url上针对特定url进行响应
// only requests to /calendar/* will be sent to our router
app.use('/calendar', router);
router.use([path], [function..], function)
类似于app.use()
使用中间件处理请求。mount路径默认为’/’
var express = require('express');
var app = express();
var router = express.Router();
// simple logger for this router's requests
// all requests to this router will first hit this middleware
router.use(function (req, ers, next) {
router.log('%s % %s', req.method, req.url, req.path);
next();
});
// this will only be invoked if the path starts with /bar front the mount point
router.use('/bar', function (req, res, next) {
// maybe some additional operation
next();
});
// always invoked
router.use(function (req, res, next) {
res.send('Hello World');
});
app.use('/foo', router);
app.listen(3000);
mount路径在传递给中间件时会自动去掉。这样产生的效果是中间件可以不关心具体路径前缀。
router.use()
注册中间件的顺序非常重要,所有中间件按照顺序调用。
var logger = require('morgan');
router.use(logger());
router.use(express.static(__dirname + '/public'));
router.use(function (req, res) {
res.send('Hello');
});
假设想要忽略静态文件的log,但是后续的中间件需要日志。可以将静态文件中间件放到前面。
router.use(express.static(__dirname + '/public'));
router.use(logger());
router.use(function (req, res) {
res.send('Hello');
});
另一个使用场景是在多个目录提供静态文件访问。express会按照顺序遍历目录来提供文件。
app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/files'));
router.param([name], callback)
为路由参数映射逻辑。例如:user
出现在路由路径的时候可以映射用户加载和验证操作。
- 回调函数是定义它的路由的本地函数。不会被mount的app或者路由继承。因此回调只会在定义它的路由上进行触发
在一个请求响应周期中回调函数只执行一次,即使参数匹配多个路由
app.param('id', function (req, res, next, id) { console.log('Called only once'); next(); }); app.get('/user/:id', function (req, res, next) { console.log('although this matches'); next(); }); app.get('/user/:id', function (req, res) { console.log('and thsi matches too'); res.end(); });
回调函数与中间件类似,但是多了一个参数,用于对应请求实际参数。也可以从
req.params
获取,可以加载用户之后添加到req.user
进行保存
router.route(path)
返回对应路径的一个路由实例,然后针对路由设置不同HTTP方法的逻辑。这样可以有效避免多次对同一个路径重复添加不同方法的中间件。
var router = express.Router();
router.param('user_id', function (req, res, next, id) {
// sample user, would actually fetch from DB, etc..
req.user = {
id: id,
name: 'tj'
};
next();
});
router.route('/users/:user_id')
.all(function (req, res, next) {
// runs for all HTTP verbs first
// think of it as route specific middleware
})
.get(function (req, res, next) {
res.json(req.user);
})
.put(function (req, res, next) {
// just an example of maybe updating the user
req.user.name = req.params.name;
// save user ... etc
res.json(req.user);
})
.post(function (req, res, next) {
next(new Error('not implemented'));
})
.delete(function (req, res, next) {
next(new Error('not implemented'));
});
通过以上方法可以重用/users/:user_id
来添加多个HTTP方法响应
router.METHOD(path, [callback…], callback)
为路由制定HTTP方法响应处理。
router.all(path, [callback…], callback)
为路由制定响应所有HTTP方法的中间件
FAQ
应该如何组织应用程序
这个问题没有确定的答案。取决于你程序的规模和开发团队。为了达到尽量的灵活,Express不对结构进行假设。
路由和其他程序相关的逻辑可以根据你的喜好放到任意目录和文件中。可以查看下面的例子来获得灵感:
同样存在一些第三方扩展,用于简化模式
应该如何定义model
express没有数据库的概念。这完全交给第三方模块,这样允许你与任何数据库交互。
可以查看LoopBack
应该如何验证用户
Express没有限制,你可以使用你希望的任意方法。这里是一个简单的用户名/密码模式:查看例子
express支持哪些模板引擎
express支持所有符合(path, locals, callback)
签名的模板引擎。如果想规格化模板引擎接口和缓存,查看consolidate.js。
如何从多个目录提供静态文件访问
通常可以多次使用任意的中间件。如下代码中,如果请求GET /js/jquery.js
在./public/js/jquery.js
没有找到,它继续尝试./files/js/jquery.js
app.use(express.static('public'));
app.use(express.static('files'));
我应该如何为静态文件添加路径前缀
connect的”mounting”特性允许你定义路径前缀。这样就好像前缀字符串没有出现在路径中一样。假设你需要访问GET /files/js/jquery.js
。你可以在/files
目录mount中间件,暴露/js/jquery.js
作为url。代码如下:
app.use('/files', express.static('public'));
如何处理404
express中,404不是错误的结果。因此,错误处理中间件不会捕获404.因为404表明还有工作没有完成,也就是说,express执行了所有中间件/路由,但是没有找到对应的响应处理。你需要在末尾添加一个中间件来处理404:
app.use(function (req, res, next) {
res.send(404,, 'Sorry can not find that');
});
如何设置错误处理程序
像定义其他中间件一样定义错误处理程序。唯一的区别是这个程序期望参数为四个(err, req, res, next)
,查看错误处理寻找更多信息。
app.use(function (err, req, res, next) {
console.error(err.stack);
res.send(500, 'something broke');
});