如何在HTTP请求中发送多部分表单数据(用于Watson NLC训练)?

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

How do I send multipart form data in an HTTP request (for Watson NLC training)?

问题

我正在编写一个应用程序,将使用Watson自然语言分类器("NLC")。当我对v1/classifiers URI路径发送HTTP POST请求,并使用以下请求消息体时,服务器会以状态码415("不支持的媒体类型")进行响应:

--04fef47728eb08148fe9c7b18dd42b75abd75ebf752fd3412a85aa3af075
Content-Disposition: form-data; name="training_data"; filename="data.csv"
Content-Type: text/csv

今天有多热?;temperature
外面热吗?;temperature
会不会热得难受?;temperature
会不会酷热难耐?;temperature
今天有多冷?;temperature
外面冷吗?;temperature
会不会冷得难受?;temperature
会不会寒冷刺骨?;temperature
今天预计最高温度是多少?;temperature
预计温度是多少?;temperature
高温会危险吗?;temperature
天气会危险寒冷吗?;temperature
何时会降温?;temperature
天气热吗?;temperature
天气冷吗?;temperature
现在有多冷?;temperature
今天会是寒冷的一天吗?;temperature
何时会停止寒冷?;temperature
预计最高温度是多少?;temperature
预计最低温度是多少?;temperature
天气暖和吗?;temperature
天气凉爽吗?;temperature
目前的摄氏温度是多少?;temperature
华氏温度是多少?;temperature
--04fef47728eb08148fe9c7b18dd42b75abd75ebf752fd3412a85aa3af075
Content-Disposition: form-data; name="training_metadata"; filename="metadata.json"
Content-Type: application/json

{"language": "en"}

415状态码表明内容类型存在问题,但看起来所有的MIME类型都是正确的。

我的Go代码如下:

func (w WatsonClassifier) createFormFile(writer *multipart.Writer, fieldname string, filename string, contentType string) (io.Writer, error)     {
	h := make(textproto.MIMEHeader)
	h.Set("Content-Disposition",
		fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
			fieldname, filename))
	h.Set("Content-Type", contentType)
	return writer.CreatePart(h)
}

func (w WatsonClassifier) request(method string, apiUrl string, body io.Reader) (string, error) {
	url := w.url + "/" + apiUrl
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return "", err
	}
	req.SetBasicAuth(w.username, w.password)
	client := http.Client{}
	resp, err := client.Do(req)
	if resp.StatusCode != 200 {
		answer, _ := ioutil.ReadAll(resp.Body)
		fmt.Println(string(answer))
		return "", errors.New("Watson returned wrong status code : " + resp.Status)
	}
	answer, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(answer), nil
}

func (w WatsonClassifier) Train(data []ClassifierCategory) (Classifier, error) {
	table := w.buildTable(data)
	str := w.buildCsv(data)
	buf := new(bytes.Buffer)
	writer := multipart.NewWriter(buf)
	data_part, err := w.createFormFile(writer, "training_data", "data.csv", "text/csv")
	if err != nil {
		return WatsonClassifier{}, err
	}
	data_part.Write([]byte(str))
	metadata_part, err := w.createFormFile(writer, "training_metadata", "metadata.json", "application/json")
	if err != nil {
		return WatsonClassifier{}, err
	}
	metadata_json := "{\"language\": \"" + w.Language + "\"}"
	metadata_part.Write([]byte(metadata_json))
	fmt.Println(buf.String())
	answer, err := w.request("POST", "v1/classifiers", buf)
	if err != nil {
		return WatsonClassifier{}, err
	}
	fmt.Println(answer)
	return WatsonClassifier{}, nil
}
英文:

I'm writing application that'll use Watson Natural Language Classifier ("NLC").
When I send an HTTP POST request against the v1/classifiers URI path with the following request message body, the server responds with status code 415 (Unsupported Media Type):

--04fef47728eb08148fe9c7b18dd42b75abd75ebf752fd3412a85aa3af075
Content-Disposition: form-data; name="training_data"; filename="data.csv"
Content-Type: text/csv

How hot is it today?;temperature
Is it hot outside?;temperature
Will it be uncomfortably hot?;temperature
Will it be sweltering?;temperature
How cold is it today?;temperature
Is it cold outside?;temperature
Will it be uncomfortably cold?;temperature
Will it be frigid?;temperature
What is the expected high for today?;temperature
What is the expected temperature?;temperature
Will high temperatures be dangerous?;temperature
Is it dangerously cold?;temperature
When will the heat subside?;temperature
Is it hot?;temperature
Is it cold?;temperature
How cold is it now?;temperature
Will we have a cold day today?;temperature
When will the cold subside?;temperature
What highs are we expecting?;temperature
What lows are we expecting?;temperature
Is it warm?;temperature
Is it chilly?;temperature
What's the current temp in Celsius?;temperature
What is the temperature in Fahrenheit?;temperature
--04fef47728eb08148fe9c7b18dd42b75abd75ebf752fd3412a85aa3af075
Content-Disposition: form-data; name="training_metadata"; filename="metadata.json"
Content-Type: application/json

{"language": "en"}

The 415 status code suggests a problem with the content type, but it seems like all the MIME types are correct.

My code (written in Go):

func (w WatsonClassifier) createFormFile(writer *multipart.Writer, fieldname string, filename string, contentType string) (io.Writer, error)     {
	h := make(textproto.MIMEHeader)
	h.Set("Content-Disposition",
		fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
			fieldname, filename))
	h.Set("Content-Type", contentType)
	return writer.CreatePart(h)
}

func (w WatsonClassifier) request(method string, apiUrl string, body io.Reader) (string, error) {
	url := w.url + "/" + apiUrl
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return "", err
	}
	req.SetBasicAuth(w.username, w.password)
	client := http.Client{}
	resp, err := client.Do(req)
	if resp.StatusCode != 200 {
		answer, _ := ioutil.ReadAll(resp.Body)
		fmt.Println(string(answer))
		return "", errors.New("Watson returned wrong status code : " + resp.Status)
	}
	answer, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(answer), nil
}

func (w WatsonClassifier) Train(data []ClassifierCategory) (Classifier, error) {
	table := w.buildTable(data)
	str := w.buildCsv(data)
	buf := new(bytes.Buffer)
	writer := multipart.NewWriter(buf)
	data_part, err := w.createFormFile(writer, "training_data", "data.csv", "text/csv")
	if err != nil {
		return WatsonClassifier{}, err
	}
	data_part.Write([]byte(str))
	metadata_part, err := w.createFormFile(writer, "training_metadata", "metadata.json", "application/json")
	if err != nil {
		return WatsonClassifier{}, err
	}
	metadata_json := "{\"language\": \"" + w.Language + "\"}"
	metadata_part.Write([]byte(metadata_json))
	fmt.Println(buf.String())
	answer, err := w.request("POST", "v1/classifiers", buf)
	if err != nil {
		return WatsonClassifier{}, err
	}
	fmt.Println(answer)
	return WatsonClassifier{}, nil
}

答案1

得分: 3

请注意,curl 发送的 "Content-Type" 头部是 multipart/form-data。而你的 Go 程序只发送了一个 "Content-Disposition" 头部,值为 form-data(注意,它缺少了前缀 multipart,即组合顶级媒体类型),但它没有正确设置包含 HTTP 请求的 "Content-Type" 头部。

Go 的 multipart.Writer 类型的 CreateFormFile 方法也是如此,但这只是工作的一部分:

h.Set("Content-Disposition",
        fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
                escapeQuotes(fieldname), escapeQuotes(filename)))

要获取正确的 "Content-Type" 头部值,你需要使用 multipart.Writer.FormDataContentType。为了使用该值,你需要将你的 multipart.Writer 传递给 WatsonClassifier.request 方法,这样你就可以在 http.Request 实例上设置内容类型:

req.Header.Set("Content-Type", writer.FormDataContentType())

或者,你可以在 WatsonClassifier.request 中添加另一个参数来表示内容类型,并在 WatsonClassifier.Train 的调用点将 FormDataContentType 的结果作为参数传递。

如果以上方法有效,请告诉我们。

英文:

Note that curl is sending a "Content-Type" header of multipart/form-data. Your Go program is sending a "Content-Disposition" header with just form-data (note the difference, it lacking the leading multipart composite top-level media type), but it doesn't take care of sending the correct "Content-Type" header for the containing HTTP request.

Go's multipart.Writer type's CreateFormFile method does the same, but again, that's only part of the job:

h.Set("Content-Disposition",
        fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
                escapeQuotes(fieldname), escapeQuotes(filename)))

To get the proper "Content-Type" header value, you need to use multipart.Writer.FormDataContentType. To put that value to use, you'll need to get your multipart.Writer into your WatsonClassifier.request method, so that you can set the content type on your http.Request instance:

req.Header.Set("Content-Type", writer.FormDataContentType())

Alternately, add another parameter to WatsonClassifier.request for the content type, and pass the result of FormDataContentType as the argument from the call site in WatsonClassifier.Train.

Let us know if that does the trick.

huangapple
  • 本文由 发表于 2016年4月2日 15:20:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/36370407.html
匿名

发表评论

匿名网友

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

确定