GoLang - 遵循带有正文数据的 POST 请求的重定向
GoLang - follow redirect for POST requests with body data
我想为 POST 请求使用相同的主体进行重定向。
来自 GO 来源(client.go)
func redirectBehavior(reqMethod string, resp *Response, ireq *Request) (redirectMethod string, shouldRedirect, includeBody bool) {
switch resp.StatusCode {
case 301, 302, 303:
redirectMethod = reqMethod
shouldRedirect = true
includeBody = false
// RFC 2616 allowed automatic redirection only with GET and
// HEAD requests. RFC 7231 lifts this restriction, but we still
// restrict other methods to GET to maintain compatibility.
// See Issue 18570.
但有时服务器 returns 302 重定向 POST 请求,这意味着我需要将同一个正文发送到另一个位置。
遇到这种情况我该怎么办?
func FollowRedirectForPost() {
client := &http.Client{}
req, _ := http.NewRequest(http.MethodPost, "example.com/test", strings.NewReader(url.Values{
"key": {"value"},
"key1":{"value1"},
}.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
client.Do(req) // If server returns 302 Redirect so it means I need make the same request with the same body to
// a different location. Just imagine i replaced "example.com/test" to "example.com/redirect_url"
}
来自RFC7231:
The server SHOULD generate a Location header field in the response
containing a URI reference for the different URI. The user agent MAY
use the Location field value for automatic redirection. The server's
response payload usually contains a short hypertext note with a
hyperlink to the different URI(s).
Note: For historical reasons, a user agent MAY change the request
method from POST to GET for the subsequent request. If this
behavior is undesired, the 307 (Temporary Redirect) status code
can be used instead.
因此您可以遵循重定向,新的 URI 在 Location
header 中。你不必。您可以将方法更改为 GET,但不必这样做。所以基本上,您所做的任何事情都符合 RFC 标准。
您可以通过提供 CheckRedirect
函数来提供自己的重定向策略。如果 includeBody
是 true
并且 redirectMethod
是 POST
:
,redirectPostOn302
基本上与 client would do 相同
func FollowRedirectForPost() {
client := &http.Client{
CheckRedirect: redirectPostOn302,
}
req, _ := http.NewRequest(http.MethodPost, "example.com/test", strings.NewReader(url.Values{
"key": {"value"},
"key1":{"value1"},
}.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
client.Do(req) // If server returns 302 Redirect so it means I need make the same request with the same body to
// a different location. Just imagine i replaced "example.com/test" to "example.com/redirect_url"
}
func redirectPostOn302(req *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
lastReq := via[len(via)-1]
if req.Response.StatusCode == 302 && lastReq.Method == http.MethodPost {
req.Method = http.MethodPost
// Get the body of the original request, set here, since req.Body will be nil if a 302 was returned
if via[0].GetBody != nil {
var err error
req.Body, err = via[0].GetBody()
if err != nil {
return err
}
req.ContentLength = via[0].ContentLength
}
}
return nil
}
最佳做法是更改服务器状态代码 - 307(临时重定向)或
308(永久重定向)。
If the server replies with a redirect, the Client first uses the CheckRedirect function to determine whether the redirect should be followed. If permitted, a 301, 302, or 303 redirect causes subsequent requests to use HTTP method GET (or HEAD if the original request was HEAD), with no body. A 307 or 308 redirect preserves the original HTTP method and body, provided that the Request.GetBody function is defined. The NewRequest function automatically sets GetBody for common standard library body types.
另一种超级 hacky 的方法可能是 - 在 CheckRedirect 函数中更改请求
https://github.com/golang/go/blob/master/src/net/http/client.go#L78
https://github.com/golang/go/blob/master/src/net/http/client.go#L691
// example hack
func FollowRedirectForPost(data io.Reader) {
client := &http.Client{
CheckRedirect: func(req *Request, via []*Request) error {
// check status code etc.
req.Method = http.MethodPost
req.Body = data
}
}
req, _ := http.NewRequest(http.MethodPost, "example.com/test", data)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
client.Do(req) // If server returns 302 Redirect so it means I need make the same request with the same body to
// a different location. Just imagine i replaced "example.com/test" to "example.com/redirect_url"
}
我想为 POST 请求使用相同的主体进行重定向。
来自 GO 来源(client.go)
func redirectBehavior(reqMethod string, resp *Response, ireq *Request) (redirectMethod string, shouldRedirect, includeBody bool) {
switch resp.StatusCode {
case 301, 302, 303:
redirectMethod = reqMethod
shouldRedirect = true
includeBody = false
// RFC 2616 allowed automatic redirection only with GET and
// HEAD requests. RFC 7231 lifts this restriction, but we still
// restrict other methods to GET to maintain compatibility.
// See Issue 18570.
但有时服务器 returns 302 重定向 POST 请求,这意味着我需要将同一个正文发送到另一个位置。
遇到这种情况我该怎么办?
func FollowRedirectForPost() {
client := &http.Client{}
req, _ := http.NewRequest(http.MethodPost, "example.com/test", strings.NewReader(url.Values{
"key": {"value"},
"key1":{"value1"},
}.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
client.Do(req) // If server returns 302 Redirect so it means I need make the same request with the same body to
// a different location. Just imagine i replaced "example.com/test" to "example.com/redirect_url"
}
来自RFC7231:
The server SHOULD generate a Location header field in the response containing a URI reference for the different URI. The user agent MAY use the Location field value for automatic redirection. The server's response payload usually contains a short hypertext note with a hyperlink to the different URI(s).
Note: For historical reasons, a user agent MAY change the request method from POST to GET for the subsequent request. If this behavior is undesired, the 307 (Temporary Redirect) status code can be used instead.
因此您可以遵循重定向,新的 URI 在 Location
header 中。你不必。您可以将方法更改为 GET,但不必这样做。所以基本上,您所做的任何事情都符合 RFC 标准。
您可以通过提供 CheckRedirect
函数来提供自己的重定向策略。如果 includeBody
是 true
并且 redirectMethod
是 POST
:
redirectPostOn302
基本上与 client would do 相同
func FollowRedirectForPost() {
client := &http.Client{
CheckRedirect: redirectPostOn302,
}
req, _ := http.NewRequest(http.MethodPost, "example.com/test", strings.NewReader(url.Values{
"key": {"value"},
"key1":{"value1"},
}.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
client.Do(req) // If server returns 302 Redirect so it means I need make the same request with the same body to
// a different location. Just imagine i replaced "example.com/test" to "example.com/redirect_url"
}
func redirectPostOn302(req *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
lastReq := via[len(via)-1]
if req.Response.StatusCode == 302 && lastReq.Method == http.MethodPost {
req.Method = http.MethodPost
// Get the body of the original request, set here, since req.Body will be nil if a 302 was returned
if via[0].GetBody != nil {
var err error
req.Body, err = via[0].GetBody()
if err != nil {
return err
}
req.ContentLength = via[0].ContentLength
}
}
return nil
}
最佳做法是更改服务器状态代码 - 307(临时重定向)或 308(永久重定向)。
If the server replies with a redirect, the Client first uses the CheckRedirect function to determine whether the redirect should be followed. If permitted, a 301, 302, or 303 redirect causes subsequent requests to use HTTP method GET (or HEAD if the original request was HEAD), with no body. A 307 or 308 redirect preserves the original HTTP method and body, provided that the Request.GetBody function is defined. The NewRequest function automatically sets GetBody for common standard library body types.
另一种超级 hacky 的方法可能是 - 在 CheckRedirect 函数中更改请求 https://github.com/golang/go/blob/master/src/net/http/client.go#L78 https://github.com/golang/go/blob/master/src/net/http/client.go#L691
// example hack
func FollowRedirectForPost(data io.Reader) {
client := &http.Client{
CheckRedirect: func(req *Request, via []*Request) error {
// check status code etc.
req.Method = http.MethodPost
req.Body = data
}
}
req, _ := http.NewRequest(http.MethodPost, "example.com/test", data)
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
client.Do(req) // If server returns 302 Redirect so it means I need make the same request with the same body to
// a different location. Just imagine i replaced "example.com/test" to "example.com/redirect_url"
}