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