You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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请求的两个核心问题:

  1. 手动设置Content-Type: multipart/form-data会丢失form-data自动生成的boundary标识,导致S3无法正确解析请求体。
  2. 使用文件流时,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 16:30:56