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

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

客户端:

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实现的客户端,类似上图的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)。

在这里插入图片描述


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

猜你喜欢

转载自blog.csdn.net/test1280/article/details/119383196
今日推荐