Welcome to Delicate template
Header
Just another WordPress site
Header

go执行multipart/form-data类型POST时无法成功问题处理

2月 27th, 2022 | Posted by 无 名 in golang

最近在用go语言做巨量视频上传接口时遇到问题,记录一下解决过程

巨量视频上传接口显示是要用multipart/form-data类型数据来进行POST

然后我先用go自带的multipart库请求了一下,却返回了错误:No permission to operate account 0,意思是没有权限操作账户0,代码如下:

	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)

	_ = bodyWriter.WriteField("advertiser_id", "1724433764965395")
	_ = bodyWriter.WriteField("video_signature", md5str)
	file, _ := os.Open("./v.mp4")
	defer file.Close()
	part, _ := bodyWriter.CreateFormFile("video_file", "v.mp4")
	_, _ = io.Copy(part, file)

	url := "https://ad.oceanengine.com/open_api/2/file/video/ad/"
	clientOcean := &http.Client{}
	req, _ := http.NewRequest("POST", url, bodyBuf)
	req.Header.Set("Access-Token", "cf3eac6784537bd4def62a1f6548cf3f679349a3")
	req.Header.Set("X-Debug-Mode", "1")
	req.Header.Set("Content-Type", bodyWriter.FormDataContentType())
	defer bodyWriter.Close()


	resp, err := clientOcean.Do(req)
	check(err)
	defer resp.Body.Close()
	res, _ := ioutil.ReadAll(resp.Body)
	fmt.Println(string(res))
	panic("ok")

返回错误如图:

错误可以看出,request_params为空,很明显巨量那边没有获取到任何数据,后来在网上查了很多资料,都没找到解决办法,这个问题已经困扰了我两天。

后来我找来了几个三方请求库,问题依旧,直到我用了httpclient库(github.com/ddliu/go-httpclient),发现竟然能正常请求。

	resp2, err := httpclient.
		WithHeader("Access-Token", "cf3eac6784537bd4def62a1f6548cf3f679349a3").
		WithHeader("X-Debug-Mode", "1").
		Post(url, map[string]string {
			"advertiser_id": "1724433764965395",
			"video_signature": md5str,
			"@video_file": "./v.mp4",
		})
	check(err)
	body1, _ := resp2.ToString()
	fmt.Println(body1)

然后我用httputil来输出请求元数据,意外发现两者居然有细微的差别,没用三方类库的请求结尾竟然少了一个结束boundary界定符。

可以很明显看到用httpclient库请求的时候,多了一个–4b9b558b03b4b6d0caa0ef392615603f368bbfee0c0d2ead26e7dd61cca5–,这个结束符起了关键作用,至此已经找到了问题所在,只要解决这个就可以了。

后来经过排查,发现竟然和defer bodyWriter.Close()这行有关,猜测有可能是bodyWriter过早关闭导致,用变量获取FormDataContentType,再改成err1 := bodyWriter.Close(),移到前面就可以了,最终完整代码如下:

package main

import (
	"os"
	"io"
	"fmt"
	"bytes"
	"net/http"
	"io/ioutil"
	"crypto/md5"
	"mime/multipart"
	"net/http/httputil"
	"github.com/ddliu/go-httpclient"
)

func main() {
	f, _ := os.Open("./v.mp4")
	defer f.Close()
	md5Handle := md5.New()
	_, _ = io.Copy(md5Handle, f)
	md := md5Handle.Sum(nil)
	md5str := fmt.Sprintf("%x", md)

	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)

	_ = bodyWriter.WriteField("advertiser_id", "1724433764965395")
	_ = bodyWriter.WriteField("video_signature", md5str)
	file, _ := os.Open("./v.mp4")
	defer file.Close()
	part, _ := bodyWriter.CreateFormFile("video_file", "v.mp4")
	_, _ = io.Copy(part, file)

	url := "https://ad.oceanengine.com/open_api/2/file/video/ad/"
	contentType := bodyWriter.FormDataContentType()
	err1 := bodyWriter.Close()
	check(err1)

	clientOcean := &http.Client{}
	req, _ := http.NewRequest("POST", url, bodyBuf)
	req.Header.Set("Access-Token", "cf3eac6784537bd4def62a1f6548cf3f679349a3")
	req.Header.Set("X-Debug-Mode", "1")
	req.Header.Set("Content-Type", contentType)

	dump, err := httputil.DumpRequestOut(req, true)
	if err == nil {
		fmt.Printf("%s\n", dump)
	}
	resp, err := clientOcean.Do(req)
	check(err)
	defer resp.Body.Close()
	res, _ := ioutil.ReadAll(resp.Body)
	fmt.Println(string(res))
	panic("ok")

	// post file(multipart) use httpclient
	resp2, err := httpclient.
		WithHeader("Access-Token", "cf3eac6784537bd4def62a1f6548cf3f679349a3").
		WithHeader("X-Debug-Mode", "1").
		WithOption(httpclient.OPT_DEBUG, true).
		Post(url, map[string]string {
			"advertiser_id": "1724433764965395",
			"video_signature": md5str,
			"@video_file": "./v.mp4",
		})
	check(err)
	body1, _ := resp2.ToString()
	fmt.Println(body1)
}

func check(err error) {
	if err != nil{
		fmt.Println(err)
		panic(err)
	}
}

至此,问题得到完美解决,这对于不喜欢用三方类库的是个好方法,毕竟用三方类库编译出来的文件要稍微大一点,分享一下希望对大家有所帮助吧。

You can follow any responses to this entry through the RSS 2.0 Both comments and pings are currently closed.