這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
在用golang做類比登入某個網站的功能時發現了一個問題:如何擷取該網站帶有重新導向資訊的Response?或者說我只需要
這個Response中的location資訊即可,但是發現對於go的標準庫net/http來說如果不hack這個庫是沒辦法直接擷取帶有重
定向資訊的Response,而且目前大多第三方的http庫也是基於標準庫net/http的,貌似就要改net/http庫或庫本身就提供了
對於重新導向的設定?
那麼我們就得先查看一下net/http庫的源碼了,通常我們都會用http.Client的Do方法發起一個請求,首先看一下它的源碼:
// Do sends an HTTP request and returns an HTTP response, following// policy (e.g. redirects, cookies, auth) as configured on the client.//// An error is returned if caused by client policy (such as// CheckRedirect), or if there was an HTTP protocol error.// A non-2xx response doesn't cause an error.//// When err is nil, resp always contains a non-nil resp.Body.//// Callers should close resp.Body when done reading from it. If// resp.Body is not closed, the Client's underlying RoundTripper// (typically Transport) may not be able to re-use a persistent TCP// connection to the server for a subsequent "keep-alive" request.//// The request Body, if non-nil, will be closed by the underlying// Transport, even on errors.//// Generally Get, Post, or PostForm will be used instead of Do.func (c *Client) Do(req *Request) (resp *Response, err error) { if req.Method == "GET" || req.Method == "HEAD" { return c.doFollowingRedirects(req, shouldRedirectGet) } if req.Method == "POST" || req.Method == "PUT" { return c.doFollowingRedirects(req, shouldRedirectPost) } return c.send(req)}
由源碼的上方的注釋可發現Do方法是不會返回有重新導向資訊的Response的,只會返回已經重新導向跳轉成功後的Response,而且會根據是否CheckRedirect返回error云云
那麼假設我們要發起的是一個Get請求,所以進入c.doFollowingRedirects()方法中去看看
func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bool) (resp *Response, err error) { var base *url.URL redirectChecker := c.CheckRedirect if redirectChecker == nil { redirectChecker = defaultCheckRedirect } var via []*Request if ireq.URL == nil { ireq.closeBody() return nil, errors.New("http: nil Request.URL") } var reqmu sync.Mutex // guards req req := ireq var timer *time.Timer if c.Timeout > 0 { type canceler interface { CancelRequest(*Request) } tr, ok := c.transport().(canceler) if !ok { return nil, fmt.Errorf("net/http: Client Transport of type %T doesn't support CancelRequest; Timeout not supported", c.transport()) } timer = time.AfterFunc(c.Timeout, func() { reqmu.Lock() defer reqmu.Unlock() tr.CancelRequest(req) }) } urlStr := "" // next relative or absolute URL to fetch (after first request) redirectFailed := false for redirect := 0; ; redirect++ { if redirect != 0 { nreq := new(Request) nreq.Method = ireq.Method if ireq.Method == "POST" || ireq.Method == "PUT" { nreq.Method = "GET" } nreq.Header = make(Header) nreq.URL, err = base.Parse(urlStr) if err != nil { break } if len(via) > 0 { // Add the Referer header. lastReq := via[len(via)-1] if ref := refererForURL(lastReq.URL, nreq.URL); ref != "" { nreq.Header.Set("Referer", ref) } err = redirectChecker(nreq, via) if err != nil { redirectFailed = true break } } reqmu.Lock() req = nreq reqmu.Unlock() } urlStr = req.URL.String() if resp, err = c.send(req); err != nil { break } if shouldRedirect(resp.StatusCode) { // Read the body if small so underlying TCP connection will be re-used. // No need to check for errors: if it fails, Transport won't reuse it anyway. const maxBodySlurpSize = 2 << 10 if resp.ContentLength == -1 || resp.ContentLength <= maxBodySlurpSize { io.CopyN(ioutil.Discard, resp.Body, maxBodySlurpSize) } resp.Body.Close() if urlStr = resp.Header.Get("Location"); urlStr == "" { err = errors.New(fmt.Sprintf("%d response missing Location header", resp.StatusCode)) break } base = req.URL via = append(via, req) continue } if timer != nil { resp.Body = &cancelTimerBody{timer, resp.Body} } return resp, nil } method := ireq.Method urlErr := &url .Error{ Op: method[0:1] + strings.ToLower(method[1:]), URL: urlStr, Err: err, } if redirectFailed { // Special case for Go 1 compatibility: return both the response // and an error if the CheckRedirect function failed. // See http://golang.org/issue/3795 return resp, urlErr } if resp != nil { resp.Body.Close() } return nil, urlErr}
在上述代碼中發現了有檢查是否進行重新導向的代碼:
redirectChecker := c.CheckRedirect if redirectChecker == nil { redirectChecker = defaultCheckRedirect }
redirectChecker是一個這樣的函數:func(req Request, via []Request) error
發現了可在client中設定自己的redirectChecker,就是只要實現了func(req Request, via []Request) error即可,其功能是由源碼的
defaultCheckRedirect可知是控制重新導向跳轉的次數而已。
func defaultCheckRedirect(req *Request, via []*Request) error { if len(via) >= 10 { return errors.New("stopped after 10 redirects") } return nil}
defaultCheckRedirect中規定重新導向跳轉不能超過10次,否則返回error,那麼如果我們要禁止跳轉重新導向的話自己實現一個
CheckRedirect,把其中的10改成0即可。但是即使這樣設定後也是不能返回有重新導向資訊的Response,而是返回一個跳轉停
止的error,不過如果你需要的只是帶有重新導向資訊Response中的location資訊的話只需要從返回的error中提取即可。http庫
client中的doFollowingRedirects中傳入了一個shouldRedirect函數,這個函數正是根據各種http協議的代碼返回是否進行跳
轉的資訊,所以如果不改動這部分代碼沒法直接擷取有重新導向資訊的Response,這樣看來http庫的設定不夠靈活,得自己改或
另外實現了。