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 responsesvar compression = require('compression');app.use(compression());// store session state in browser cookievar cookieSession = require('cookie-session');app.use(cookieSession({keys: ['secret1', 'secret2']}));// parse urlencoded request bodies into req.bodyvar bodyParser = require('body-parser');app.use(bodyParser.urlencoded({extended: false}));// respond to all requestsapp.use(function(req, res){res.end('Hello from Connect!\n');});//create node.js http server and listen on portapp.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 handlervar done = out || finalhandler(req, res, {env: env,onerror: logerror});// store the original URLreq.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,拿到下一个layervar layer = stack[index++];// all doneif (!layer) {defer(done, err);return;}// route datavar path = parseUrl(req).pathname || '/';var route = layer.route;// 错误处理// skip this layer if the route doesn't matchif (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) {return next(err);}// skip if route match does not border "/", ".", or endvar 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 routeif (route.length !== 0 && route !== '/') {removed = route;req.url = protohost + req.url.substr(protohost.length + removed.length);// ensure leading slashif (!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); // 处理错误nextreturn;} else if (!hasError && arity < 4) {// 执行handle函数。// next函数是其参数,如果要传递下一个,执行next函数handle(req, res, next);return;}} catch (e) {error = e;}// 继续nextnext(error);}
