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

Golang实现带自定义字段的S3 Multipart上传报错求助

问题

本人是Golang新手,现有一段可正常运行的Python代码用于向S3上传文件并附带自定义key字段:

import requests
url = "https://bucket.s3-us-east-1.amazonaws.com/"
data = { 'key': 'test.txt' }
files = { 'file': open('/tmp/test.txt', 'rb') }
r = requests.post(url, data=data, files=files)
print "status %s" % r.status_code

尝试用Golang实现相同功能,编写了如下代码:

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
)

func SendPostRequest(url string, filename string) (string, []byte) {
    client := &http.Client{}
    data, err := os.Open(filename)
    if err != nil {
        log.Fatal(err)
    }
    req, err := http.NewRequest("POST", url, data)
    req.Header.Set("Content-Type", "multipart/form-data")

    if err != nil {
        log.Fatal(err)
    }
    resp, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    content, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatal(err)
    }
    return resp.Status, content
}

func main() {
    status, content := SendPostRequest("https://bucket.s3.amazonaws.com/", "what.html")
    fmt.Println(status)
    fmt.Println(string(content))
}

但收到S3返回的错误:

The body of your POST request is not well-formed multipart/form-data.

注:已为S3桶配置全权限,当前尝试无凭证上传,后续会限制为仅VPC访问,寻求正确的Golang实现方法。

问题原因

Python的requests库在同时传入data和files参数时,会自动构建符合规范的multipart/form-data请求:自动生成分隔边界(boundary)、将data中的字段和files中的文件分别封装为不同的表单部分(part),并设置正确的Content-Type头(包含boundary信息)。

而你当前的Go代码只是直接把文件内容作为请求体,手动设置了Content-Type: multipart/form-data但没有实际构建multipart表单结构,也没有添加必要的boundary,导致S3无法识别请求体格式。

正确的Golang实现

使用Go标准库中的mime/multipart包构建合法的multipart表单请求,具体代码如下:

package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"os"
)

func SendPostRequest(url string, filename string, objectKey string) (string, []byte) {
	client := &http.Client{}

	// 创建缓冲区存储multipart表单数据
	var requestBody bytes.Buffer
	writer := multipart.NewWriter(&requestBody)

	// 添加自定义key字段
	if err := writer.WriteField("key", objectKey); err != nil {
		log.Fatal(err)
	}

	// 添加文件字段
	file, err := os.Open(filename)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	filePart, err := writer.CreateFormFile("file", filename)
	if err != nil {
		log.Fatal(err)
	}
	// 将文件内容复制到表单部分
	if _, err := io.Copy(filePart, file); err != nil {
		log.Fatal(err)
	}

	// 结束表单构建
	if err := writer.Close(); err != nil {
		log.Fatal(err)
	}

	// 创建POST请求
	req, err := http.NewRequest("POST", url, &requestBody)
	if err != nil {
		log.Fatal(err)
	}

	// 设置自动生成的Content-Type头(包含boundary)
	req.Header.Set("Content-Type", writer.FormDataContentType())

	// 发送请求
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	// 读取响应内容
	content, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	return resp.Status, content
}

func main() {
	// 替换为你的S3桶地址、本地文件路径和目标key
	status, content := SendPostRequest("https://bucket.s3-us-east-1.amazonaws.com/", "/tmp/test.txt", "test.txt")
	fmt.Println(status)
	fmt.Println(string(content))
}

关键说明:

  • 用multipart.Writer构建表单:自动处理boundary生成、表单部分封装,确保请求符合规范。
  • 不要手动设置Content-Type:通过writer.FormDataContentType()获取带boundary的标准头,避免格式错误。
  • 资源清理:用defer确保文件、响应体等资源及时释放。
  • 自定义key字段:通过writer.WriteField添加对应表单字段,和Python代码的data={'key': 'test.txt'}功能一致。

内容的提问来源于stack exchange,提问作者Gracie williams

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 08:48:20