可以使用 Content-Type: multipart/form-data 发布吗?

huangapple go评论96阅读模式
英文:

Can I post with Content-Type: multipart/form-data

问题

如何使用Content-Type: multipart/form-data、[]byte参数和字符串参数向API进行POST请求?我已经尝试过,但是失败了。

错误信息:

details: "[301 301 Moved Permanently]<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n<html>\r\n301 Moved Permanently\r\n<body bgcolor=\"white\">\r\n301 Moved Permanently\r\n<p>The requested resource has been assigned a new permanent URI.</p >\r\n<hr/>Powered by Tengine/2.1.0</body>\r\n</html>\r\n"

Go代码:

func NewPost2(url string) ([]byte, error) {
    m := make(map[string]interface{}, 0)
    m["fileName"] = "good"
    m["name"] = Base64ToByte("/9j/4AAQSkZJRgABAQEAeAB4AAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAABAAEDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDHooor+wD+Zz//2Q==")
    b, _ := json.Marshal(m)

    httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(b))
    httpReq.Header.Set("Content-Type", "multipart/form-data;charset=UTF-8")

    client := &http.Client{}
    resp, err := client.Do(httpReq)
    if err != nil {
        return nil, err
    }

    defer resp.Body.Close()

    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        b, _ := ioutil.ReadAll(resp.Body)
        return nil, fmt.Errorf("[%d %s]%s", resp.StatusCode, resp.Status, string(b))
    }

    respData, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    return respData, nil
}
英文:

How do I POST to an API with Content-Type: multipart/form-data, []byte parameters and string arguments? I have tried, but it is failing.

Error message:

details: &quot;[301 301 Moved Permanently]&lt;!DOCTYPE HTML PUBLIC \&quot;-//IETF//DTD HTML 2.0//EN\&quot;&gt;\r\n&lt;html&gt;\r\n301 Moved Permanently\r\n&lt;body bgcolor=\&quot;white\&quot;&gt;\r\n301 Moved Permanently\r\n&lt;p&gt;The requested resource has been assigned a new permanent URI.&lt;/p &gt;\r\n&lt;hr/&gt;Powered by Tengine/2.1.0&lt;/body&gt;\r\n&lt;/html&gt;\r\n&quot;

Go code:

func NewPost2(url string) ([]byte, error) {
    m := make(map[string]interface{}, 0)
    m[&quot;fileName&quot;] =&quot;good&quot;
    m[&quot;name&quot;] = Base64ToByte(&quot;/9j/4AAQSkZJRgABAQEAeAB4AAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAABAAEDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDHooor+wD+Zz//2Q==&quot;)
b, _ := json.Marshal(m)

    httpReq, err := http.NewRequest(&quot;POST&quot;, url, bytes.NewBuffer(b))
    httpReq.Header.Set(&quot;Content-Type&quot;, &quot;multipart/form-data;charset=UTF-8&quot;)

    client := &amp;http.Client{}
    resp, err := client.Do(httpReq)
    if err != nil {
	    return nil, err
    }

    defer resp.Body.Close()

    if resp.StatusCode &lt; 200 || resp.StatusCode &gt;= 300 {
	    b, _ := ioutil.ReadAll(resp.Body)
	    return nil, fmt.Errorf(&quot;[%d %s]%s&quot;, resp.StatusCode, resp.Status, string(b))
    }

    respData, err := ioutil.ReadAll(resp.Body)
    if err != nil {
	    return nil, err
    }

    return respData, nil
}

答案1

得分: 10

现在,我非常高兴地分享我的解决方案。

func NewPostFile(url string, paramTexts map[string]interface{}, paramFile FileItem) ([]byte, error) {
	// if paramFiles ==nil {
	// 	return NewPost(url,paramTexts,header,transport)
	// }

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

	for k, v := range paramTexts {
		bodyWriter.WriteField(k, v.(string))
	}
	fileWriter, err := bodyWriter.CreateFormFile(paramFile.Key, paramFile.FileName)
	if err != nil {
		fmt.Println(err)
		//fmt.Println("Create form file error: ", error)
		return nil, err
	}
	fileWriter.Write(paramFile.Content)
	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()
	fmt.Println(bodyBuf.String())

	resp, err := http.Post(url, contentType, bodyBuf)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	fmt.Println(resp)

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		b, _ := ioutil.ReadAll(resp.Body)
		return nil, fmt.Errorf("[%d %s]%s", resp.StatusCode, resp.Status, string(b))
	}
	respData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	fmt.Println(string(respData))
	return respData, nil
}

type FileItem struct {
	Key      string //image_content
	FileName string //test.jpg
	Content  []byte //[]byte
}

以上是要翻译的内容。

英文:

Now, I am very happy with the mood to share my solution

func NewPostFile(url string, paramTexts map[string]interface{}, paramFile FileItem) ([]byte, error) {
// if paramFiles ==nil {
// 	return NewPost(url,paramTexts,header,transport)
// }
bodyBuf := &amp;bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuf)
for k, v := range paramTexts {
bodyWriter.WriteField(k, v.(string))
}
fileWriter, err := bodyWriter.CreateFormFile(paramFile.Key, paramFile.FileName)
if err != nil {
fmt.Println(err)
//fmt.Println(&quot;Create form file error: &quot;, error)
return nil, err
}
fileWriter.Write(paramFile.Content)
contentType := bodyWriter.FormDataContentType()
bodyWriter.Close()
fmt.Println(bodyBuf.String())
resp, err := http.Post(url, contentType, bodyBuf)
if err != nil {
return nil, err
}
defer resp.Body.Close()
fmt.Println(resp)
if resp.StatusCode &lt; 200 || resp.StatusCode &gt;= 300 {
b, _ := ioutil.ReadAll(resp.Body)
return nil, fmt.Errorf(&quot;[%d %s]%s&quot;, resp.StatusCode, resp.Status, string(b))
}
respData, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
fmt.Println(string(respData))
return respData, nil

}

type FileItem struct {
Key      string //image_content
FileName string //test.jpg
Content  []byte //[]byte

}

答案2

得分: 1

我将多部分代码封装在一个函数中,因为在发出请求之前需要使用Close关闭它。我的方法使用@作为启发式方法,类似于cURL [1]:

package main
import (
"bytes"
"io"
"mime/multipart"
"os"
"strings"
)
func createForm(form map[string]string) (string, io.Reader, error) {
body := new(bytes.Buffer)
mp := multipart.NewWriter(body)
defer mp.Close()
for key, val := range form {
if strings.HasPrefix(val, "@") {
val = val[1:]
file, err := os.Open(val)
if err != nil { return "", nil, err }
defer file.Close()
part, err := mp.CreateFormFile(key, val)
if err != nil { return "", nil, err }
io.Copy(part, file)
} else {
mp.WriteField(key, val)
}
}
return mp.FormDataContentType(), body, nil
}

示例:

package main
import "net/http"
func main() {
form := map[string]string{"profile": "@portrait.jpg", "name": "John"}
ct, body, err := createForm(form)
if err != nil {
panic(err)
}
http.Post("https://stackoverflow.com", ct, body)
}
  1. https://curl.se/docs/manpage.html#-F
英文:

I wrapped the multipart code in a function, as you need to Close it before you
can make a request. Also my method is using @ as a heuristic, similar to
cURL [1]:

package main
import (
&quot;bytes&quot;
&quot;io&quot;
&quot;mime/multipart&quot;
&quot;os&quot;
&quot;strings&quot;
)
func createForm(form map[string]string) (string, io.Reader, error) {
body := new(bytes.Buffer)
mp := multipart.NewWriter(body)
defer mp.Close()
for key, val := range form {
if strings.HasPrefix(val, &quot;@&quot;) {
val = val[1:]
file, err := os.Open(val)
if err != nil { return &quot;&quot;, nil, err }
defer file.Close()
part, err := mp.CreateFormFile(key, val)
if err != nil { return &quot;&quot;, nil, err }
io.Copy(part, file)
} else {
mp.WriteField(key, val)
}
}
return mp.FormDataContentType(), body, nil
}

Example:

package main
import &quot;net/http&quot;
func main() {
form := map[string]string{&quot;profile&quot;: &quot;@portrait.jpg&quot;, &quot;name&quot;: &quot;John&quot;}
ct, body, err := createForm(form)
if err != nil {
panic(err)
}
http.Post(&quot;https://stackoverflow.com&quot;, ct, body)
}
  1. https://curl.se/docs/manpage.html#-F

答案3

得分: 0

在301响应中,新的URL在响应的headers中指定,而不是在响应的主体中。

(参见https://en.wikipedia.org/wiki/HTTP_301)

尝试打印:

resp.Header["Location"]

如果您将此错误作为最终响应,这也意味着http.Client选择不遵循此重定向。

文档中指出,Client的默认策略是遵循最多10次重定向。

为了调试重定向,您可以编写自己的CheckRedirect函数,该函数可以打印URL序列,例如:

英文:

On a 301 response, the new url is specified in the headers of the response, not its body

(see https://en.wikipedia.org/wiki/HTTP_301)

try printing :

resp.Header[&quot;Location&quot;]

If you have this error as a final response, this also means that the http.Client chose to not follow this redirection.

The doc says that the dafult policy for a Client is to follow up to 10 redirects.

In order to debug redirects, you can write your own CheckRedirect function, which can for instance print the sequence of urls

huangapple
  • 本文由 发表于 2017年6月1日 16:30:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/44302374.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定