Go:客户端禁止重定向

Go:客户端禁止重定向


1.现象

服务端代码:

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

服务端代码说明:

  1. 服务端监听8080端口;
  2. 服务端提供两个接口:/redirect、/do,访问/redirect将重定向(301)到/do。

服务端代码测试:

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

Go:客户端禁止重定向

Go:客户端禁止重定向

Go:客户端禁止重定向

客户端:

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

客户端代码说明:

  1. 客户端访问8080端口/redirect,服务端返回301,重定向/do,客户端自动重定向2;
  2. 客户端访问8080端口/do,服务端返回200,应答正文do;

客户端代码测试:

Go:客户端禁止重定向

可以看到,go实现的客户端,类似上图的Browser(Chrome),可以自动处理重定向,返回最终的,重定向之后的结果。


2.禁止Go客户端自动重定向

话不多说,先上代码:

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

即,自己创建http.Client,设置CheckRedirect字段,指向自定义函数,返回ErrUseLastResponse。

测试:Go客户端已禁止重定向,返回访问/redirect的真实的应答(301)。

Go:客户端禁止重定向


3.解释

参考http.Client.Do:Client收到redirect时,其CheckRedirect决定其行为(是否follow)。

// 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)
}

参考CheckRedirect说明:

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

注意:

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

如果CheckRedirect返回ErrUseLastResponse错误,则最近一次的响应(例如301响应)将被返回(以及nil的错误值—指代无错误)

具体实现处:

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

在checkRedirect中检查Client的CheckRedirect是否有值(函数):

  • 有 自定义checkRedirect函数:调用自定义设置的callback;
  • 无 自定义checkRedirect函数:调用默认的内置的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)
}

默认内置的defaultCheckRedirect,将检查已重定向的次数,最多重定向10次。

// 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.参考

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

上一篇:php eclipse调试


下一篇:页面刷新(vue)