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)
}
服务端代码说明:
- 服务端监听8080端口;
- 服务端提供两个接口:/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))
}
客户端代码说明:
- 客户端访问8080端口/redirect,服务端返回301,重定向/do,客户端自动重定向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