multipart-parser
multipart-parser
是一个快速、高效的多部分流解析器。它可以在任何 JavaScript 环境中使用(不仅限于 node.js),适用于多种场景,包括:
- 处理文件上传(
multipart/form-data
请求) - 解析
multipart/mixed
消息(电子邮件附件、API 响应等) - 解析同时包含纯文本和 HTML 版本的电子邮件消息(
multipart/alternative
)
目标
本项目的目标是:
- 提供一个可在任何 JavaScript 运行环境中使用的多部分解析器
- 支持全系列
multipart/*
消息类型 - 在使用尽可能少内存的同时,保持足够快的处理速度
安装
从 npm 安装:
npm install @mjackson/multipart-parser
或从 JSR 安装:
deno add @mjackson/multipart-parser
使用方法
multipart-parser
最常见的用途是在构建 Web 服务器时处理文件上传。对于这种情况,parseMultipartRequest
函数是你的好帮手。它会自动验证请求是否为 multipart/form-data
,从 Content-Type
头部提取多部分边界,解析 request.body
流中的所有字段和文件,并将每个部分作为 MultipartPart
对象 yield
给你,以便你可以将其保存到磁盘或上传到其他地方。
import { MultipartParseError, parseMultipartRequest } from '@mjackson/multipart-parser';
async function handleMultipartRequest(request: Request): void {
try {
// 解析器会在每个 MultipartPart 可用时 `yield` 它
for await (let part of parseMultipartRequest(request)) {
console.log(part.name);
console.log(part.filename);
if (/^text\//.test(part.mediaType)) {
console.log(await part.text());
} else {
// TODO: part.body 是一个 ReadableStream<Uint8Array>,将其流式传输到文件
}
}
} catch (error) {
if (error instanceof MultipartParseError) {
console.error('解析多部分请求失败:', error.message);
} else {
console.error('发生意外错误:', error);
}
}
}
主模块(import from "@mjackson/multipart-parser"
)假设你正在使用 fetch API(Request
、ReadableStream
等)。Node.js 在 16.5.0 版本 中通过 undici 项目添加了对这些接口的支持。
但是,如果你正在为 Node.js 构建依赖于特定 node API 的服务器,如 http.IncomingMessage
、stream.Readable
和 buffer.Buffer
(类似于 Express 或 http.createServer
),multipart-parser
还提供了一个额外的模块,可直接与这些 API 配合使用。
import * as http from 'node:http';
import { MultipartParseError } from '@mjackson/multipart-parser';
// 注意:从 multipart-parser/node 导入以使用 node 特定的 API
import { parseMultipartRequest } from '@mjackson/multipart-parser/node';
const server = http.createServer(async (req, res) => {
try {
for await (let part of parseMultipartRequest(req)) {
console.log(part.name);
console.log(part.filename);
console.log(part.mediaType);
// ...
}
} catch (error) {
if (error instanceof MultipartParseError) {
console.error('解析多部分请求失败:', error.message);
} else {
console.error('发生意外错误:', error);
}
}
});
server.listen(8080);
底层 API
如果你直接处理多部分边界和不一定属于请求的多部分数据缓冲区/流,multipart-parser
提供了一些你可以直接使用的底层 API:
import { parseMultipart } from '@mjackson/multipart-parser';
// 从某个 API、文件系统等获取数据
let multipartMessage = new Uint8Array();
// 也可以是流或任何 Iterable/AsyncIterable
// let multipartData = new ReadableStream(...);
// let multipartData = [new Uint8Array(...), new Uint8Array(...)];
let boundary = '----WebKitFormBoundary56eac3x';
for await (let part of parseMultipart(multipartMessage, boundary)) {
// 对 part 进行任何你想要的操作...
}
如果你更喜欢基于回调的 API,可以实例化你自己的 MultipartParser
并使用它:
import { MultipartParser } from '@mjackson/multipart-parser';
let multipartMessage = new Uint8Array(); // 或 ReadableStream<Uint8Array>
let boundary = '...';
let parser = new MultipartParser(boundary);
// parser.parse() 将在所有回调完成后解析
await parser.parse(multipartMessage, async (part) => {
// 执行你需要的操作...
});
示例
examples
目录包含了几个如何使用这个库的工作示例:
examples/bun
- 在 Bun 中使用 multipart-parserexamples/cf-workers
- 在 Cloudflare Worker 中使用 multipart-parser 并将文件上传存储在 R2 中examples/deno
- 在 Deno 中使用 multipart-parserexamples/node
- 在 Node.js 中使用 multipart-parser
基准测试
multipart-parser
设计为尽可能高效,仅对数据流进行操作,在常见用法中从不缓冲。这种设计在处理任何大小的多部分负载时都能产生卓越的性能。在大多数基准测试中,multipart-parser
的速度与 busboy
一样快或更快。
重要提示:基准测试可能比较棘手,结果会因平台、参数和其他因素而有很大差异。因此,请谨慎看待这些结果。这个库的主要目的是在 JavaScript 运行时之间保持可移植性。为此,我们在三个主要的开源 JavaScript 运行时上运行基准测试:Node.js、Bun 和 Deno。这些基准测试仅用于表明 multipart-parser 在任何运行环境中都能快速完成工作。
在我的笔记本电脑上运行基准测试的结果:
> @mjackson/multipart-parser@0.4.2 bench:node /Users/michael/Projects/multipart-parser
> node --import tsimp/import ./bench/runner.ts
平台:Darwin (23.5.0) CPU:Apple M1 Pro 日期:2024年8月13日,下午6:47:34 Node.js v22.1.0 ┌──────────────────┬──────────────────┬──────────────────┬──────────────────┬───────────────────┐ │ (索引) │ 1个小文件 │ 1个大文件 │ 100个小文件 │ 5个大文件 │ ├──────────────────┼──────────────────┼──────────────────┼──────────────────┼───────────────────┤ │ multipart-parser │ '0.01 ms ± 0.08' │ '1.17 ms ± 0.27' │ '0.12 ms ± 0.03' │ '10.94 ms ± 0.24' │ │ busboy │ '0.03 ms ± 0.08' │ '3.00 ms ± 0.09' │ '0.21 ms ± 0.03' │ '30.10 ms ± 2.73' │ │ @fastify/busboy │ '0.02 ms ± 0.07' │ '1.19 ms ± 0.07' │ '0.38 ms ± 0.07' │ '12.15 ms ± 2.30' │ └──────────────────┴──────────────────┴──────────────────┴──────────────────┴───────────────────┘
@mjackson/multipart-parser@0.4.2 bench:bun /Users/michael/Projects/multipart-parser bun run ./bench/runner.ts
平台:Darwin (23.5.0) CPU:Apple M1 Pro 日期:2024年8月13日,下午6:49:45 Bun 1.1.21 ┌──────────────────┬────────────────┬────────────────┬─────────────────┬─────────────────┐ │ │ 1个小文件 │ 1个大文件 │ 100个小文件 │ 5个大文件 │ ├──────────────────┼────────────────┼────────────────┼─────────────────┼─────────────────┤ │ multipart-parser │ 0.01 ms ± 0.07 │ 0.95 ms ± 0.12 │ 0.13 ms ± 0.09 │ 9.13 ms ± 0.29 │ │ busboy │ 0.03 ms ± 0.10 │ 3.55 ms ± 0.10 │ 0.35 ms ± 0.17 │ 35.54 ms ± 2.57 │ │ @fastify/busboy │ 0.04 ms ± 0.10 │ 7.17 ms ± 0.10 │ 0.62 ms ± 0.13 │ 71.99 ms ± 3.01 │ └──────────────────┴────────────────┴────────────────┴─────────────────┴─────────────────┘
@mjackson/multipart-parser@0.4.2 bench:deno /Users/michael/Projects/multipart-parser deno --unstable-byonm --unstable-sloppy-imports run --allow-sys ./bench/runner.ts
平台:Darwin (23.5.0) CPU:Apple M1 Pro 日期:2024年8月13日,下午6:52:51 Deno 1.45.5 ┌──────────────────┬──────────────────┬───────────────────┬──────────────────┬────────────────────┐ │ (索引) │ 1个小文件 │ 1个大文件 │ 100个小文件 │ 5个大文件 │ ├──────────────────┼──────────────────┼───────────────────┼──────────────────┼────────────────────┤ │ multipart-parser │ "0.02 ms ± 0.18" │ "1.18 ms ± 1.07" │ "0.10 ms ± 0.43" │ "11.00 ms ± 1.18" │ │ busboy │ "0.04 ms ± 0.27" │ "3.02 ms ± 1.00" │ "0.29 ms ± 0.71" │ "30.22 ms ± 2.61" │ │ @fastify/busboy │ "0.05 ms ± 0.31" │ "12.32 ms ± 0.73" │ "0.77 ms ± 0.97" │ "125.04 ms ± 8.39" │ └──────────────────┴──────────────────┴───────────────────┴──────────────────┴────────────────────┘
我鼓励你自己运行这些基准测试。你可能会得到不同的结果!
pnpm run bench
致谢
感谢Jacob Ebey在发布前对这个项目进行了多次代码审查。
许可证
请参阅LICENSE