Go: client disallows redirects

Go: client disallows redirects


1. Phenomenon

Server code:

package main

import (
	"fmt"
	"net/http"
)

func main() {
    
    
	http.HandleFunc("/do", func(writer http.ResponseWriter, request *http.Request) {
    
    
		fmt.Fprintf(writer, "do")
	})
	http.HandleFunc("/redirect", func(writer http.ResponseWriter, request *http.Request) {
    
    
		http.Redirect(writer, request, "/do", http.StatusMovedPermanently)
	})
	http.ListenAndServe(":8080", nil)
}

Server code description:

  1. The server listens on port 8080;
  2. The server provides two interfaces: /redirect and /do. Accessing /redirect will redirect (301) to /do.

Server-side code testing:

$ curl -v http://127.0.0.1:8080/redirect -L

Insert image description here

Insert image description here

Insert image description here

Client:

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
    
    
	resp, err := http.Get("http://127.0.0.1:8080/redirect")
	defer resp.Body.Close()
	if err != nil {
    
    
		panic(err)
	}
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
    
    
		panic(err)
	}
	fmt.Println(string(data))
}

Client code description:

  1. The client accesses port 8080/redirect, the server returns 301, redirect/do, and the client automatically redirects 2;
  2. The client accesses port 8080 /do, the server returns 200, and the response text is do;

Client code test:

Insert image description here

As you can see, the client implemented by go, similar to the Browser (Chrome) in the picture above, can automatically handle redirection and return the final result after the redirection.


2. Disable automatic redirection of Go client

Without further ado, let’s start with the code:

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
    
    
	c := http.Client{
    
    
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
    
    
			return http.ErrUseLastResponse
		},
	}
	resp, err := c.Get("http://127.0.0.1:8080/redirect")
	defer resp.Body.Close()
	if err != nil {
    
    
		panic(err)
	}
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
    
    
		panic(err)
	}
	fmt.Println(string(data))
}

That is, create http.Client yourself, set the CheckRedirect field, point to the custom function, and return ErrUseLastResponse.

Test: The Go client has disabled redirection and returns the real response (301) for accessing /redirect.

Insert image description here


3.Explain

Refer to http.Client.Do: When the Client receives the redirect, its CheckRedirect determines its behavior (whether to follow or not).

// net/http/client.go

// 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.
//
// Any returned error will be of type *url.Error. The url.Error
// value's Timeout method will report true if request timed out or was
// canceled.
func (c *Client) Do(req *Request) (*Response, error) {
    
    
	return c.do(req)
}

Refer to CheckRedirect instructions:

type Client struct {
    
    
	...
	// CheckRedirect specifies the policy for handling redirects.
	// If CheckRedirect is not nil, the client calls it before
	// following an HTTP redirect. The arguments req and via are
	// the upcoming request and the requests made already, oldest
	// first. If CheckRedirect returns an error, the Client's Get
	// method returns both the previous Response (with its Body
	// closed) and CheckRedirect's error (wrapped in a url.Error)
	// instead of issuing the Request req.
	// As a special case, if CheckRedirect returns ErrUseLastResponse,
	// then the most recent response is returned with its body
	// unclosed, along with a nil error.
	//
	// If CheckRedirect is nil, the Client uses its default policy,
	// which is to stop after 10 consecutive requests.
	CheckRedirect func(req *Request, via []*Request) error
	...
}

Notice:

As a special case, if CheckRedirect returns ErrUseLastResponse, then the most recent response is returned with its body unclosed, along with a nil error.

If CheckRedirect returns an ErrUseLastResponse error, the most recent response (e.g. a 301 response) will be returned (along with a nil error value - meaning no error)

Specific implementation:

// net/http/client.go
func (c *Client) do(req *Request) (retres *Response, reterr error) {
    
    
...
			// Add the Referer header from the most recent
			// request URL to the new one, if it's not https->http:
			if ref := refererForURL(reqs[len(reqs)-1].URL, req.URL); ref != "" {
    
    
				req.Header.Set("Referer", ref)
			}
			err = c.checkRedirect(req, reqs)

			// Sentinel error to let users select the
			// previous response, without closing its
			// body. See Issue 10069.
			if err == ErrUseLastResponse {
    
    
				return resp, nil
			}
...
}

Check whether the Client's CheckRedirect has a value (function) in checkRedirect:

  • There is a custom checkRedirect function: calling the custom set callback;
  • No custom checkRedirect function: call the default built-in defaultCheckRedirect;
// net/http/client.go

// checkRedirect calls either the user's configured CheckRedirect
// function, or the default.
func (c *Client) checkRedirect(req *Request, via []*Request) error {
    
    
	fn := c.CheckRedirect
	if fn == nil {
    
    
		fn = defaultCheckRedirect
	}
	return fn(req, via)
}

The default built-in defaultCheckRedirect will check the number of redirects, up to 10 redirects.

// net/http/client.go

func defaultCheckRedirect(req *Request, via []*Request) error {
    
    
	if len(via) >= 10 {
    
    
		return errors.New("stopped after 10 redirects")
	}
	return nil
}

4.Reference

https://blog.csdn.net/feifeixiang2835/article/details/109300948

Guess you like

Origin blog.csdn.net/test1280/article/details/119383196