如何在Fastify 2.15中实现Multipart文件上传代理网关
问题描述
我基于Fastify实现了一个后端网关代理服务器,所有请求通过Axios转发,但处理Multipart请求时失败,请求无法到达后端。
代理服务器配置
网关中使用fastify-multipart插件:
app.register(require("fastify-multipart"), { addToBody: true });
请求处理逻辑
handle : async (request) => { const formData = new FormData(); const keys = Object.keys(JSON.parse(request.body)) const buffer = JSON.parse(request.body)[keys[0]][0] formData.append('my_field', keys[0]); formData.append('my_buffer', buffer.data.data[0]); config = { qs : queryStringParameters, method : 'POST', headers : { "Content-Type" : headersNormalized['content-type'], "User-Agent" : headersNormalized['user-agent'], "client-version": headersNormalized['androidversionname'], "Content-Length": headersNormalized['content-length'], }, body: formData, raw : true } const { status, data } = await http(request).LB("/downstream/upload", config) }
当前尝试转发接收的.png文件(网关接收后封装为Multipart格式发送),但后端服务器始终返回500错误。后端服务器同样使用fastify-multipart插件:
app.register(require("fastify-multipart"));
需要指导如何基于Fastify和Node.js实现Multipart文件上传代理网关。
解决方案
核心问题分析
当前实现存在几个关键错误:
- 启用
addToBody: true会把Multipart请求解析为JSON格式,破坏原始的Multipart结构,导致重构时数据格式错误 - 手动解析
request.body并重构FormData的方式丢失了文件元数据(如文件名、MIME类型),后端无法正确解析 - 强行设置原始
Content-Length和Content-Type,与新构建的FormData不匹配,引发后端解析失败
正确实现步骤
1. 调整网关端fastify-multipart配置
关闭addToBody,直接保留原始Multipart流,避免解析破坏结构:
// 网关端插件注册 app.register(require("fastify-multipart"), { limits: { fileSize: 10 * 1024 * 1024 // 根据业务需求调整文件大小限制 } });
2. 网关端转发Multipart请求的正确逻辑
直接转发原始请求流,保证数据格式完全一致,这是最可靠的方式:
handle: async (request) => { // 构建转发用的请求头,排除自动生成的字段 const forwardHeaders = { ...request.headers, 'content-length': undefined, // 让Axios自动计算长度 'host': 'your-downstream-server-host' // 替换为实际后端服务的Host }; const config = { method: 'POST', url: '/downstream/upload', baseURL: 'http://your-downstream-server-url', // 替换为实际后端地址 headers: forwardHeaders, data: request.raw, // 直接转发原始请求流 responseType: 'stream' // 禁用自动解析,保持流的原始状态 }; try { const { status, data } = await axios(config); return { status, data }; } catch (error) { console.error('转发失败:', error); throw error; } }
3. 后端服务器配置(无需修改)
后端继续使用fastify-multipart即可,示例处理路由如下:
app.register(require("fastify-multipart")); // 后端处理上传的示例路由 app.post('/downstream/upload', async (request, reply) => { const parts = await request.parts(); for await (const part of parts) { if (part.file) { // 处理文件流 const buffer = await part.toBuffer(); console.log(`接收文件: ${part.filename}, 大小: ${buffer.length} bytes`); // 这里可以添加文件保存或业务处理逻辑 } else { // 处理普通表单字段 console.log(`字段: ${part.fieldname}, 值: ${part.value}`); } } reply.send({ status: 'success' }); });
替代方案:需修改Multipart内容时的正确写法
如果必须在网关中修改Multipart内容,需保留完整的文件元数据:
handle: async (request) => { const formData = new FormData(); const parts = await request.parts(); // 遍历原始Multipart的所有部分,完整保留元数据 for await (const part of parts) { if (part.file) { const buffer = await part.toBuffer(); // 正确添加文件到FormData,保留文件名和MIME类型 formData.append(part.fieldname, buffer, { filename: part.filename, contentType: part.mimetype }); } else { // 添加普通表单字段 formData.append(part.fieldname, part.value); } } // 可在此添加自定义字段(如果需要) formData.append('my_field', 'custom_value'); const config = { method: 'POST', url: '/downstream/upload', baseURL: 'http://your-downstream-server-url', // 让FormData自动生成包含boundary的正确Content-Type headers: { ...formData.getHeaders(), 'User-Agent': request.headers['user-agent'], 'client-version': request.headers['androidversionname'] }, data: formData }; const { status, data } = await axios(config); return { status, data }; }
关键注意事项
- 不要手动设置
Content-Type和Content-Length,让Axios或FormData自动处理,避免boundary不匹配 - 优先使用原始请求流转发,减少数据解析和重构带来的错误
- 确保网关和后端的fastify-multipart版本兼容,避免解析逻辑差异
内容的提问来源于stack exchange,提问作者Ashok Singh
相关产品推荐
相关产品推荐

