express源码学习

打开 Express4 的源码,目录结构很清晰,阅读时建议对照源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
. lib
├── middleware -------------- 中间件
├── init.js ------------ 增强 req 对象和 res 对象
└── query.js ------------ 添加 req.query
├── router ------------------ 路由
├── index.js ------------ Router 构造器
├── layer.js ------------ 中间件和路由的抽象
└── route.js ------------ 实现 router.route
├── application.js ---------- app 对象拓展
├── express.js -------------- 项目入口
├── request.js --------------- req 对象拓展
├── response.js ------------- res 对象拓展
├── utils.js ---------------- 工具集
└── view.js ----------------- 模板引擎

底层原理

Express 框架建立在 node.js 内置的 http 模块上,http 模块生成服务器的原始代码如下。

1
2
3
4
5
6
7
8
var http = require("http");

var app = http.createServer(function(request, response) {
response.writeHead(200, { "Content-Type": "text/plain" });
response.end("Hello world!");
});

app.listen(3000, "localhost");

Express 代理了http.createServer(requestHandler)中的requestHandler,并使用注册后的中间件和路由,匹配响应传来的用户请求。

代码梳理

下面是一个简单的 Express 使用案例。

1
2
3
4
5
6
7
8
9
10
11
var express = require("express");
// first step
var app = express();

// second step
app.get("/", function(req, res) {
res.send("Hello world!");
});

// third step
app.listen(3000);

third step

app.listen 方法在 application.js 中被定义。

1
2
3
4
5
// application.js
app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};

启动一个服务,并指定监听端口,这里的关键是http.createServer(this)中的this(也就是app),即http.createServer(requestHandler)中的requestHandlerthis应该是一个函数(request, response) => {},下面介绍 express 如何改造这个函数。

first step

express 方法就是 express.js 中的 createApplication 方法,该方法返回了一个函数,即requestHandler

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
// express.js
var proto = require("./application");
var req = require("./request");
var res = require("./response");

function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};
mixin(app, EventEmitter.prototype, false);
// 混入 application 中的属性,其中包括 app.handle 、app.init、app.get、app.listen
mixin(app, proto, false);

// 暴露增强后的 req 对象,会在 middleware/init.js 中被用到
app.request = Object.create(req, {
app: { configurable: true, enumerable: true, writable: true, value: app }
});

// 暴露增强后的 res 对象,会在 middleware/init.js 中被用到
app.response = Object.create(res, {
app: { configurable: true, enumerable: true, writable: true, value: app }
});
// app.init 内部调用 app.defaultConfiguration ,初始化配置项
app.init();
return app;
}
1
2
3
4
5
6
7
8
// application.js
app.init = function init() {
this.cache = {}; // 存储缓存的对象
this.engines = {}; // 存储模板引擎中扩展名和渲染函数的对应关系
this.settings = {}; // 存储 app.set 和 app.get 处理的对象
// 设置默认的配置项
this.defaultConfiguration();
};

second step

app.get 方法在 application.js 中被定义。

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
// application.js
var methods = require("methods");
var Router = require("./router");
var middleware = require("./middleware/init");
var query = require("./middleware/query");

methods.forEach(function(method) {
app[method] = function(path) {
if (method === "get" && arguments.length === 1) {
// app.get(setting)
return this.set(path);
}

this.lazyrouter();

var route = this._router.route(path);
route[method].apply(route, slice.call(arguments, 1));
return this;
};
});

// lazily adds the base router if it has not yet been added.
app.lazyrouter = function lazyrouter() {
if (!this._router) {
this._router = new Router({
caseSensitive: this.enabled("case sensitive routing"),
strict: this.enabled("strict routing")
});

this._router.use(query(this.get("query parser fn")));
this._router.use(middleware.init(this));
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// router/index.js
var Layer = require("./layer");

// app.use 的实质
proto.use = function use(fn) {
// ... 省略部分代码
for (var i = 0; i < callbacks.length; i++) {
var fn = callbacks[i];
var layer = new Layer(
path,
{
sensitive: this.caseSensitive,
strict: false,
end: false
},
fn
);
layer.route = undefined;
// 将 layer 对象推入 stack 中管理
this.stack.push(layer);
}

return this;
};

一个 Layer 对象包含一个路径和回调,它会把路径正则表达式化,在响应请求时用来匹配路径。在app.lazyrouter方法里添加了一个中间件middleware.init(this),重新设置了 req 和 res 的原型,增加了许多方法,详见request.jsresponse.js

1
2
// request.js
var req = Object.create(http.IncomingMessage.prototype);
1
2
// response.js
var res = Object.create(http.ServerResponse.prototype);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// middleware/init.js
exports.init = function(app) {
return function expressInit(req, res, next) {
if (app.enabled("x-powered-by")) res.setHeader("X-Powered-By", "Express");
req.res = res;
res.req = req;
req.next = next;

setPrototypeOf(req, app.request);
setPrototypeOf(res, app.response);

res.locals = res.locals || Object.create(null);

next();
};
};

响应请求阶段

app.handle 实质上是调用了自身 router 的 handle,router.handle 遍历 router 维护的 stack 数组,找到匹配路径的 layer 对象。对于中间件 layer(layer.routeundefined),匹配成功后就执行中间件函数;对于路由 layer(layer.route 不是 undefined),匹配成功后还需要匹配 http method 才能执行路由函数。

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
// router/index.js
proto.handle = function handle(req, res, out) {
var self = this;

var idx = 0;
var protohost = getProtohost(req.url) || "";
var removed = "";
var slashAdded = false;
var paramcalled = {};

// store options for OPTIONS request
// only used if OPTIONS request
var options = [];

// middleware and routes
var stack = self.stack;

// manage inter-router variables
var parentParams = req.params;
var parentUrl = req.baseUrl || "";
var done = restore(out, req, "baseUrl", "next", "params");

// setup next layer
req.next = next;

// for options requests, respond with a default if nothing else responds
if (req.method === "OPTIONS") {
done = wrap(done, function(old, err) {
if (err || options.length === 0) return old(err);
sendOptionsResponse(res, options, old);
});
}

// setup basic req values
req.baseUrl = parentUrl;
req.originalUrl = req.originalUrl || req.url;

next();

function next(err) {
// ... 在下面介绍
}
};

next 函数内部有个 while 循环,每次循环都会从 stack 中拿出一个 layer,这个 layer 中包含了路由和中间件信息,然后就会用 layer 和请求的 path 进行匹配,如果匹配成功就会执行 layer.handle_request,调用中间件函数。但如果匹配失败,就会循环下一个 layer 对象。

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
// router/index.js
// ... 省略部分代码
function next(err) {
// 拿到当前的访问路径
var path = getPathname(req);

// find next matching layer
var layer;
var match;
var route;

while (match !== true && idx < stack.length) {
layer = stack[idx++];

// 进行路径匹配,匹配返回 true,不匹配返回 false
match = matchLayer(layer, path);
route = layer.route;

if (match !== true) {
continue;
}

if (!route) {
// 正常处理非路由中间件
continue;
}

var method = req.method;
var has_method = route._handles_method(method);

// build up automatic options response
if (!has_method && method === "OPTIONS") {
appendMethods(options, route._options());
}

// don't even bother matching route
if (!has_method && method !== "HEAD") {
match = false;
continue;
}
}
// ... 省略部分代码
// this should be done for the layer
if (err) {
layer.handle_error(err, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}

路由中间件

当请求来时,会执行 handle,循环 stack,发现 path 相同且 method 相同,则调用对应 callback。当路由不断增多,stack 数组会不断增大,匹配效率必然下降,为了提高效率,express 引入了 route ,也就是路由中间件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 一个案例
var route = app
.route("/list")
.get((req, res) => {
res.end("hello get");
})
.post((req, res) => {
res.end("hello post");
})
.put((req, res) => {
res.end("hello put");
})
.delete((req, res) => {
res.end("hello delete");
});
app.use("/", routers);

app.routeapplication.js中被定义,内部调用了 router 的 route 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// router/index.js
var Route = require("./route");

proto.route = function route(path) {
var route = new Route(path);
// 把 route 里面的 dispatch 方法作为 Layer 的处理函数
var layer = new Layer(
path,
{
sensitive: this.caseSensitive,
strict: this.strict,
end: true
},
route.dispatch.bind(route)
);

layer.route = route;

this.stack.push(layer);
// 返回route实例,用于链式调用,注册 method 方法
return route;
};

当 layer 的路径匹配成功,就会交给 route 来匹配 method,route 存放了所有注册的方法名,可以快速匹配是否有注册该方法,如果有则执行回调函数。

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
// router/route.js
Route.prototype.dispatch = function dispatch(req, res, done) {
var idx = 0;
var stack = this.stack;
if (stack.length === 0) {
return done();
}

var method = req.method.toLowerCase();
if (method === "head" && !this.methods["head"]) {
method = "get";
}

req.route = this;

next();

function next(err) {
// 处理退出 route 信号
if (err && err === "route") {
return done();
}

// 处理退出 router 信号
if (err && err === "router") {
return done(err);
}

// 通过递归调用 next,遍历 stack
var layer = stack[idx++];
if (!layer) {
return done(err);
}

if (layer.method && layer.method !== method) {
return next(err);
}

if (err) {
layer.handle_error(err, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}
};

next(err)将控制权传递到错误处理中间件,当调用next(err)时,实质是调用layer.handle_error,如果 fn 的参数不足 4 个,认为不是一个标准的错误处理中间件,则继续调用next(err),直到参数达到 4 个,执行错误处理中间件。

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
// router/layer.js
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle; // 回调函数

if (fn.length > 3) {
// 形参数量大于 3,则认为不是一个标准的 requestHandler,调用 next 去查找下一个 layer
return next();
}

try {
// 执行标准的 requestHandler
fn(req, res, next);
} catch (err) {
next(err);
}
};

Layer.prototype.handle_error = function handle_error(error, req, res, next) {
var fn = this.handle; // 中间件函数

if (fn.length !== 4) {
return next(error);
}

try {
fn(error, req, res, next);
} catch (err) {
next(err);
}
};

模板渲染

下面是使用模板引擎的写法。

1
2
3
4
5
6
7
// 指定模板文件所在目录
app.set("views", "./views");
// 指定要使用的模板引擎
app.set("view engine", "pug");
app.get("/", function(req, res) {
res.render("home", { title: "Hey", message: "Hello there!" });
});

res.render实际调用的是app.render,内部创建 View 实例,并调用view.render(options, callback)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// response.js
res.render = function render(view, options, callback) {
var app = this.req.app;
var opts = options || {};
var done = callback;
// ...省略部分代码
// default callback to respond
done =
done ||
function(err, str) {
if (err) return req.next(err);
self.send(str);
};

// render
app.render(view, opts, done);
};
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
// application.js
app.render = function render(name, options, callback) {
var cache = this.cache;
var done = callback;
var engines = this.engines;
var opts = options;
var renderOptions = {};
var view;
// ...省略部分代码
if (!view) {
var View = this.get("view");

view = new View(name, {
defaultEngine: this.get("view engine"),
root: this.get("views"),
engines: engines
});

// prime the cache
if (renderOptions.cache) {
cache[name] = view;
}
}

// render
tryRender(view, renderOptions, done);
};

function tryRender(view, options, callback) {
try {
view.render(options, callback);
} catch (err) {
callback(err);
}
}
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
// view.js
function View(name, options) {
// ...省略部分代码
var opts = options || {};
this.ext = extname(name);
// 如果对应后缀的模板引擎没有被注册
if (!opts.engines[this.ext]) {
var mod = this.ext.substr(1);
// 使用 require() 去加载没有被注册的模板引擎
var fn = require(mod).__express;
if (typeof fn !== "function") {
throw new Error('Module "' + mod + '" does not provide a view engine.');
}
// 注册模板引擎
opts.engines[this.ext] = fn;
}
// 保存需要的模板引擎
this.engine = opts.engines[this.ext];
// 拿到文件的绝对路径
this.path = this.lookup(fileName);
}
View.prototype.render = function render(options, callback) {
// 调用模板引擎的函数,调用完就执行 callback
this.engine(this.path, options, callback);
};