内置中间件: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 内置中间件处理流程 HTTP 请求 connect.query() 解析 URL 查询字符串 → req.query 检查 Content-Type application/json connect.json() → req.body x-www-form-urlencoded connect.urlencoded() → req.body 其他 / 无请求体 跳过解析,继续下一个中间件 注意:connect.static 通常在路由中间件之前注册,而解析类中间件在路由之后、业务逻辑之前注册

从这张图你能看到,请求进来后,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 给你准备好的「基础设施」,让你不用重复造轮子。记住它们的职责和用法,后面写业务代码的时候会顺手很多。