无需body-parser,如何在Express(NestJS)中增大请求体大小限制?
问题描述
现有一个基于Express的NestJS应用,采用流式方式处理文件上传:
- Node.js端通过读取文件流并使用Axios发送:
const data = fs.createReadStream(filePath); await axios.post('/files/upload', data)
- NestJS端通过
req对象获取传入的文件流,再将该流传入S3的PutObjectCommand完成上传:
@Post('/upload') async upload(@Req() req: Request) { await s3Client.send(new PutObjectCommand({ Bucket: 'bucket-name', Key: 'file-key', Body: req })); }
上传大文件时,NestJS抛出错误:[ExceptionsHandler] request entity too large PayloadTooLargeError: request entity too large。
尝试添加express.urlencoded({ limit: '5gb', parameterLimit: Infinity })中间件虽解决了错误,但导致应用内存占用过高——原因是该中间件会解析请求体并将文件内容以缓冲区形式存入req.body。
咨询:能否在不解析请求体并存入req.body的前提下增大请求体大小限制?或是否有其他更合适的S3文件上传方案?
解决方案
一、不解析请求体调整大小限制
直接用Express的raw中间件配置大小限制即可,它只会校验请求体大小,不会解析内容到req.body,完全保留原始流:
全局配置(main.ts)
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import * as express from 'express'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 全局设置请求体大小限制,不解析内容 app.use(express.raw({ limit: '5gb', type: '*/*' // 匹配所有请求类型 })); await app.listen(3000); } bootstrap();
单路由配置
如果不想全局生效,也可以给目标路由单独应用中间件:
import { Controller, Post, Req, UseMiddleware } from '@nestjs/common'; import * as express from 'express'; @Controller('files') export class FilesController { @Post('/upload') @UseMiddleware(express.raw({ limit: '5gb', type: '*/*' })) async upload(@Req() req: Request) { await s3Client.send(new PutObjectCommand({ Bucket: 'bucket-name', Key: 'file-key', Body: req })); } }
二、更优的S3大文件上传方案
对于大文件,S3官方推荐分片上传(Multipart Upload),优势包括:断点续传、内存占用低、支持并发上传,能彻底解决大文件上传的内存和稳定性问题。
方案1:手动实现分片上传
- 初始化上传:获取S3分配的上传ID
@Post('/upload/init') async initMultipartUpload() { const command = new CreateMultipartUploadCommand({ Bucket: 'bucket-name', Key: 'file-key' }); const response = await s3Client.send(command); return { uploadId: response.UploadId }; }
- 分片上传:客户端将文件分成固定大小(如5MB)的分片,并发上传并记录每个分片的ETag
// 客户端Node.js示例 const chunkSize = 5 * 1024 * 1024; // 5MB分片 const fileStream = fs.createReadStream(filePath, { highWaterMark: chunkSize }); let partNumber = 1; const parts = []; for await (const chunk of fileStream) { const command = new UploadPartCommand({ Bucket: 'bucket-name', Key: 'file-key', UploadId: uploadId, PartNumber: partNumber, Body: chunk }); const response = await s3Client.send(command); parts.push({ PartNumber: partNumber, ETag: response.ETag }); partNumber++; }
- 完成上传:所有分片上传后,通知S3合并分片
@Post('/upload/complete') async completeMultipartUpload(@Body() body: { uploadId: string, parts: any[] }) { const command = new CompleteMultipartUploadCommand({ Bucket: 'bucket-name', Key: 'file-key', UploadId: body.uploadId, MultipartUpload: { Parts: body.parts } }); await s3Client.send(command); }
方案2:使用AWS SDK工具类自动处理分片
AWS提供的@aws-sdk/lib-storage包中的Upload类,会自动判断文件大小,超过阈值就启用分片上传,无需手动处理分片逻辑:
import { Upload } from '@aws-sdk/lib-storage'; @Post('/upload') async upload(@Req() req: Request) { const upload = new Upload({ client: s3Client, params: { Bucket: 'bucket-name', Key: 'file-key', Body: req }, partSize: 5 * 1024 * 1024, // 5MB分片大小 queueSize: 4 // 并发上传的分片数 }); await upload.done(); }
内容的提问来源于stack exchange,提问作者nikkorfed
相关产品推荐
相关产品推荐

