Wang's blog Wang's blog
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Wang Mings

跟随大神,成为大神!
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Python爬虫

  • 前端教程

    • 团队规范

    • Project

    • JS

    • NodeJS

      • Koa 洋葱模型原理分析
      • Nginx反向代理
      • Connect源码解析
        • [#](#connect-usage) Connect Usage
        • [#](#源码解析) 源码解析
      • Express源码解析
      • Node模块源码分析
      • MongoDB指南
      • 常用Node API
      • 常用Node工具总结
      • Node Debug for VSCode
      • Redis简介
    • Vue

    • React

    • 效率工具

    • 读书笔记

  • 教程
  • 前端教程
  • NodeJS
wangmings
2022-07-19
目录

Connect源码解析

# # Connect源码解析

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

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

# # 源码解析

源码非常简单,尾递归调用。next 方法不断的取出stack中的“中间件”函数进行调用,同时把next 本身传递给“中间件”作为第三个参数,每个中间件约定的固定形式为 (req, res, next) => {}, 这样每个“中间件“函数中只要调用 next 方法即可传递调用下一个中间件。

之所以说是”尾递归“是因为递归函数的最后一条语句是调用函数本身,所以每一个中间件的最后一条语句需要是next()才能形成”尾递归“,否则就是普通递归,”尾递归“相对于普通”递归“的好处在于节省内存空间,不会形成深度嵌套的函数调用栈。尾调用优化 (opens new window) (opens new window)

增加中间件:

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

处理中间件:

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

← Nginx反向代理 Express源码解析→

最近更新
01
theme-vdoing-blog博客静态编译问题
09-16
02
搜索引擎
07-19
03
友情链接
07-19
更多文章>
Theme by Vdoing | Copyright © 2019-2022 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式