内置中间件:connect.static、connect.json、connect.urlencoded、connect.query
好,咱们接着聊 Connect 的内置中间件。说实话,这部分在实际项目中几乎天天用。你想想看,一个 Web 应用无非就是处理三件事:返回静态文件、解析请求体、处理查询参数。Connect 把这三种场景都封装好了,直接拿来用就行。
我个人习惯把内置中间件分成两类:一类是「开箱即用型」,比如 connect.static;另一类是「数据预处理型」,比如 connect.json、connect.urlencoded 和 connect.query。咱们一个一个来看。
connect.static:静态文件服务
这个中间件,说白了就是帮你把硬盘上的文件直接吐给浏览器。我在项目中遇到过好几次这样的场景:前端打包后的 dist 目录,后端直接用 connect.static 挂载,省去了配置 Nginx 的麻烦。
用法很简单:
var connect = require('connect');
var serveStatic = require('serve-static');
var app = connect();
app.use(serveStatic('public'));
app.listen(3000);
嗯,这里要注意:connect.static 在 Connect 3.x 之后被移到了独立的 serve-static 包。如果你用的是较新版本,需要单独安装:
npm install serve-static
它的核心逻辑其实不复杂:收到请求后,把 URL 路径映射到文件系统路径,然后读取文件返回。比如你访问 http://localhost:3000/css/style.css,它就会去 public/css/style.css 找这个文件。
关键参数一览:
| 参数 | 类型 | 说明 |
|---|---|---|
root |
String | 静态文件根目录(必填) |
index |
String/Array | 默认索引文件,默认 'index.html' |
maxAge |
Number | 缓存控制,单位毫秒 |
dotfiles |
String | 是否允许访问点号开头的文件,可选 'allow'、'deny'、'ignore' |
我曾经踩过一个坑:把 root 设成了项目根目录,结果用户可以通过 ../ 路径穿越访问到上级目录的敏感文件。后来我强制把 dotfiles 设为 'deny',并且把根目录限定在一个专门的 public 文件夹里。
connect.json:解析 JSON 请求体
现在前后端分离的项目,大部分接口都用 JSON 传数据。这个中间件就是干这个的——把请求体里的 JSON 字符串解析成 JavaScript 对象,挂到 req.body 上。
用法:
var connect = require('connect');
var bodyParser = require('body-parser');
var app = connect();
app.use(bodyParser.json());
app.use(function(req, res) {
res.end(JSON.stringify(req.body));
});
app.listen(3000);
为什么需要这个?因为 HTTP 请求体本质上是一段字节流,Node.js 的 req 对象本身不会帮你解析。你想想看,如果每次都要自己拼 data 事件、自己 JSON.parse,那得多麻烦。
它内部做了几件事:
- 监听
data事件,收集请求体数据块 - 监听
end事件,拼接完整数据 - 检查
Content-Type是否为application/json - 调用
JSON.parse解析,结果赋值给req.body
小技巧:如果请求体太大,可以设置 limit 参数限制大小,防止恶意攻击。比如 bodyParser.json({ limit: '1mb' })。我一般设成 100KB 就够用了。
connect.urlencoded:解析表单数据
这个跟 connect.json 是兄弟,只不过它处理的是 application/x-www-form-urlencoded 格式。说白了,就是 HTML 表单默认的提交格式。
app.use(bodyParser.urlencoded({ extended: false }));
这里有个 extended 参数,我简单解释一下:
extended: false:使用querystring模块解析,只支持简单的键值对extended: true:使用qs模块解析,支持嵌套对象和数组
我在项目中一般用 extended: true,因为前端有时候会传一些复杂结构。但要注意,qs 模块功能更强,也意味着更耗性能。如果只是简单的表单提交,false 就够了。
connect.query:解析查询字符串
这个中间件最简单,也最容易被忽略。它把 URL 里 ? 后面的查询字符串解析成对象,挂到 req.query 上。
var connect = require('connect');
var app = connect();
app.use(connect.query());
app.use(function(req, res) {
console.log(req.query); // { name: '张三', age: '25' }
res.end('ok');
});
比如访问 /user?name=张三&age=25,req.query 就是 { name: '张三', age: '25' }。
你可能会问:这个跟 urlencoded 有什么区别?区别大了:
query解析的是 URL 里?后面的部分urlencoded解析的是请求体里的表单数据- 两者来源不同,但格式相似
三个解析中间件的对比:
| 中间件 | 数据来源 | Content-Type | 结果挂载 |
|---|---|---|---|
connect.json |
请求体 | application/json |
req.body |
connect.urlencoded |
请求体 | application/x-www-form-urlencoded |
req.body |
connect.query |
URL 查询字符串 | 无 | req.query |
它们是怎么协同工作的?
我画了一张图,帮你理清这几个中间件在请求处理流程中的位置:
从这张图你能看到,请求进来后,connect.query 最先处理 URL 上的查询参数。然后根据 Content-Type 决定走哪个解析器。如果请求体是 JSON,就走 connect.json;如果是表单,就走 connect.urlencoded;如果都不是,就跳过。
实际项目中的组合用法
我一般这样组织中间件顺序:
var connect = require('connect');
var serveStatic = require('serve-static');
var bodyParser = require('body-parser');
var app = connect();
// 1. 日志记录(如果有)
// 2. 静态文件服务(优先处理静态资源)
app.use(serveStatic('public'));
// 3. 解析请求体
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// 4. 解析查询参数
app.use(connect.query());
// 5. 业务路由
app.use('/api/users', function(req, res) {
// req.body 和 req.query 都已经可用
console.log('Body:', req.body);
console.log('Query:', req.query);
res.end(JSON.stringify({ status: 'ok' }));
});
app.listen(3000);
我的经验:静态文件中间件一定要放在前面。为什么呢?因为如果先解析请求体再处理静态文件,那对于图片、CSS 这些请求,解析器白白浪费了 CPU 时间。先匹配静态资源,匹配到了就直接返回,后面的中间件根本不会执行。
好了,这四个内置中间件就讲完了。说白了,它们就是 Connect 给你准备好的「基础设施」,让你不用重复造轮子。记住它们的职责和用法,后面写业务代码的时候会顺手很多。