前言

想必对于广大前后端的同学们,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 后面的代码才会按作用域依次执行。这就是我们所谓的 thunk 函数,它将异步操作包装成 thunk 函数,在回调函数里面交回执行权。

整个过程总结如下:

结语

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

 

深入理解Node系列-细说Connect(上)的更多相关文章

  1. 深入理解JavaScript系列:史上最清晰的JavaScript的原型讲解

    一说起JavaScript就要谈的几个问题,原型就是其中的一个.说了句大话,史上最清晰.本来是想按照大纲式的行文写一下,但写到后边感觉其实就一个概念,没有什么条理性,所以下面就简单按照概念解释的模式谈 ...

  2. 深入理解JavaScript系列(49):Function模式(上篇)

    介绍 本篇主要是介绍Function方面使用的一些技巧(上篇),利用Function特性可以编写出很多非常有意思的代码,本篇主要包括:回调模式.配置对象.返回函数.分布程序.柯里化(Currying) ...

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

    介绍 上一章我们介绍了JavaScript的基本内容和DOM对象的各个方面,包括如何访问node节点.本章我们将讲解如何通过DOM操作元素并且讨论浏览器事件模型. 本文参考:http://net.tu ...

  4. vue 快速入门 系列 —— vue loader 上

    其他章节请看: vue 快速入门 系列 vue loader 上 通过前面"webpack 系列"的学习,我们知道如何用 webpack 实现一个不成熟的脚手架,比如提供开发环境和 ...

  5. 深入理解javascript系列(4):立即调用的函数表达式

    本文来自汤姆大叔 前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行. 在详细了解这个之前,我们来谈了解一下“自执行”这个叫法,本文对这个功能的叫法 ...

  6. 深入理解javascript系列,读书笔记

    深入理解JavaScript系列(2):揭秘命名函数表达式 1.讲了函数声明和函数表达式的区别,包括一些在函数提升上的区别 2.如果给函数表达式的函数也取名,会在调试的时候受益 3.不要在block( ...

  7. 深入理解JavaScript系列

    转自http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html 深入理解JavaScript系列(1):编写高质量JavaScript代码 ...

  8. 深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点

    深入理解JavaScript系列(1):编写高质量JavaScript代码的基本要点 2011-12-28 23:00 by 汤姆大叔, 139489 阅读, 119 评论, 收藏, 编辑 才华横溢的 ...

  9. hdu---(4515)小Q系列故事——世界上最遥远的距离(模拟题)

    小Q系列故事——世界上最遥远的距离 Time Limit: 500/200 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)T ...

随机推荐

  1. php 禁用eval( )函数

    php的eval函数并不是系统组件函数,因此我们在php.ini中使用disable_functions是无法禁止它的. 但是eval()对于php安全来说具有很大的杀伤力,因此一般不用的情况下为了防 ...

  2. Spring mvc系列一之 Spring mvc简单配置

    Spring mvc系列一之 Spring mvc简单配置-引用 Spring MVC做为SpringFrameWork的后续产品,Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块 ...

  3. Xcode 添加前缀

    项目导航栏>Targets>右侧的Utilities>Class Prefix

  4. Carthage - 一个简单、去集中化的Cocoa依赖管理器

    作为一名新时代的90后猿 在swift大势所趋的时候  怎能不会Carthage 配置它其实很简单  下面我们一步一步来 (1)打开你的终端 输入 brew update brew install c ...

  5. C# 安装包制作

    VS制作安装包的一般步骤. 一·新建项目 (1)新建 (2)界面跳转 二·添加引用 (1)添加卸载程序 1.在'C:WINDOWSsystem32'路径下,找到msiexec.exe . 2.将msi ...

  6. 批量转换引擎为innodb

    [root@HE1 ~]# catconvert_Storage_Engine.sh #/bin/bash #FileName:Convert_Storage_Engine.sh #Desc:Conv ...

  7. tomcat服务器搭建之ngrok——将内网地址映射到外网

    最近心血来潮,想学习微信公众号开发.但是自己又没有外网服务器,这个给我带来很大的麻烦. 刚开始申请了新浪云服务和百度云服务,将写好的代码打包丢到相应的云服务器上就行. 但问题就来了,开发中避免不了错误 ...

  8. 内存快照排查OOM,加密时错误方法指定provider方式错误引起的OOM

    写在前面: 最近开始总结内存方面的东西,已经总结以前遇到的一些内存案例分享下,接下来还有几篇,然后是进程/线程相关的,逐渐形成我的知识体系树 如果你有兴趣,可以文章末尾的公众号二维码一起梳理这些信息. ...

  9. 蓝桥杯java试题《洗牌》

    问题描述 小弱T在闲暇的时候会和室友打扑克,输的人就要负责洗牌.虽然小弱T不怎么会洗牌,但是他却总是输. 渐渐地小弱T发现了一个规律:只要自己洗牌,自己就一定会输.所以小弱T认为自己洗牌不够均匀,就独 ...

  10. GIT团队开发操作

    01. 建立代码仓库(专门用于团队开发的代码仓库) ========================================================================== ...