如何在go中使用formdata计算多部分文件请求的内容长度

How to compute content length of multipart file request with formdata in go

目前正在尝试创建分段上传请求以上传 png 以及一些额外的正文参数。服务器正在返回响应:

The request body did not contain the specified number of bytes. Got 142,827, expected 146,836

显然计算出了问题。我终其一生都无法弄清楚如何实际手动计算请求正文和文件的内容长度。请参阅下面我发送请求的代码:

func signUp(email, username, password, profilePath string) account {
    session := createClient()

    capToke := captchaToken(session)

    solved := solveCaptcha(capToke, "example.com")

    if solved == failed {
        panic("Solve Failed")
    }
    extraParams := map[string]string{
        "Username":             username,
        "Age":                  string(random(18, 21)),
        "Gender":               "1",
        "EmailAddress":         email,
        "Password":             password,
        "ConfirmPassword":      password,
        "g-recaptcha-response": solved,
        "Recaptcha":            "",
        "ReceiveNewsletter":    "false",
    }

    req, err := makeFileUploadRequest("https://example.com", extraParams, "ProfileImage", profilePath)
    if err != nil {
        panic(err)
    }

    req.Header.Set("Host", "example.com")
    req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0")
    req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
    req.Header.Set("Accept-Language", "en-US,en;q=0.5")
    req.Header.Set("Accept-Encoding", "gzip, deflate, br")
    req.Header.Set("Referer", "https://example.com")
    req.Header.Set("Connection", "keep-alive")
    req.Header.Set("Upgrade-Insecure-Requests", "1")

    client := createClient()
    resp, err := client.Do(req)
    defer resp.Body.Close()
    if err != nil {
        panic(err)
    } else {
        var bodyContent []byte
        fmt.Println(resp.StatusCode)
        fmt.Println(resp.Header)
        resp.Body.Read(bodyContent)
        resp.Body.Close()
        fmt.Println(bodyContent)
    }
    var acc account

    acc.email = email
    acc.password = password
    acc.username = username

    return acc
}

func makeFileUploadRequest(uri string, params map[string]string, paramName, path string) (*http.Request, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    fileContents, err := ioutil.ReadAll(file)
    if err != nil {
        return nil, err
    }
    fi, err := file.Stat()
    if err != nil {
        return nil, err
    }
    file.Close()

    body := new(bytes.Buffer)
    writer := multipart.NewWriter(body)

    for key, val := range params {
        _ = writer.WriteField(key, val)
    }
    err = writer.Close()
    if err != nil {
        return nil, err
    }

    part, err := writer.CreateFormFile(paramName, fi.Name())
    if err != nil {
        return nil, err
    }
    ht, _ := http.NewRequest("POST", uri, body)
    ht.Header.Set("Content-Type", writer.FormDataContentType())
    writer.Close()
    return ht, err
}           

错误在这些行上:

ht, _ := http.NewRequest("POST", uri, body)
ht.Header.Set("Content-Type", writer.FormDataContentType())
writer.Close()

对 NewRequest 的调用识别出 body 是一个 bytes.Buffer 并使用缓冲区的当前大小设置内容长度 header。

最终的多部分边界在对 writer.Close() 的调用中写入请求 body。此边界不包含在先前调用 NewRequest 时设置的内容长度中。

要解决此问题,请在创建请求之前关闭编写器:

writer.Close()
ht, _ := http.NewRequest("POST", uri, body)
ht.Header.Set("Content-Type", writer.FormDataContentType())