connect和express出自同一作者TJ Holowaychuk。从依赖性看,express基于connect;但从2个项目的git提交历史来看,实际上先有express项目(2009-6-27),2010-5-27 前后connect从express项目分化出来(express 0.12.0)。所以两者有相同的中间件机制,不同的是express拥有子路由、view模板等web应用框架内容。

Connect Usage

  1. var connect = require('connect');
  2. var app = connect();
  3. // gzip/deflate outgoing responses
  4. var compression = require('compression');
  5. app.use(compression());
  6. // store session state in browser cookie
  7. var cookieSession = require('cookie-session');
  8. app.use(cookieSession({
  9. keys: ['secret1', 'secret2']
  10. }));
  11. // parse urlencoded request bodies into req.body
  12. var bodyParser = require('body-parser');
  13. app.use(bodyParser.urlencoded({extended: false}));
  14. // respond to all requests
  15. app.use(function(req, res){
  16. res.end('Hello from Connect!\n');
  17. });
  18. //create node.js http server and listen on port
  19. app.listen(3000);


源码解析

源码非常简单,尾递归调用。next 方法不断的取出stack中的“中间件”函数进行调用,同时把next 本身传递给“中间件”作为第三个参数,每个中间件约定的固定形式为 (req, res, next) => {}, 这样每个“中间件“函数中只要调用 next 方法即可传递调用下一个中间件。
之所以说是”尾递归“是因为递归函数的最后一条语句是调用函数本身,所以每一个中间件的最后一条语句需要是next()才能形成”尾递归“,否则就是普通递归,”尾递归“相对于普通”递归“的好处在于节省内存空间,不会形成深度嵌套的函数调用栈。尾调用优化(opens new window)
增加中间件:

  1. module.exports = createServer;
  2. function createServer() {
  3. function app(req, res, next){
  4. app.handle(req, res, next); // 执行中间件
  5. }
  6. merge(app, proto);
  7. merge(app, EventEmitter.prototype);
  8. app.route = '/';
  9. app.stack = [];
  10. return app;
  11. }
  12. // 增加中间件
  13. proto.use = function use(route, fn) {
  14. var handle = fn;
  15. var path = route;
  16. // 核心就这一句,往数组中增加一项
  17. this.stack.push({ route: path, handle: handle });
  18. return this;
  19. };

处理中间件:

  1. proto.handle = function handle(req, res, out) {
  2. var index = 0;
  3. var protohost = getProtohost(req.url) || '';
  4. var removed = '';
  5. var slashAdded = false;
  6. var stack = this.stack;
  7. // final function handler
  8. var done = out || finalhandler(req, res, {
  9. env: env,
  10. onerror: logerror
  11. });
  12. // store the original URL
  13. req.originalUrl = req.originalUrl || req.url;
  14. function next(err) {
  15. if (slashAdded) {
  16. req.url = req.url.substr(1);
  17. slashAdded = false;
  18. }
  19. if (removed.length !== 0) {
  20. req.url = protohost + removed + req.url.substr(protohost.length);
  21. removed = '';
  22. }
  23. // 遍历stack,拿到下一个layer
  24. var layer = stack[index++];
  25. // all done
  26. if (!layer) {
  27. defer(done, err);
  28. return;
  29. }
  30. // route data
  31. var path = parseUrl(req).pathname || '/';
  32. var route = layer.route;
  33. // 错误处理
  34. // skip this layer if the route doesn't match
  35. if (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) {
  36. return next(err);
  37. }
  38. // skip if route match does not border "/", ".", or end
  39. var c = path.length > route.length && path[route.length];
  40. if (c && c !== '/' && c !== '.') {
  41. return next(err);
  42. }
  43. // trim off the part of the url that matches the route
  44. if (route.length !== 0 && route !== '/') {
  45. removed = route;
  46. req.url = protohost + req.url.substr(protohost.length + removed.length);
  47. // ensure leading slash
  48. if (!protohost && req.url[0] !== '/') {
  49. req.url = '/' + req.url;
  50. slashAdded = true;
  51. }
  52. }
  53. // 匹配到layer,执行handle函数
  54. call(layer.handle, route, err, req, res, next);
  55. }
  56. next(); // 开始next
  57. };
  58. function call(handle, route, err, req, res, next) {
  59. var arity = handle.length;
  60. var error = err;
  61. var hasError = Boolean(err);
  62. try {
  63. if (hasError && arity === 4) {
  64. handle(err, req, res, next); // 处理错误next
  65. return;
  66. } else if (!hasError && arity < 4) {
  67. // 执行handle函数。
  68. // next函数是其参数,如果要传递下一个,执行next函数
  69. handle(req, res, next);
  70. return;
  71. }
  72. } catch (e) {
  73. error = e;
  74. }
  75. // 继续next
  76. next(error);
  77. }