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

使用Echo框架构建REST API:无落地转发文件遇FormData解析错误

Echo转发文件到第三方API时"Could not parse content as FormData"问题解决

问题原因

你当前代码的核心问题是:直接把文件流作为POST请求体发送,虽然手动设置了Content-Type: multipart/form-data,但完全不符合multipart表单的规范——既没有添加边界分隔符(boundary),也没有把文件包装成指定的表单字段(比如下游API预期的"file"字段),导致对方无法解析表单数据。

修正方案

使用Go标准库的mime/multipart来构建符合规范的multipart请求体,自动处理boundary和表单结构:

修改后的完整代码

首先调整Handler的UploadFile方法,传递文件名给下游服务:

func (h *Handler) UploadFile(c echo.Context) error {
    formFile, err := c.FormFile("file")
    if err != nil {
        return err
    }

    file, err := formFile.Open()
    if err != nil {
        return err
    }
    defer file.Close()

    // 传递文件名给Upload方法,保证表单字段包含正确的文件名
    cid, err := h.s.Upload(context.Background(), file, formFile.Filename)
    if err != nil {
        return err
    }

    return c.String(http.StatusOK, "Ok")
}

然后重写Storage的Upload方法,用multipart.Writer构建合法的表单请求:

import (
    "bytes"
    "io"
    "mime/multipart"
    "net/http"
    // 保留你原有的其他依赖
)

func (s *Storage) Upload(ctx context.Context, file io.Reader, fileName string) (cid.Cid, error) {
    // 创建缓冲区存储multipart表单数据
    var buf bytes.Buffer
    writer := multipart.NewWriter(&buf)

    // 创建名为"file"的表单字段,传入文件名
    formFile, err := writer.CreateFormFile("file", fileName)
    if err != nil {
        return cid.Undef, err
    }

    // 将文件内容复制到表单字段中
    _, err = io.Copy(formFile, file)
    if err != nil {
        return cid.Undef, err
    }

    // 必须关闭writer,否则会缺少boundary结束标记
    if err = writer.Close(); err != nil {
        return cid.Undef, err
    }

    // 创建POST请求,使用缓冲区作为请求体
    req, err := http.NewRequest(http.MethodPost, s.config.endpoint+"/upload", &buf)
    if err != nil {
        return cid.Undef, err
    }

    // 设置请求头:Content-Type由writer自动生成(包含正确的boundary)
    req.Header.Set("accept", "application/json")
    req.Header.Set("Content-Type", writer.FormDataContentType())
    req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.config.token))

    res, err := s.config.hc.Do(req)
    if err != nil {
        return cid.Undef, err
    }
    defer res.Body.Close() // 及时关闭响应体,避免资源泄漏

    d := json.NewDecoder(res.Body)
    if res.StatusCode != http.StatusOK {
        return cid.Undef, fmt.Errorf("unexpected response status: %d", res.StatusCode)
    }
    var out struct {
        Cid string `json:"cid"`
    }
    if err = d.Decode(&out); err != nil {
        return cid.Undef, err
    }
    return cid.Parse(out.Cid)
}

关键注意点

  • multipart.Writer会自动生成唯一的boundary,并通过FormDataContentType()返回正确的Content-Type值(格式为multipart/form-data; boundary=xxxx),这是下游API能解析表单的核心。
  • 必须用CreateFormFile创建对应字段的表单项,确保文件被包装在下游API预期的字段名下(这里是"file")。
  • 一定要关闭multipart.Writer,否则请求体末尾会缺少boundary的结束标记,依然会导致解析失败。
  • 别忘了关闭响应体res.Body,避免HTTP客户端的连接泄漏。

内容的提问来源于stack exchange,提问作者Danila Derzhavin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 23:47:30