环境
go1.13.5
分析
平时我们通过标准库net/http
发起http
请求后如果遇到重定向后会继续发起跳转请求得到最后的响应返回给我们。
但是当我们需要获取重定向的相关信息的时候怎么办呢?
我们是通过Client.Do
方法发起的请求,那我们可以从这里入手。打开源码可以看到如下注释:
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.
可以看到在遇到重定向的时候会先通过CheckRedirect
方法来检测是否要跳转。
直接看源码for
里面流程,获取到响应后,首先会通过c.checkRedirect
检测是否可以跳转,如果返回了一个非nil
的error
则结束请求,否者继续执行后面的逻辑。
注意下面有个err
是否为ErrUseLastResponse
的判断,我们稍后再说。
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
}
// ....省略
if err != nil {
// Special case for Go 1 compatibility: return both the response
// and an error if the CheckRedirect function failed.
// See https://golang.org/issue/3795
// The resp.Body has already been closed.
ue := uerr(err)
ue.(*url.Error).URL = loc
return resp, ue
}
来到redirectBehavior
方法来觉得遇到3XX
状态码的相关处理,它会返回的shouldRedirect
将最后决定是否需要跳转。
var shouldRedirect bool
redirectMethod, shouldRedirect, includeBody = redirectBehavior(req.Method, resp, reqs[0])
if !shouldRedirect {
return resp, nil
}
通过追踪c.checkRedirect
发现它会获取c.CheckRedirect
,如果c.CheckRedirect
是nil
,则调用默认的defaultCheckRedirect
来控制跳转次数。
// 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)
}
func defaultCheckRedirect(req *Request, via []*Request) error {
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
return nil
}
再来看看c.CheckRedirect
:
// 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
通过注释可以看出CheckRedirect
是用来控制跳转的,而当它返回一个ErrUseLastResponse
的error
就会返回最近一次响应体。这里和上面c.checkRedirect
下面的判断相呼应,当接受到ErrUseLastResponse
是就退出了循环体。这下就明白了,只要我们给CheckRedirect
传入自定义的func
并返回ErrUseLastResponse
类型的error
就可以我们自己控制跳转了。
结果
发起请求时给http.Client
的CheckRedirect
传入返回http.ErrUseLastResponse
的func
即可。
例:
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
c := http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Timeout: 30 * time.Second,
}
resp, err := c.Do(req)