写点什么

深入浅出 Node.js(七):Connect 模块解析(之一)

2012 年 6 月 05 日

Connect 模块背景

Node.js 的愿望是成为一个能构建高速,可伸缩的网络应用的平台,它本身具有基于事件,异步,非阻塞,回调等特性,这在前几篇专栏中有过描述。正是基于这样的一些特性,Node.js 平台上的 Web 框架也具有不同于其他平台的一些特性,其中 Connect 是众多 Web 框架中的佼佼者。
Connect 在它的官方介绍中,它是 Node 的一个中间件框架。超过 18 个捆绑的中间件和一些精选第三方中间件。尽管 Connect 可能不是性能最好的 Node.jsWeb 框架,但它却几乎是最为流行的 Web 框架。为何 Connect 能在众多框架中胜出,其原因不外乎有如下几个:

  • 模型简单
  • 中间件易于组合和插拔
  • 中间件易于定制和优化
  • 丰富的中间件

Connect 自身十分简单,其作用是基于 Web 服务器做中间件管理。至于如何如何处理网络请求,这些任务通过路由分派给管理的中间件们进行处理。它的处理模型仅仅只是一个中间队列,进行流式处理而已,流式处理可能性能不是最优,但是却是最易于被理解和接受。基于中间件可以自由组合和插拔的情况,优化它十分容易。
Connect 模块目前在 NPM 仓库的 MDO(被依赖最多的模块)排行第八位。但这并没有真实反映出它的价值,因为排行第五位的 Express 框架实际上是依赖 Connect 创建而成的。关于 Express 的介绍,将会在后续的专栏中一一为你讲解。

中间件

让我们回顾一下 Node.js 最简单的 Web 服务器是如何编写的:

复制代码
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(1337, '127.0.0.1');

我们从最朴素的 Web 服务器处理流程开始,可以看到 HTTP 模块基于事件处理网络访问无外乎两个主要的因素,请求和响应。同理的是 Connect 的中间件也是扮演这样一个角色,处理请求,然后响应客户端或是让下一个中间件继续处理。如下是一个中间件最朴素的原型:

复制代码
function (req, res, next) {
// 中间件
}

在中间件的上下文中,有着三个变量。分别代表请求对象、响应对象、下一个中间件。如果当前中间件调用了 res.end() 结束了响应,执行下一个中间件就显得没有必要。

流式处理

为了演示中间件的流式处理,我们可以看看中间件的使用形式:

复制代码
var app = connect();
// Middleware
app.use(connect.staticCache());
app.use(connect.static(__dirname + '/public'));
app.use(connect.cookieParser());
app.use(connect.session());
app.use(connect.query());
app.use(connect.bodyParser());
app.use(connect.csrf());
app.use(function (req, res, next) {
// 中间件
});
app.listen(3001);

Conncet 提供 use 方法用于注册中间件到一个 Connect 对象的队列中,我们称该队列叫做中间件队列。

Conncet 的部分核心代码如下,它通过 use 方法来维护一个中间件队列。然后在请求来临的时候,依次调用队列中的中间件,直到某个中间件不再调用下一个中间件为止。

复制代码
app.stack = [];
app.use = function(route, fn){
// …
// add the middleware
debug('use %s %s', route || '/', fn.name || 'anonymous');
this.stack.push({ route: route, handle: fn });
return this;
};

值得注意的是,必须要有一个中间件调用 res.end() 方法来告知客户端请求已被处理完成,否则客户端将一直处于等待状态。
流式处理也是 Node.js 中用于流程控制的经典模式,Connect 模块是典型的应用了它。流式处理的好处在于,每一个中间层的职责都是单一的,开发者通过这个模式可以将复杂的业务逻辑进行分解。

路由

从前文可以看到其实 app.use() 方法接受两个参数,route 和 fn,既路由信息和中间件函数,一个完整的中间件,其实包含路由信息和中间件函数。路由信息的作用是过滤不匹配的 URL。请求在遇见路由信息不匹配时,直接传递给下一个中间件处理。
通常在调用 app.use() 注册中间件时,只需要传递一个中间件函数即可。实际上这个过程中,Connect 会将 / 作为该中间件的默认路由,它表示所有的请求都会被该中间件处理。
中间件的优势类似于 Java 中的过滤器,能够全局性地处理一些事务,使得业务逻辑保持简单。
任何事物均有两面性,当你调用 app.use() 添加中间件的时候,需要考虑的是中间件队列是否太长,因为每一层中间件的调用都是会降低性能的。为了提高性能,在添加中间件的时候,如非全局需求的,尽量附带上精确的路由信息。
以 multipart 中间件为例,它用于处理表单提交的文件信息,相对而言较为耗费资源。它存在潜在的问题,那就是有可能被人在客户端恶意提交文件,造成服务器资源的浪费。如果不采用路由信息加以限制,那么任何 URL 都可以被攻击。

复制代码
app.use("/upload", connect.multipart({ uploadDir: path }));

加上精确的路由信息后,可以将问题减小。

MVC 目录

借助 Connect 可以自由定制中间件的优势,可以自行提升性能或是设计出适合自己需要的项目。Connect 自身提供了路由功能,在此基础上,可以轻松搭建 MVC 模式的框架,以达到开发效率和执行效率的平衡。以下是笔者项目中采用的目录结构,清晰地划分目录结构可以帮助划分代码的职责,此处仅供参考。

复制代码
├── Makefile // 构建文件,通常用于启动单元测试运行等操作
├── app.js // 应用文件
├── automation // 自动化测试目录
├── bin // 存放启动应用相关脚本的目录
├── conf // 配置文件目录
├── controllers // 控制层目录
├── helpers // 帮助类库
├── middlewares // 自定义中间件目录
├── models // 数据层目录
├── node_modules // 第三方模块目录
├── package.json // 项目包描述文件
├── public // 静态文件目录
│   ├── images // 图片目录
│   ├── libs // 第三方前端 JavaScript 库目录
│   ├── scripts // 前端 JavaScript 脚本目录
│   └── styles // 样式表目录
├── test // 单元测试目录
└── views // 视图层目录

参考:

关于作者

田永强,新浪微博 @朴灵,前端工程师,曾就职于 SAP,现就职于淘宝,花名朴灵,致力于 NodeJS 和 Mobile Web App 方面的研发工作。双修前后端 JavaScript,寄望将 NodeJS 引荐给更多的工程师。兴趣:读万卷书,行万里路。个人 Github 地址: http://github.com/JacksonTian


感谢崔康对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2012 年 6 月 05 日 00:0032384

评论

发布
暂无评论
发现更多内容

写给孩子的两本书我读得津津有味

孙苏勇

读书 陪伴 随笔杂谈

分布式缓存 - 第五周作业

孙志平

谈谈容器和K8s

Gabriel

架构师训练营第五周总结

Melo

极客大学架构师训练营

起底印度禁用59款应用的数据表现

谢锐 | Frozen

移动应用 游戏开发 游戏出海 移动互联网 游戏制作

Gradle快速入门使用指南 - 安装篇

小隐乐乐

maven

理解Redis的内存回收机制和过期淘汰策略

老胡爱分享

redis LRU

手把手教你看MySQL官方文档

Simon

MySQL

产业数字化无法“一蹴而就”,而是“长跑冠军”。

CECBC区块链专委会

【自学成才系列二】multipass上ubuntu安装篇

小朱

ubuntu multipass

重学 Java 设计模式:实战状态模式「模拟系统营销活动,状态流程审核发布上线场景」

小傅哥

Java 设计模式 小傅哥 重构 代码规范

面试腾讯被问JVM性能调优,勉强入职后,发现工资差了这么多

互联网架构师小马

Java 程序员 面试 性能优化 JVM

腾讯的辣酱不香了 支付宝的区块链真能解决“萝卜章”问题?

CECBC区块链专委会

双链通 萝卜章 区块链方案

锦囊篇|一文摸懂SharedPreferences和MMKV(一)

ClericYi

一文解决MySQL时区相关问题

Simon

MySQL 数据库

面试时被问创建多少个线程合适?你该怎么说?

小谈

面试 线程 JVM springboot SpringCloud

集中全世界程序员的力量,可以在三天之内实现一个手机淘宝吗?

非著名程序员

程序员 软件 程序人生 软件工程 人月神话

Git 的进阶操作

多选参数

git GitHub gitlab

系统架构师week 04 - 互联网架构总结

尔东雨田

极客大学架构师训练营

Java面试常用知识(附赠最新面试题)

架构大数据双料架构师

模式与重构-作业

秤须苑

MyBatis入门

Simon郎

Java mybatis

小师妹学JVM之:JIT中的PrintAssembly续集

程序那些事

JVM jdk8 小师妹 JDK14 assembly

如何快速将 Linux 系统制作成 ISO 镜像文件?

JackTian

Linux 运维 操作系统 镜像文件 ISO

Linux 操作系统!开篇!!!

cxuan

Linux

ARTS-week5

王钰淇

ARTS 打卡计划

为什么大家都说SELECT * 效率低

Java小咖秀

MySQL Java 面试 经验

架构师训练营第五周总结

陈靓-哲露

开篇词——你所不知道的神经网络攻防

P小二

神经网络 AIPwn 对抗样本 AI安全 P小二

极客大学架构师训练营 系统架构 分布式缓存 一致性哈希 Hash 第9课 听课总结

John(易筋)

极客时间 极客大学 极客大学架构师训练营 分布式缓存 一致性哈希

微服务网关演进之路

小楼

Java 微服务 dubbo 网关

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

深入浅出Node.js(七):Connect模块解析(之一)-InfoQ