我观察到,没有为空/零负载的 PATCH 请求设置 Content-Length 标头。即使我们通过
req.Header.Set("content-length", "0")
手动设置它,它实际上并没有在发出的请求中设置。
这种奇怪的行为(Go bug?)仅发生在 PATCH 请求中,并且仅当有效负载为空或 nil(或设置为 http.NoBody)时
package main
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
)
func main() {
url := "http://localhost:9999"
method := "PATCH"
payload := strings.NewReader("")
client := &http.Client {
}
req, err := http.NewRequest(method, url, payload)
if err != nil {
fmt.Println(err)
}
req.Header.Set("Authorization", "Bearer my-token")
req.Header.Set("Content-Length", "0") //this is not honoured
res, err := client.Do(req)
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
fmt.Println(string(body))
}
即使在最新的 go 版本中也可以重现
1.15
。
只需针对一个简单的 http 服务器运行上面的代码并亲自查看即可。
是否有任何解决方案/解决方法可以发送 Content-Length 设置为 0 的 PATCH 请求?
您可以通过将 TransferEncoding 设置为
Content-Length
来告诉 HTTP 客户端包含值为 0 的 identity
标头,如下所示:
url := "http://localhost:9999"
method := "PATCH"
client := &http.Client{}
req, err := http.NewRequest(method, url, http.NoBody)
if err != nil {
panic(err)
}
req.TransferEncoding = []string{"identity"}
req.Header.Set("Authorization", "Bearer my-token")
// req.Header.Set("Content-Length", "0")
请注意对原始代码的以下更改:
req.TransferEncoding = []string{"identity"}
http.NoBody
(对发送长度没有影响)req.Header.Set("Content-Length", "0")
,客户端自行填写identity
的传输编码没有写入请求,因此除了标头Content-Length = 0
之外,请求看起来与之前相同。
不幸的是,这没有记录(请随时向 Go 团队提出问题),但可以在以下代码中看到:
繁琐的细节:
transferWriter.writeHeader 检查以下内容以写入
Content-Length
标头:
// Write Content-Length and/or Transfer-Encoding whose values are a
// function of the sanitized field triple (Body, ContentLength,
// TransferEncoding)
if t.shouldSendContentLength() {
if _, err := io.WriteString(w, "Content-Length: "); err != nil {
return err
}
if _, err := io.WriteString(w, strconv.FormatInt(t.ContentLength, 10)+"\r\n"); err != nil {
return err
}
反过来,shouldCheckContentLength 在长度为零的情况下查看传输编码:
if t.ContentLength == 0 && isIdentity(t.TransferEncoding) {
if t.Method == "GET" || t.Method == "HEAD" {
return false
}
return true
}
isIdentity 验证
TransferEncoding
是完全 []string{"identity"}
:
func isIdentity(te []string) bool { return len(te) == 1 && te[0] == "identity" })
由于有效负载是一个 io.Reader,golang 没有任何方法可以预先知道内容的长度是多少。 你需要告诉 go 内容的长度是多少。
req, err := http.NewRequest(method, url, payload)
if err != nil {
fmt.Println(err)
}
req.ContentLength = len(payload)