中间件组件 | 介绍 |
---|---|
cookieParser() | 为后续中间件提供req.cookies和req.signedCookies |
bodyParser() | 为后续中间件提供req.body和req.files |
limit() | 基于给定字节长度限制请求主体的大小,必须用在bodyParser中间件之前 |
query() | 为后续中间件提供req.query |
logger() | 将HTTP请求的信息输出到stdout或日志文件之类的流中 |
favicon() | 响应/favicon.ico HTTP请求。通常放在中间件logger前面,这样它就不会出现在你的日志文件中了 |
methodOverride() | 可以替不能使用正确请求方法的浏览器仿造req.method,依赖于bodyParser |
vhost() | 根据指定的主机名使用给定的中间件和/或HTTP服务器实例 |
session() | 为用户设置一个HTTP会话,并提供一个可以跨越请求的持久化req.session对象。依赖于cookieParser |
basicAuth() | 为程序提供HTTP基本认证 |
csrf() | 防止HTTP表单中的跨站请求伪造攻击,依赖于session |
errorHandler() | 当出现错误时把堆栈跟踪信息返回给客户端。在开发时很实用,不过不要用在生产环境中 |
static() | 把指定目录中的文件发给HTTP客户端。跟Connect的挂载功能配合得很好 |
compress() | 用gzip压缩优化HTTP响应 |
directory() | 为HTTP客户端提供目录清单服务,基于客户端的Accept请求头提供经过优化的结果 |
下面的代码只做参考用,版本太旧
/* cookieParser()的基本用法 */
var connect = require('connect');
var app = connect()
.use(connect.cookieParser('tobi is a cool ferret'))
.use(function(req, res) {
console.log(req.cookies);
console.log(req.signedCookies);
// 设定出站cookie
res.setHeader('Set-Cookie', 'foo=bar');
// 设定cookie的有效期
res.setHeader('Set-Cookie', 'tobi=ferret; Expires=Tue, 08 Jun 2021 10:18:14 GMT');
res.end('hello\n');
}).listen(3000);
/* bodyParser():解析表单的POST请求主体 */
app.use(connect.bodyParser())
.use(function(req, res) {
// 基本用法
console.log(req.body.username);
// 解析MULTIPART <FORM>数据
console.log(req.body);
console.log(req.files);
res.end('bodyParser!');
});
/* limit():请求主体的限制 */
// 基本用法
var app = connect()
.use(connect.limit('32kb'))
.use(connect.bodyParser())
.use(hello);
http.createServer(app).listen(3000);
// 改进做法
function type(type, fn) {
return function(req, res, next) {
var ct = req.headers['content-type'] || '';
if (0 != ct.indexOf(type)) { // 检查content-type
return next();
}
fn(req, res, next);
}
}
var app = connect()
.use(type('application/x-www-form-urlencoded', connect.limit('64kb')))
.use(type('application/json', connect.limit('32kb')))
.use(type('image', connect.limit('2mb')))
.use(type('video', connect.limit('300mb')))
.use(connect.bodyParser())
.use(hello);
/* query():查询字符串解析,解析GET请求 */
var app = connect()
.use(connect.query())
.use(function(req, res, next) {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(req.query));
});
/********************************************************
* 实现Web程序核心功能的中间件
********************************************************/
/* logger():记录请求 */
var connect = require('connect');
var app = connect()
.use(connect.logger()) // 没有参数,使用默认的logger选项
.use(hello)
.listen(3000);
// 定制日志格式
var app = connect()
.use(connect.logger(':method :url :response-time ms'))
.use(hello);
/******************************
信令
:req[头名称] 比如::req[Accept]
:res[头名称] 比如::res[Content-Length]
:http-version
:response-time
:remote-addr
:date
:method
:url
:referrer
:user-agent
:status
*******************************/
// 定制信令:只需要给connect.logger.token函数提供信令名称和回调函数
var url = require('url');
connect.logger.token('query-string', function(req, res) {
return url.parse(req.url).query;
});
// STREAM选项
var fs = require('fs');
var log = fs.createWriteStream('/var/log/myapp.log', {flag = 'a'});
var app = connect()
.use(connect.logger({ format: ':method :url', stream: log}))
.use(hello);
// immediate选项:一收到请求就写日志
var app = connect()
.use(connect.logger({immediate: true}))
.use(hello);
// buffer选项:接受一个数值,以毫秒为单位指定缓冲区刷新的时间间隔,或者只传入true使用默认间隔
/* favicon(): 提供favicon */
// 还可以传入一个maxAge参数,指明浏览器应该把favicon放在内存中缓存多长时间
connect()
.use(connect.favicon(__dirname + '/public/favicon.ico'))
.use(function(req, res) {
res.end('Hello World!\n')
});
/* methodOverride():伪造HTTP方法 */
// HTML输入控件默认的名称是_method,但是可以给methodOverride()传入一个定制值,connect.use(connect.methodOverride('__method__'))
var connect = require('connect');
function edit(req, res, next) {
if ('GET' != req.method) return next();
res.setHeader('Content-Type', 'text/html');
res.write('<form method="post">');
res.write('<input type="hidden" name="_method" value="put" />');
res.write('<input type="submit" name="user[name]" value="Tobi"/>');
res.write('</form>');
res.end();
}
function update(req, res, next) {
// 访问原始的req.method属性
console.log(req.originalMethod);
if ('PUT' != req.method) return next();
res.end('Updated name to ' + req.body.user.name);
}
var app = connect()
.use(connect.logger('dev'))
.use(connect.bodyParser())
.use(connect.methodOverride())
.use(edit)
.use(update)
.listen(3000);
/* vhost():虚拟主机 */
// 通过请求头Host路由请求
// 两个参数:第一个是主机名,vhost实例会用它进行匹配;第二个是http.Server实例
var app = require('./sites/expressjs.dev');
server.use(connect.vhost('expressjs.dev', app));
// 从文件系统中生成一个主机列表
var connect = require('connect');
var fs = require('fs');
var app = connect();
var sites = fs.readdirSync('source/sites');
sites.forEach(function(site) {
console.log(' ...%s', site);
app.use(connect.vhost(site, require('./sites/' + site)));
});
app.listen(3000);
/* session():会话管理 */
// session()需要用签名cookie,所以应该在它上面使用cookieParser(),并传给它一个秘钥
var connect = require('connect');
var app = connect()
.use(connect.favicon())
.use(connect.cookieParser('keyboard cat'))
.use(connect.session())
.use(function(req, res, next) {
var sess = req.session;
if (sess.views) {
res.setHeader('Content-Type', 'text/html');
res.write('<p>views: ' + sess.views + '</p>');
res.end();
sess.views++;
} else {
sess.views = 1;
res.end('welcome to the session demo. refresh!');
}
});
app.listen(3000);
// 设定会话有效期
var hour = 3600000;
var sessionOpts = {
key: 'myapp_sid', // 配置会话的名称
cookie: {maxAge: hour * 24, secure: true} // 24小时后过期,只有在HTTPS时才发送会话cookie
};
app.use(connect.cookieParser('keyboard cat'));
app.use(connect.session(sessionOpts));
// 处理会话数据
// 赋给req.session对象的所有属性都会被保存下来;当相同的用户(浏览器)再次发来请求时,会加载它们
req.session.cart = {items: [1, 2, 3]};
req.session.cart.items.push(4);
// 操纵会话cookie
res.write('<p>views: ' + sess.views + '</p>');
res.write('<p>expires in: ' + (sess.cookie.maxAge / 1000) + 's</p>');
res.write('<p>httpOnly: ' + sess.cookie.httpOnly + '</p>');
res.write('<p>path: ' + sess.cookie.path + '</p>');
res.write('<p>domain: ' + sess.cookie.domain + '</p>');
res.write('<p>secure: ' + sess.cookie.secure + '</p>');
/********************************************************
* 处理Web程序安全的中间件
********************************************************/
/* basicAuth():HTTP基本认证 */
// 第一种方法,传入用户名和密码
var app = connect()
.use(connect.basicAuth('tj', 'tobi'));
// 第二种方法,提供回调函数
var users = {
tobi: 'foo',
loki: 'bar',
jane: 'baz'
};
var app = connect()
.use(connect.basicAuth(function(user, pass) {
return users[user] == pass;
});
// 第三种方法:提供异步回调函数
var app = connect();
app.use(connect.basicAuth(function(user, pass, callback) {
// 执行数据库验证函数,当数据库响应完成时,运行异步回调函数
User.authenticate({user: user, pass: pass}, gotUser);
function gotUser(err, user) {
if (err) return callback(err);
callback(null, user);
}
}));
/* csrf():跨站请求伪造防护 */
// 必须确保csrf()添加在了bodyParser()和session()的下面
connect()
.use(connec.bodyParser())
.use(connect.cookieParser('secret'))
.use(connect.session())
.use(connect.csrf());
/* errorHandler():开发错误处理 */
// 可以基于请求头域Accept提供详尽的HTML、JSON和普通文本错误响应
var app = connect()
.use(connect.logger('dev'))
.use(function(req, res, next) {
setTimeout(function() {
next(new Error('something broke!'));
}, 500);
})
.use(connect.errorHandler());
/*******************************************************
* 提供静态文件服务的中间件
*******************************************************/
/* static():静态文件服务 */
// 基本用法
// static 会根据请求的URL检查./public/中的普通文件,如果文件存在,响应中的Content-Type域的值默认会根据文件的扩展名设定,并传输文件中的数据,如果请求的路径不是文件,则调用next(),让后续的中间件处理该请求
// curl http://localhost/foo.js -i
app.use(connect.static('public'));
// 使用带挂载的static()
// curl http://localhost/app.files/foo.js
app.use('/app/files', connect.static('public'));
// 使用绝对路径指定根目录
app.use('/app/files', connect.static(__dirname + '/public'));
// 请求目录时返回index.html:当请求的是目录,并且那个目录下有index.html时,它可以返回这个文件作为响应
/* compress():压缩静态文件 */
// compress()组件通过请求头域Accept-Encoding自动检测客户端可接受的编码,如果请求头中没有该域,则不会对响应做处理
// 基本用法
var connect = require('connect');
var app = connect()
.use(connect.compress())
.use(connect.static('source'));
app.listen(3000);
// 使用定制的过滤器函数
// 只压缩普通文本
function filter(req) {
var type = req.getHeader('Content-Type') || '';
return 0 == type.indexOf('text/plain');
}
connect()
.use(connect.compress({filter: filter}));
// 指定压缩及内存水平
// 下面这个例子level被设为3,压缩水平更低但更快,memLevel被设为8,使用更多内存加快压缩速度
connect()
.use(connect.compress({level: 3, memLevel:8}));
/* directory():目录列表 */
// 这个组件要配合static()使用,由static()提供真正的文件服务,而directory()只是提供列表
var connect = require('connect');
var app = connect()
.use(connect.directory('public'))
.use(connect.static('public'));
app.listen(3000);
// 使用带挂载的directory()
// 这里的选项icons用来启用图标,hidden表明两个组件都可以查看并返回隐藏文件
var app = connect()
.use('/files', connect.directory('public', {icons: true, hidden: true}))
.use('files', connect.static('public', {hidden: true}));
app.listen(3000);