使用S3预签名Post URL上传文件:服务端缺失Content-Length报错
问题:S3预签名Post URL上传异常 - 浏览器正常服务端报错MissingContentLength
问题现象
通过Lambda生成预签名Post URL,浏览器端上传文件到S3完全正常,但服务端用相同逻辑上传时失败,报错:
You must provide the Content-Length HTTP header
详细错误返回:
<?xml version="1.0" encoding="UTF-8"?> <Error> <Code>MissingContentLength</Code> <Message>You must provide the Content-Length HTTP header.</Message> <RequestId>JP75YMFARK0G3X5Z</RequestId> <HostId>toHsKmxmVYYAtac94cQoy8wXoregKG3PNBm97c3gQewEmKxLggcumTAP882T/pJNWx/lxRgH98A=</HostId> </Error> Request failed with status code 411
服务端代码
const axios = require('axios'); const { createReadStream, readFileSync } = require('fs'); const FormData = require('form-data'); const getPostPresignedUrl = async () => { var config = { method: 'post', url: LAMBDA_GET_URL, headers: { 'Content-Type': 'application/json', }, data: JSON.stringify({ key: 'test-2.jpg', fileType: 'image/jpeg', }), }; const { data: { data }, } = await axios(config); return data; }; const uploadFileToS3 = async (fields, url) => { const formData = new FormData(); Object.entries(fields).map(([key, value]) => { formData.append(key, value); }); const file = createReadStream('./test-1.jpg'); formData.append('file', file); try { const { data } = await axios({ url, method: 'post', headers: { 'Content-Type': 'multipart/form-data', }, data: formData, }); } catch (error) { if (error instanceof axios.AxiosError) { console.log(error.response.data); } console.log(error.message); } }; const init = async () => { const { fields, url } = await getPostPresignedUrl(); await uploadFileToS3(fields, url); }; init();
浏览器端代码
const form = document.getElementById('form'); const input = document.getElementById('file'); const getPostPresignedUrl = async (name) => { var config = { method: 'post', url: LAMBDA_GET_URL, headers: { 'Content-Type': 'application/json', }, data: JSON.stringify({ key: name, fileType: 'image/jpeg', }), }; const { data: { data }, } = await axios(config); return data; }; const uploadFileToS3 = async (fields, url, file) => { const formData = new FormData(); Object.entries(fields).map(([key, value]) => { formData.append(key, value); }); formData.append('file', file); try { const { data } = await axios({ url, method: 'post', headers: { 'Content-Type': 'multipart/form-data', }, data: formData, }); } catch (error) { if (error instanceof axios.AxiosError) { console.log(error.response.data); } console.log(error.message); } }; const handleSubmit = async (e) => { e.preventDefault(); const file = input.files[0]; const data = await getPostPresignedUrl(file.name); await uploadFileToS3(data.fields, data.url, file); }; form.onsubmit = handleSubmit;
解决方案
问题出在服务端axios请求的两个核心问题:
- 手动设置
Content-Type: multipart/form-data会丢失form-data自动生成的boundary标识,导致S3无法正确解析请求体。 - 使用文件流时,form-data无法自动获取文件大小,需要手动添加
Content-Length头以满足S3的要求。
修改服务端的uploadFileToS3函数:
const fs = require('fs'); // 确保引入fs模块 const uploadFileToS3 = async (fields, url) => { const formData = new FormData(); // 追加预签名字段 Object.entries(fields).forEach(([key, value]) => { formData.append(key, value); }); const filePath = './test-1.jpg'; // 获取文件大小,用于设置Content-Length const fileStats = fs.statSync(filePath); const fileSize = fileStats.size; const file = createReadStream(filePath); // 追加文件时指定文件名,确保S3能正确识别文件信息 formData.append('file', file, { filename: 'test-1.jpg' }); try { const { data } = await axios({ url, method: 'post', // 使用form-data的getHeaders()获取包含boundary的正确头 headers: { ...formData.getHeaders(), 'Content-Length': fileSize }, data: formData, }); console.log('上传成功:', data); } catch (error) { if (error instanceof axios.AxiosError) { console.error('上传失败响应:', error.response.data); } console.error('上传失败:', error.message); } };
关键修改点说明
- 用
formData.getHeaders()替代手动设置Content-Type,确保请求头包含正确的multipart/form-data; boundary=xxx格式。 - 通过
fs.statSync获取文件大小,手动添加Content-Length头,满足S3的强制要求。 - 追加文件时指定
filename参数,避免S3接收的文件缺少名称信息。
内容的提问来源于stack exchange,提问作者t97
相关产品推荐
相关产品推荐

