express-13 中间件

简介

  • 从概念上讲,中间件是一种功能的封装方式,具体来说就是封装在程序中处理HTTP请求的功能。

  • 中间件是在管道中执行的,在Express程序中,通过调用app.use向管道中插入中间件。(在Express 4.0中,中间件和路由处理器是按它们的连入顺序调用的)

  • 在管道的最后放一个“捕获一切”请求的处理器是常见的做法,由它来处理跟前面其他所有路由都不匹配的请求。这个中间件一般会返回状态码404(未找到)。

  • 如果不调用next(),请求就在那个中间件中终止了。

中间件和路由处理器

  • 路由处理器(app.get、app.post等,经常被统称为app.VERB)可以被看作只处理特定HTTP谓词(GET、POST等)的中间件; 也可以将中间件看作可以处理全部HTTP谓词的路由处理器(基本上等同于app.all,可以处理任何HTTP谓词;

  • 路由处理器的第一个参数必须是路径;中间件也可以将路径作为第一个参数,但它是可选的(如果忽略这个参数,它会匹配所有路径)。

  • 路由处理器和中间件的参数中都有回调函数,这个函数有2个、3个或4个参数;

    • 如果有2个或3个参数,头两个参数是请求和响应对象,第三个参数是next函数。
    • 如果有4个参数,它就变成了错误处理中间件,第一个参数变成了错误对象,然后依次是请求、响应和next对象。
  • 如果不调用next(),管道就会被终止,也不会再有处理器或中间件做后续处理。

    • 如果不调用next(),则应该发送一个响应到客户端(res.send、res.json、res.render等);
    • 如果不这样做,客户端会被挂起并最终导致超时。
  • 如果调用了next(),一般不宜再发送响应到客户端。

    • 如果发送了,管道中后续的中间件或路由处理器还会执行,但它们发送的任何响应都会被忽略。
app.use(function(req, res, next){
  console.log('processing request for "' + req.url + '"....'); 
  next();
});

app.use(function(req, res, next){
  console.log('terminating request');
  res.send('thanks for playing!');
  // 注意,我们没有调用next()......这样请求处理就终止了;
  //如果忽略了`res.send`,则不会有响应返回到客户端,最终会导致客户端超时。
});

//最后一个中间件永远也不会执行
app.use(function(req, res, next){
    console.log('whoops, i\'ll never get called!');
});
var app = require('express')();

app.use(function(req, res, next){
  console.log('\n\nALLWAYS');
  next();
});

app.get('/a', function(req, res){ 
  console.log('/a: 路由终止'); 
  res.send('a');
});
app.get('/a', function(req, res){
  console.log('/a: 永远不会调用');
});
app.get('/b', function(req, res, next){
  console.log('/b: 路由未终止');
  next(); 
});
app.use(function(req, res, next){ 
  console.log('SOMETIMES');
  next(); 
});
app.get('/b', function(req, res, next){ 
  console.log('/b (part 2): 抛出错误' ); 
  throw new Error('b 失败');
});

app.use('/b', function(err, req, res, next){ 
  console.log('/b 检测到错误并传递'); 
  next(err);
});
app.get('/c', function(err, req){
  console.log('/c: 抛出错误'); 
  throw new Error('c 失败');
});
app.use('/c', function(err, req, res, next){
  console.log('/c: 检测到错误但不传递');
  next(); 
});

app.use(function(err, req, res, next){
  console.log('检测到未处理的错误: ' + err.message);
  res.send('500 - 服务器错误');
});

app.use(function(req, res){ 
  console.log('未处理的路由'); 
  res.send('404 - 未找到');
});

app.listen(3000, function(){
  console.log('监听端口3000');
});

要特别注意请求/b和请求/c的差异,在这两个实例中都有一个错误,但一个结果是404,另一个是500。

  • 中间件必须是一个函数

  • 模块可以输出一个函数,而这个函数又可以直接用作中间件。

//lib/tourRequiresWaiver.js模块
module.exports = function(req,res,next){ 
  var cart = req.session.cart;
  if(!cart) return next();
  if(cart.some(function(item){ return item.product.requiresWaiver; })){
    if(!cart.warnings) cart.warnings = [];
    cart.warnings.push('One or more of your selected tours' +
            'requires a waiver.');
    }
    next();
}

//引入这个中间件:
app.use(require('./lib/requiresWaiver.js'));

//不过更常见的做法是输出一个以中间件为属性的对象
module.exports = {
  checkWaivers: function(req, res, next){
    var cart = req.session.cart;
    if(!cart) return next();
    if(cart.some(function(i){ return i.product.requiresWaiver; })){
      if(!cart.warnings) cart.warnings = []; 
      cart.warnings.push('One or more of your selected ' +
              'tours requires a waiver.');
    }
    next();
  },

  checkGuestCounts: function(req, res, next){ 
    var cart = req.session.cart;
    if(!cart) return next(); 
    if(cart.some(function(item){ return item.guests >
          item.product.maximumGuests; })){ 
      if(!cart.errors) cart.errors = [];
      cart.errors.push('One or more of your selected tours ' +
          'cannot accommodate the number of guests you ' +
              'have selected.');
    }
    next(); 
  }
}

//像以下这样连入中间件

var cartValidation = require('./lib/cartValidation.js');

app.use(cartValidation.checkWaivers);
app.use(cartValidation.checkGuestCounts);

在前面的例子中,我们的中间件会用语句return next()提前终止。Express不期望中间件返回值(并且它不会用返回值做任何事情),所以这只是缩短了的next(); return;。

常用中间件

Express 4.0之后,唯一保留在Express中的中间件只剩下static

  • basicAuth: 提供基本的访问授权。basic-auth只提供最基本的安全,并且只能通过HTTPS使用basic-auth(否则用户名和密码是通过明文传输的)。只有在需要又快又容易的东西,并且在使用HTTPS时,才应该用basic-auth。

  • body-parser: 只连入json和urlencoded的便利中间件。

  • json: 解析JSON编码的请求体。

  • urlencoded: 解析互联网媒体类型为application/x-www-form-urlencoded的请求体。这是处理表单和AJAX请求最常用的方式;

  • multipart(已废弃): 解析互联网媒体类型为multipart/form-data的请求体; 应该用Busboy或Formidable代替它。

  • compress: 用gzip压缩响应数据。特别使用在那些网络比较慢或者用手机上网的情况; 它应该在任何可能会发送响应的中间件之前被尽早连入。唯一应该出现在compress之前的中间件只有debugging或logging(它们不发送响应)。

  • cookie-parser: 提供对cookie的支持; app.use(require(cookie-parser)(秘钥放在这里);

  • cookie-session: 提供cookie存储的会话支持。一般不推荐使用这种存储方式的会话;一定要把它放在cookie-parser后面连入。

  • express-session: 提供会话ID (存在cookie里)的会话支持。默认存在内存里,不适用于生产环境,并且可以配置为使用数据库存储。

  • csurf: 防范跨域请求伪造(CSRF)攻击。因为它要使用会话,所以必须放在express-session中间件后面。它目前等同于connect.csrf中间件。可惜简单连入这个中间件并不能神奇地防范CSRF攻击;

  • directory: 提供静态文件的目录清单支持。如果不需要目录清单,则无需引入这个中间件。

  • errorhandler: 为客户端提供栈追踪和错误消息;建议不要在生产环境中连入它,因为它会暴露实现细节,可能引发安全或隐私问题。

  • static-favicon: 提供favicon(出现在浏览器标题栏上的图标)。这个中间件不是必需的,可以简单地在static目录下放一个favicon.ico,但这个中间件能提升性能。如果要使用它,应该尽可能地往中间件栈的上面放。

  • morgan: 提供自动日志记录支持:所有请求都会被记录。

  • method-override: 提供对x-http-method-override请求头的支持,允许浏览器“假装”使用除GETPOST之外的HTTP方法。这对调试有帮助。只在编写API时才需要。

  • query: 解析查询字符串,并将其变成请求对象上的query属性。这个中间件是由Express隐含连入的,所以不要自己连入它。

  • response-time: 向响应中添加X-Response-Time头,提供以毫秒为单位的响应时间。一般在做性能调优时才需要这个中间件。

  • static: 提供对静态(public)文件的支持; 这个中间件可以连入多次,并可指定不同的目录。

  • vhost: 虚拟主机(vhost),这个术语是从Apache借来的,它可使子域名在Express中更容易管理。

posted @ 2015-03-19 13:48  JinksPeng  阅读(800)  评论(0编辑  收藏  举报