在Node.js里处理multipart/form-data时,为什么用busboy比用body-parser更稳妥
做文件上传接口的时候,很多人第一反应是挂上 `express.json()` 和 `express.urlencoded()`,然后发现 `req.body` 里空空如也,或者只拿到一个奇怪的字段。这个问题通常出现在前端用 `FormData` 提交、Content-Type 是 `multipart/form-data; boundary=...` 的场景。`express.json()` 只认 `application/json`,`urlencoded` 只处理 `application/x-www-form-urlencoded`,它们都不会去解析分段边界和二进制流。更麻烦的是,如果先把整个请求体缓冲成字符串再处理,遇到几 MB 的图片或混入二进制数据时,内存占用和编码转换都可能出问题。这篇文章从实际代码出发,讲清楚 multipart 的边界结构,演示如何用 busboy 以流的方式拿到普通字段和文件,并处理临时文件、大小限制、中文文件名和错误中断这些容易踩的细节。 先看一眼真实请求体的结构。浏览器用 FormData 提交时,请求头里会有类似这样的内容: ```http Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW ``` body 里每一段都以 `--boundary` 开始,字段名放在 `Content-Disposition: form-data; name="..."` 里,文件还会带 `filename` 和 `Content-Type`。最后以 `--boundary--` 结束。如果用字符串拼接的方式去切分,很容易在二进制内容里误判边界,尤其是文件内容恰好包含类似 `--` 加 boundary 的字节序列时。 busboy 的做法是监听流上的事件,把每个 part 当成独立的可读流处理。下面是一个不依赖中间件的原生 http 例子: ```js const http = require('http') const Busboy = require('busboy') const fs = require('fs') const path = require('path') const os = require('os') const server = http.createServer((req, res) => { if (req.method !== 'POST' || !req.headers['content-type']?.startsWith('multipart/form-data')) { res.writeHead(400) return res.end('expected multipart/form-data') } const fields = {} const busboy = Busboy({ headers: req.headers, limits: { fileSize: 5 * 1024 * 1024, files: 2, fields: 10 } }) busboy.on('field', (name, val) => { fields[name] = val }) busboy.on('file', (name, stream, info) => { const { filename, encoding, mimeType } = info // 浏览器提交空文件输入时 filename 可能是空字符串 if (!filename) { stream.resume() return } const safeName = path.basename(filename).replace(/[^\w.\u4e00-\u9fa5-]/g, '_') const tmpPath = path.join(os.tmpdir(), `${Date.now()}-${safeName}`) const ws = fs.createWriteStream(tmpPath) stream.on('limit', () => { ws.destroy() fs.unlink(tmpPath, () => {}) res.writeHead(413) res.end('file too large') }) stream.pipe(ws) ws.on('finish', () => { fields[name] = { tmpPath, filename: safeName, mimeType, encoding } }) }) busboy.on('error', (err) => { res.writeHead(400) res.end('bad multipart: ' + err.message) }) busboy.on('finish', () => { // 注意:finish 只代表解析结束,文件写入可能还在进行 res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify(fields)) }) req.pipe(busboy) }) server.listen(3000) ``` 这段代码里有个容易被忽略的点:`busboy.on('finish')` 触发时,文件流可能还没写完。如果直接在 finish 里返回结果,拿到的临时文件路径可能还不存在。稳妥的做法是用 `Promise` 或者维护一个写入计数,等所有 `ws.on('finish')` 都完成后再响应。我在实际项目里更倾向于把文件处理包成 Promise: ```js const busboy = Busboy({ headers: req.headers, limits: { fileSize: 5 * 1024 * 1024 } }) const fields = {} const fileTasks = [] busboy.on('file', (name, stream, info) => { if (!info.filename) { stream.resume() return } const task = new Promise((resolve, reject) => { const tmpPath = path.join(os.tmpdir(), `${Date.now()}-${path.basename(info.filename)}`) const ws = fs.createWriteStream(tmpPath) stream.on('limit', () => reject(Object.assign(new Error('file too large'), { status: 413 }))) stream.on('error', reject) ws.on('error', reject) ws.on('finish', () => resolve({ name, tmpPath, info })) stream.pipe(ws) }) fileTasks.push(task) }) busboy.on('field', (name, val) => { fields[name] = val }) busboy.on('finish', async () => { try { const files = await Promise.all(fileTasks) // 这里再落库或转存对象存储 console.log(fields, files) } catch (err) { // 清理已经写出的临时文件 } }) req.pipe(busboy) ``` 如果是在 Express 里,不要试图用 `body-parser` 去接 multipart,它本来就不管这个。可以选择 `multer`,它内部也是基于 busboy 封装的。但有些老项目里 multer 版本和 Express 4/5 的兼容问题会带来额外排查成本,直接用 busboy 反而更透明。比如需要边解析边把文件流转发到对象存储,而不是先落盘,busboy 的事件模型就很合适: ```js busboy.on('file', (name, stream, info) => { const upload = storage.createWriteStream({ key: info.filename }) stream.pipe(upload) }) ``` 再提几个实际会遇到的坑。中文文件名在部分客户端里会以 `filename*=` 的 RFC 5987 形式出现,busboy 新版本会做解码,但老版本可能拿到 `UTF-8''%E4%B8%AD%E6%96%87` 这样的原始值,需要自己 `decodeURIComponent`。另外,`limits` 里的 `fileSize` 触发后流会报 `limit` 事件,但请求本身不会自动断开,如果客户端还在继续发数据,最好主动 `req.unpipe(busboy)` 并销毁连接,否则会白白消耗带宽。还有一个常见问题是前面挂了 Nginx,默认 `client_max_body_size` 是 1MB,大文件请求会被 Nginx 直接返回 413,这时候 Node 侧根本收不到请求,排查时先看反向代理配置。 如果不需要保留文件,只是解析字段,可以用 `stream.resume()` 丢弃文件内容,避免写盘。但要注意 `resume()` 之后仍然要等流结束,否则 busboy 可能卡在解析状态。对于需要做类型校验的场景,在 `file` 事件里根据 `info.mimeType` 判断,不要只信任扩展名。最后,任何临时文件都要在请求结束或异常时清理,尤其是上面那种先落盘再处理的写法,`finish` 和 `error` 两条路径都得覆盖到。 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1401.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1402.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1403.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1404.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1405.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1406.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1407.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1408.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1409.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1410.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1411.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1412.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1413.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1414.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1415.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1416.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1417.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1418.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1419.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1420.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1421.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1422.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1423.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1424.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1425.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1426.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1427.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1428.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1429.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1430.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1431.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1432.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1433.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1434.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1435.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1436.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1437.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1438.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1439.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1440.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1441.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1442.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1443.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1444.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1445.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1446.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1447.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1448.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1449.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1450.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1451.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1452.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1453.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1454.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1455.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1456.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1457.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1458.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1459.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1460.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1461.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1462.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1463.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1464.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1465.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1466.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1467.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1468.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1469.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1470.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1471.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1472.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1473.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1474.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1475.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1476.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1477.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1478.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1479.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1480.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1481.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1482.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1483.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1484.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1485.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1486.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1487.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1488.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1489.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1490.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1491.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1492.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1493.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1494.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1495.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1496.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1497.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1498.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1499.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1500.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1501.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1502.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1503.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1504.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1505.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1506.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1507.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1508.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1509.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1510.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1511.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1512.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1513.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1514.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1515.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1516.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1517.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1518.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1519.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1520.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1521.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1522.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1523.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1524.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1525.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1526.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1527.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1528.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1529.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1530.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1531.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1532.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1533.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1534.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1535.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1536.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1537.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1538.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1539.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1540.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1541.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1542.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1543.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1544.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1545.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1546.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1547.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1548.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1549.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1550.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1551.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1552.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1553.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1554.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1555.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1556.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1557.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1558.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1559.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1560.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1561.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1562.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1563.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1564.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1565.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1566.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1567.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1568.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1569.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1570.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1571.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1572.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1573.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1574.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1575.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1576.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1577.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1578.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1579.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1580.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1581.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1582.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1583.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1584.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1585.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1586.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1587.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1588.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1589.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1590.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1591.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1592.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1593.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1594.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1595.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1596.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1597.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1598.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1599.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1600.html