内容
- 搭建一个Connect程序
- Connect中间件的工作机制
- 为什么中间件的顺序很重要
- 挂载中间件和服务器创建可配置的中间件
- 使用错误处理中间件
Connect常用的组件
在Connect中,中间件组件是一个函数,它拦截HTTP服务器提供的请求和响应对象,执行逻辑,然后或者结束响应,或者把它传递给下一个中间件组件。
Connect用分派器把中间件“连接”在一起
- 请求日志
- 静态文件服务
- 请求体解析
- 会话管理
使用connect
1)安装
npm install connect
2)引入
Connect创建的“程序”实际上是一个JavaScript函数,用来接收HTTP请求并把它派发给你指定的中间件
它依次调用所有附着的中间件组件,直到其中一个决定响应该请求。如果直到中间件列表末尾还没有组件决定响应,程序会用404作为响应
var connect = require('connect');
var app = connect();
app.listen(3000);
connect工作机制
在Connect中,中间件组件是一个JavaScript函数,按惯例会接受三个参数:
一个请求对象,
一个响应对象,
还有一个通常命名为next的参数,它是一个回调函数,表明这个组件已经完成了它的工作,可以执行下一个中间件组件了
使用中间件
中间件组件hello的参数中没有next回调。因为这个组件结束了HTTP响应,从不需要把控制权交回给分派器var connect = require('connect'); var app = connect(); app.use(logger); app.use(hello); app.listen(3000, function() { console.log('listening...'); }); function logger(req, res, next) { console.log('%s %s', req.method, req.url); next(); } function hello(req, res) { res.setHeader('Content-Type','text/plain'); res.end('hello world'); }
中间件的顺序
1) 中间件.use()调用的顺序很重要,以及如何策略性地调整顺序改变程序的工作方式
2) 当一个组件不调用next()时,命令链中的后续中间件都不会被调用
3) 使用中间件执行认证,只允许有效的用户访问挂载中间件和服务器
挂载将只对路径前缀(挂载点)内的请求调用中间件或程序,通过拦截请求进行处理- basic认证
- Basic认证是一种简单的认证机制,借助带着Base64编码认证信息的HTTP请求头中的authorization字段进行认证
- 中间件组件解码认证信息,检查用户名和密码的正确性
function restrict(req, res, next) { var authorization = req.headers.authorization; if(!authorization) return next(new Error('Unauthorized')); var parts = authorization.split(' '); var scheme = parts[0]; var auth = new Buffer(parts[1], 'base64').toString().split(':'); var user = auth[0]; var pass = auth[1]; authenticateWithDatabase(user, pass, function(err) { if(err) return next(err); next(); }); }
注意:用Error做参数调用next 注意前面例子中用Error对象做参数的next函数调用。这相当于通知Connect程序中出现了错误,也就是对于这个HTTP请求而言,后续执行的中间件只有错误处理中间件
使用挂载后的程序
var connect = require('connect'); var app = connect(); app.use(logger); app.use('/admin', restrict); app.use('/admin', admin); app.use(hello); app.listen(3000, function() { console.log('listening...'); }); function logger(req, res, next) { console.log('%s %s', req.method, req.url); next(); } function hello(req, res) { res.setHeader('Content-Type','text/plain'); res.end('hello world'); } function restrict(req, res, next) { var authorization = req.headers.authorization; if(!authorization) return next(new Error('Unauthorized')); var parts = authorization.split(' '); var scheme = parts[0]; //解密得到string var auth = new Buffer(parts[1], 'base64').toString().split(':'); var user = auth[0]; var pass = auth[1]; //进行验证 authenticateWithDatabase(user, pass, function(err) { if(err) return next(err); next(); }); } //模拟认证操作 function authenticateWithDatabase(user, pass, callback) { if (user == 'fafa' && pass == 'fafa') { console.log('ok'); callback(null); } else { //出错了,回调处理函数 callback(new Error('unauthorized user')); } } function admin(req, res, next) { switch (req.url) { case '/': res.end('try /users'); break; case '/users': res.setHeader('Content-Type','application/json'); res.end(JSON.stringify(['fafa','papa','nana'])); break; default: // statements_def break; } }
创建可配置中间件
- 更通用的、可重用的中间件
- 中间件通常会遵循一个简单的惯例:用函数返回另一个函数(这是一个强大的JavaScript特性,通常称为闭包)
- 向其中传入额外的参数来改变它的行为
作为一个中间件模块
function setup(format) { //正则匹配请求的属性 var regexp = /:(\w+)/g; return function logger(req, res,next) { var str = format.replace(regexp, function(match, property){ return req[property]; }); console.log("query:" + str); next(); } } module.exports = setup;
在主文件中引入时传入参数,实现重用
var connect = require('connect'); var app = connect(); var logger = require('./midware/logger'); app.use(logger(':method:url')); app.use('/admin', restrict); app.use('/admin', admin); app.use(hello); app.listen(3000, function() { console.log('listening...'); });
构建路由中间件
- 把请求URL映射到实现业务逻辑的函数上
- HTTP谓词和路径被表示为一个简单的对象和一些回调函数
var parse = require('url').parse; module.exports = function route(obj) { return function(req, res, next) { if(!obj[req.method]) { //req.method未定义 next(); return; } var routes = obj[req.method]; var url = parse(req.url); //将路径放入数组中,准备开始遍历数组 var paths = Obeject.keys(routes); for(var i = 0; i < paths.length; i++) { var path = paths[i]; var fn = routes[path]; path = path.replace(/\//g, '\\/') .replace(/:(\w+)/g, '([^\\/]+)'); var re = new RegExp('^' + path + '$'); var captures = url.pathname.match(re); if (captures) { var args = [req, res].concat(captures.slice(1)); //有函数匹配到,返回,防止后续的Next() fn.apply(null, args); return; } } next(); } };
在app.js中引入,并在app.js中指定不同路径的处理函数
var connect = require('connect'); var app = connect(); var logger = require('./midware/logger'); var router = require('./midware/router'); var routers = { GET:{ '/users':function(req, res) { res.end('fafa,papa,nana'); }, 'user/:id':function(req, res, id) { res.end('user' + id); } }, DELETE:{ 'user/:id':function(req, res, id) { res.end('delete user' + id); } } }; app.use(logger(':method:url')); app.user(router(routers)) app.use('/admin', restrict); app.use('/admin', admin); app.use(hello); app.listen(3000, function() { console.log('listening...'); });
使用错误处理中间件
- Connect的默认错误处理器
- 默认情况下,Connect给出的响应是状态码500,包含文本“Internal Server Error”以及错误自身详细信息的响应主体。这很好,但在任何实际的程序中,你很可能都会对那些错误做些特殊的处理,比如将它们发送给一个日志守护进程
- 自行处理程序错误
- 在Connect中,你还可以用错误处理中间件自行处理程序错误。比如说,在开发时你可能想用JSON格式把错误发送到客户端,做简单快捷的错误报告,而在生产环境中,你可能只想响应一个简单的“服务器错误”,以免把敏感的内部信息(比如堆栈跟踪,文件名和行号等)暴露给潜在的攻击者
- 错误处理中间件函数必须接受四个参数:err、req、res和next,如下面的代码清单所示,而常规的中间件只有三个参数:req、res和next。
function errorHandler() { var env = process.env.NODE_ENV || 'development'; return function(err, req, res, next) { res.statusCode = 500; //根据不同环境执行不同操作 switch (env) { case 'development': res.setHeader('contentType','application/json'); res.end(JSON.stringify(err)); break; default: res.end('Server error'); break; } } }
用NODE_ENV设定程序的模式Connect通常是用环境变量NODE_ENV (process. env.NODE_ENV)在不同的服务器环境之间切换,比如生产和开发环
比如在前面那个管理程序中,如果给用户路由的路由中间件组件出现了错误,blog和admin中间件组件都会被跳过去,因为从它们的表现来看都不是错误处理中间件,只定义了三个参数。然后Connect看到接受错误参数的errorHandler,就会调用它
connect()
.use(router(require('./router/user')))
.use(router(require('./router/blog')))
.use(router(require('./router/admin')))
.use(errorHandler());