深入理解Node系列-细说Connect(上)

Posted 你不知道的sharlly

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解Node系列-细说Connect(上)相关的知识,希望对你有一定的参考价值。

前言

想必对于广大前后端的同学们,Node 或是用来作为网站服务器的搭建,亦或是用来作为开发脚手架的运用,或是早有套路,亦或是浅尝辄止。从现在开始博主将会不定时的对 Node 系列的产品做分析,其中夹杂着常见的基础模块,三方模块,丰富大家的 Node 技术栈。

很多童鞋上手项目时,通常会将 Express 作为 Node 端框架,而本文主要对其底层构件 Connect 做一个分析。

connect

Connect 是一个可扩展(中间件作为插件)的 Http 服务器框架,Connect 刚出道之时自带了许多中间件,为保证其框架的轻量级以及扩展性,最终还是将这些中间件的实现抛给了社区。可能在搜索 Connect 的相关项目时,你会发现 connect().use(connect.bodyParser())这些的写法,这对于现在的 Connect (最新版本3.6.0) 是不支持的,而只能通过 npm 下载第三方的模块 (如 body-parser) 替代原先的中间价。

1. 基本使用
Connect 提供的 API 不多,并且非常容易理解。
listen
Connect 引入了 http 原生模块,因此 listen 也是用来监听端口的。

var connect = require('connect');
var PORT = 3000;
connect()
    .use(function(req, res) 
        res.end('listen port is ' + PORT);
    )
    .listen(PORT);
// 访问localhost:3000

use
在 req/res 中有许多内容需要通过中间件处理才能方便取到,而 use 正好为中间件提供了一个入口。

var connect = require('connect');
var cookieParser = require('cookie-parser');

connect()
    .use(function(req, res, next) 
        console.log('未使用cookie-parser', req.cookies);
        next();
    )
    .use(cookieParser())
    .use(function(req, res) 
        console.log('使用cookie-parser', req.cookies);
        res.end('.');
    )
    .listen(3000);

执行 curl http://localhost:3000 -H "Cookie: name=sharlly" 会得到
未使用cookie-parser undefined
使用cookie-parser name: 'sharlly'

你可能会关注到 cookieParser 上面的函数比下面的多了一个 next 参数,这是 Connect 对中间件设定,只用调用了 next() 才会继续下一个中间件的执行,最后一个中间件则不需要使用。

挂载url
如果你想针对某个访问路径做出不同的响应(即挂载,如设置用户访问权限),则同样可以使用 use() ,不过写法有所改变,如我希望访问 /home/… 和 /articles/… 并得到不同的内容。

var connect = require('connect');

connect()
    .use('/home', function(req, res) 
        res.end('home');
    )
    .use('/articles', function(req, res) 
        res.end('articles');
    )
    .use(function(req, res) 
        res.end('others');
    )
    .listen(3000)

2. 源码剖析
connect 的源码非常简短,可简单整理如下图:

use函数

proto.use = function use(route, fn) 
  var handle = fn;
  var path = route;

  // default route to '/'
  if (typeof route !== 'string') 
    handle = route;
    path = '/';
  

  // wrap sub-apps
  if (typeof handle.handle === 'function') 
    var server = handle;
    server.route = path;
    handle = function (req, res, next) 
      server.handle(req, res, next);
    ;
  

  // wrap vanilla http.Servers
  if (handle instanceof http.Server) 
    handle = handle.listeners('request')[0];
  

  // strip trailing slash
  if (path[path.length - 1] === '/') 
    path = path.slice(0, -1);
  

  // add the middleware
  debug('use %s %s', path || '/', handle.name || 'anonymous');
  this.stack.push( route: path, handle: handle );

  return this;
;

非常好理解,use() 将 route 和 function 一一对应并保存到 this.stack 当中,若只有一个参数,则默认 route = ‘/’ 。那保存下来的函数在哪里执行呢?再来看看 handle 函数。

handle函数

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) 
    // omit...
    var layer = stack[index++];
    // call the layer handle
    call(layer.handle, route, err, req, res, next);
  

  next();
;

当有请求进入到 Node 后,http 模块会触发 request 事件,此时会执行一次 handle(req, res, next) 。而在 handle 函数中可以看到回调方法 next(),通过 stack[index++] 将下一个 layer.handle 传递给 call() 执行,call() 带了几个参数,分别是 use中自定义的方法请求路径错误对象请求对象响应对象handle()中的next()

而 call 方法也是非常简单

function call(handle, route, err, req, res, next) 
  var arity = handle.length;
  var error = err;
  var hasError = Boolean(err);

  debug('%s %s : %s', handle.name || '<anonymous>', route, req.originalUrl);

  try 
    if (hasError && arity === 4) 
      // error-handling middleware
      handle(err, req, res, next);
      return;
     else if (!hasError && arity < 4) 
      // request-handling middleware
      handle(req, res, next);
      return;
    
   catch (e) 
    // replace the error
    error = e;
  

  // continue
  next(error);

call 将 next 传给 use 自定义的函数上,这样 在自定义函数中调用 next 就可以调用下一个中间件了,直到最后一个中间件执行完后,之前的中间件 next 后面的代码才会按作用域依次执行。

整个过程总结如下:

结语

通过分析 Connect 源码,对其中间件运行机制也有了一定的掌握。下篇,将会对 Connect 常用中间件以及第三方模块进行介绍。

以上是关于深入理解Node系列-细说Connect(上)的主要内容,如果未能解决你的问题,请参考以下文章

深入理解Node系列-细说Connect(上)

深入理解Node系列-细说Connect(下)

深入理解Node系列-细说Connect(下)

. 细说Kalman滤波:The Kalman Filter

深入理解JavaScript系列(24):JavaScript与DOM(下)

深入理解TCP协议及其源代码——connect及bindlistenaccept背后的“三次握手”