Home > Blockchain >  GoLang - follow redirect for POST requests with body data
GoLang - follow redirect for POST requests with body data

Time:12-29

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

CodePudding user response:

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
}

CodePudding user response:

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