GoLang – 跟随重定向处理带有请求体数据的 POST 请求

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

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.

但有时服务器会对POST请求返回302重定向,这意味着我需要将相同的请求体发送到另一个位置。

在这种情况下,我应该怎么做?

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) // 如果服务器返回302重定向,那么意味着我需要使用相同的请求体进行相同的请求
	// 只需将"example.com/test"替换为"example.com/redirect_url"
}

希望这能帮到你!

英文:

I want to follow redirect with the same body for POST request.

From GO sources (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.

But sometimes server returns 302 Redirect for POST request that means I need to send the same body to another location.

What should I do in this situation?

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"
}

答案1

得分: 4

根据RFC7231的规定:

服务器应该在响应中生成一个包含不同URI的URI引用的Location头字段。用户代理可以使用Location字段值进行自动重定向。服务器的响应有效载荷通常包含一个带有指向不同URI的超链接的简短超文本注释。

注意:出于历史原因,用户代理可以将请求方法从POST更改为GET以进行后续请求。如果不希望出现这种行为,可以使用307(临时重定向)状态码。

因此,你可以遵循重定向,新的URI在Location头字段中。但你不必这样做。你可以将方法更改为GET,但也不必如此。因此,你所做的任何操作都符合RFC的规定。

你可以通过提供一个CheckRedirect函数来定义自己的重定向策略。redirectPostOn302函数基本上执行与客户端includeBodytrueredirectMethodPOST时执行的相同操作:

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) // 如果服务器返回302重定向,则意味着我需要使用相同的请求和相同的正文进行相同的请求
    // 到不同的位置。想象一下,我将"example.com/test"替换为"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

		// 获取原始请求的正文,在此处设置,因为如果返回了302,则req.Body将为nil
		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
}
英文:

From 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.

So you MAY follow the redirect, the new URI is in the Location header. You don't have to. And you MAY change the the method to GET, but don't have to. So essentially, anything you do is RFC compliant.

You can provide your own redirect policy by supplying a CheckRedirect function. redirectPostOn302 basically does the same as the client would do if includeBody was true and redirectMethod was POST:

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
}

答案2

得分: 1

最佳实践是更改服务器状态码为307(临时重定向)或308(永久重定向)。

如果服务器返回重定向,客户端首先使用CheckRedirect函数确定是否应该跟随重定向。如果允许,301、302或303重定向会导致后续请求使用HTTP方法GET(如果原始请求是HEAD,则使用HEAD),不带正文。307或308重定向会保留原始的HTTP方法和正文,前提是定义了Request.GetBody函数。NewRequest函数会自动为常见的标准库正文类型设置GetBody。

另一种超级巧妙的方法可能是在CheckRedirect函数中更改请求。

// 示例hack
func FollowRedirectForPost(data io.Reader) {
    client := &http.Client{
        CheckRedirect: func(req *Request, via []*Request) error {
            // 检查状态码等等
            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) // 如果服务器返回302重定向,那么意味着我需要使用相同的正文进行相同的请求
    // 到不同的位置。想象一下,我将"example.com/test"替换为"example.com/redirect_url"
}
英文:

The best practice would be to change the server status code - 307 (Temporary Redirect) or
308 (Permanent Redirect).

> 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.

Another super hacky way could be - changing the request in CheckRedirect function
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"
}
</details>

huangapple
  • 本文由 发表于 2021年12月29日 00:38:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/70509909.html
匿名

发表评论

匿名网友

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

确定