← 中文资源

简体中文版本

限制请求体大小并返回稳定的 413

在解析器层限制 JSON 与原始请求体,超限请求在缓冲前被拒绝,并统一返回 413 与固定错误结构。

阅读英文原文

概述

Express 一旦匹配到路由就会解析请求体。若未显式设置 limit,自定义的 body parser 或框架封装很容易把上限去掉。真正危险的场景不是慢速上传,而是一个请求声称拥有巨大体积,迫使进程在任何鉴权与校验之前分配内存,从而把客户端的小问题放大成整台节点不可用。

步骤

  1. 按路由族分别设置上限,不要全站共用一个值:
app.use('/api', express.json({ limit: '100kb' }));
app.use('/imports', express.raw({ limit: '5mb', type: 'text/csv' }));
  1. 在读取流之前检查 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();
});
  1. 捕获解析器抛出的 entity.too.large,让分块传输与声明长度两条路径返回同样的 413 结构。
  2. 把上限收敛为一个导出常量,测试与接口文档都引用它,避免数值漂移。

验证

分别发送略小于和略大于上限的请求:前者应返回 200,后者应在几十毫秒内返回 413,且进程内存没有明显上涨。再用分块传输(不带 Content-Length)发送超限数据,确认请求在流式读取过程中被中止。最后用 curl -H 'Content-Length: 999999999' 验证声明超限时服务器根本不读取请求体。

注意事项

  • 绕过 body parser 的路由仍然可以接收任意大的请求体,守卫要加在路由组级别。
  • 压缩请求体按解压后的大小计算,上限需要考虑解压后的体积,必要时禁用 Content-Encoding。
  • 真正的大文件应改走对象存储的预签名上传,API 只传引用,不要与 JSON 接口共用解析器。