概述
Express 一旦匹配到路由就会解析请求体。若未显式设置 limit,自定义的 body parser 或框架封装很容易把上限去掉。真正危险的场景不是慢速上传,而是一个请求声称拥有巨大体积,迫使进程在任何鉴权与校验之前分配内存,从而把客户端的小问题放大成整台节点不可用。
步骤
- 按路由族分别设置上限,不要全站共用一个值:
app.use('/api', express.json({ limit: '100kb' }));
app.use('/imports', express.raw({ limit: '5mb', type: 'text/csv' }));
- 在读取流之前检查
Content-Length,声明值超限就直接拒绝并关闭连接:
const MAX_BYTES = 100 * 1024;
app.use('/api', (req, res, next) => {
if (Number(req.headers['content-length'] ?? 0) > MAX_BYTES) {
res.set('Connection', 'close');
return res.status(413).json({ error: 'payload_too_large' });
}
next();
});
- 捕获解析器抛出的
entity.too.large,让分块传输与声明长度两条路径返回同样的 413 结构。 - 把上限收敛为一个导出常量,测试与接口文档都引用它,避免数值漂移。
验证
分别发送略小于和略大于上限的请求:前者应返回 200,后者应在几十毫秒内返回 413,且进程内存没有明显上涨。再用分块传输(不带 Content-Length)发送超限数据,确认请求在流式读取过程中被中止。最后用 curl -H 'Content-Length: 999999999' 验证声明超限时服务器根本不读取请求体。
注意事项
- 绕过 body parser 的路由仍然可以接收任意大的请求体,守卫要加在路由组级别。
- 压缩请求体按解压后的大小计算,上限需要考虑解压后的体积,必要时禁用
Content-Encoding。 - 真正的大文件应改走对象存储的预签名上传,API 只传引用,不要与 JSON 接口共用解析器。