用了这么久的express框架, 对它的原理还是不太清楚,惭愧惭愧, 赶紧学习一下。

express的主要功能有:

  • 中间件的使用
  • 路由
  • 模板引擎
  • 静态文件服务
  • 设置代理

这篇文章主要涉及到express中间件的实现原理, 这也是面试的重头戏啦。

先看一个最简单的express例子:

1
2
3
4
5
6
7
8
9
var express = require('express');
var app = express();
app.listen(3000, function () {
    console.log('listen 3000...');
});

app.use(middlewareA);
app.use(middlewareB);
app.use(middlewareC);

app.use()就是通常所说的使用中间件

什么是中间件?

一个请求发送到服务器后,它的生命周期是 先收到request(请求),然后服务端处理,处理完了以后发送response(响应)回去

而这个服务端处理的过程就有文章可做了,想象一下当业务逻辑复杂的时候,为了明确和便于维护,需要把处理的事情分一下,分配成几个部分来做,而每个部分就是一个中间件

app.use 加载用于处理http请求的middleware(中间件),当一个请求来的时候,会依次被这些 middlewares处理。

中间件执行的顺序是你定义的顺序.

中间件源码

 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
//connect.js 的简要内容

function createServer(){
    
    // app是用于http.createServer的回调函数
    function app(req, res, next){
    
        // 运行时调用handle函数
        app.handle(req, res, next);
    }

    mixin(app, proto, false);
    
    // 初始化一个stack数组
    app.stack = []; 
    return app;
}

// use调用时往app的stack数组中push一个对象(中间件),标识path与回调函数
proto.use = function(route, fn){
    var path = route, 
    handle = fn;

    //...  省略其他
    
    this.stack.push({
        route: path,
        handle
    });
};

// handle方法,串行取出stack数组中的中间件,逐个运行
proto.handle = function(req, res, out){
    var index = 0;
    var stack = this.stack;
    var done = out || finalhandler(req, res, { onerror: logerror });

    // 遍历stack,逐个取出中间件运行
    function next(err){
        var layer = stack[index++];
        // 遍历完成为止
        if(layer === undefined){
            return done();
        }

        var route = pathFormat(layer.route);
        var pathname = pathFormat(urlParser(req.url).pathname || '/');

        // 匹配中间件,不匹配的不运行
        if(route !== '' && pathname !== route){
            next(err);
            return;
        }

        // 调用中间件
        call(layer.handle, err, req, res, next);
    }

    next();
};

不难看出,app.use中间件时,只是把它放入一个数组中。

当http请求时,app会从数组中逐个取出,进行匹配过滤,逐个运行。遍历完成后,运行finalhandler,结束一个http请求。

可以从http请求的角度思考,一次请求它经历经历了多少东西。

express的这个中间件架构就是负责管理与调用这些注册的中间件。

中间件顺序执行,通过next来继续下一个,一旦没有继续next,则流程结束。

总结

总的来说, express内部维护一个函数数组,这个函数数组表示在发出响应之前要执行的所有函数,也就是中间件数组。

使用app.use(fn)后,传进来的fn就会被扔到这个数组里,执行完毕后调用next()方法执行函数数组里的下一个函数, 如果没有调用next()的话,就不会调用下一个函数了,也就是说调用就会被终止。

并没有想象中的复杂吧。

参考资料

  1. NodeJS express框架核心原理全揭秘
  2. Express中间件的原理及实现