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

使用Go发送multipart/form-data POST请求时遇500错误求助

问题分析与修复方案

核心问题

你的代码存在两个关键问题,导致Express服务无法解析请求体:

  1. Multipart表单未完成写入:multipart.Writer需要显式调用Close()来写入表单的结束边界,否则请求体不完整,Express解析时会判定请求为空。原代码中defer writer.Close()会在函数执行完毕才触发,此时请求已经发送,buffer中没有完整的表单数据。
  2. 忽略字段写入错误:代码用_忽略了writer.WriteField()的返回错误,若字段写入失败,会直接导致请求体损坏,无法被正确解析。

修复后的代码

func RenderAndUploadToS3(files []*multipart.FileHeader, browserName string, paths string, screenshotHeight int, screenshotWidth int, buildId string) error {
    var buffer bytes.Buffer
    writer := multipart.NewWriter(&buffer)
    
    numberOfFiles := len(files)
    logger.InfoLog("The number of stories to be rendered for buildId: " + buildId + " is " + strconv.Itoa(numberOfFiles))

    for i := 0; i < numberOfFiles; i++ {
        f, err := files[i].Open()
        if err != nil {
            logger.ErrorLog("Failed to open file for buildId:" + buildId + " due to " + err.Error())
            return err
        }
        defer f.Close() // 用defer确保文件一定会关闭

        // 创建文件字段
        formFile, err := writer.CreateFormFile("html", files[i].Filename)
        if err != nil {
            logger.ErrorLog("Failed to create writer for file for buildId: " + buildId + " due to " + err.Error())
            return err
        }
        _, err = io.Copy(formFile, f)
        if err != nil {
            logger.ErrorLog("Error while copying file contents into writer for buildId: " + buildId + " due to " + err.Error())
            return err
        }
    }

    // 检查所有字段写入的错误,不能忽略
    if err := writer.WriteField("browserName", browserName); err != nil {
        logger.ErrorLog("Failed to write browserName field for buildId: " + buildId + " due to " + err.Error())
        return err
    }
    if err := writer.WriteField("paths", paths); err != nil {
        logger.ErrorLog("Failed to write paths field for buildId: " + buildId + " due to " + err.Error())
        return err
    }
    if err := writer.WriteField("height", strconv.Itoa(screenshotHeight)); err != nil {
        logger.ErrorLog("Failed to write height field for buildId: " + buildId + " due to " + err.Error())
        return err
    }
    if err := writer.WriteField("width", strconv.Itoa(screenshotWidth)); err != nil {
        logger.ErrorLog("Failed to write width field for buildId: " + buildId + " due to " + err.Error())
        return err
    }
    if err := writer.WriteField("buildId", buildId); err != nil {
        logger.ErrorLog("Failed to write buildId field for buildId: " + buildId + " due to " + err.Error())
        return err
    }

    // 必须在创建请求前关闭writer,写入表单结束边界
    if err := writer.Close(); err != nil {
        logger.ErrorLog("Failed to close multipart writer for buildId: " + buildId + " due to " + err.Error())
        return err
    }

    url := constants.DOTUI_HOST + constants.DOTUI_RENDER_ROUTE_V2
    logger.InfoLog("The url for sending dotui request is " + url)
    req, err := http.NewRequest("POST", url, &buffer)
    if err != nil {
        logger.ErrorLog("Failed to do POST request for buildId: " + buildId + " due to  " + err.Error())
        return err
    }
    req.Header.Set("Content-Type", writer.FormDataContentType())
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        logger.ErrorLog("Failed to do POST request due to  " + err.Error())
        return err
    }
    defer resp.Body.Close()
    b, err := io.ReadAll(resp.Body)
    if err != nil {
        logger.ErrorLog("Failed to read POST response body from screenshot-render api v2 for buildId:" + buildId + "due to  " + err.Error())
        return err
    }
    logger.InfoLog("The response from screenshot-render api v2 for buildId: " + buildId + " is " + string(b))
    return nil
}

额外优化点

  • 文件打开后用defer f.Close()替代手动Close,避免中间出错return时文件未关闭。
  • 所有字段写入操作都添加错误捕获,防止因写入失败导致请求体损坏。

内容的提问来源于stack exchange,提问作者arush saxena

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 01:24:29