Go HTTP Redirect的知識點總結

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

HTTP 規範中定義了返回碼為 3xx 代表用戶端需要做一些額外的工作來完成請求,大部分3xx用來做轉寄(redirect)。

狀態代碼的詳細說明可以參照規範或者 wikipedia、維基百科, 以下是代碼的簡短介紹。

  • 300 Multiple Choices: 返回多個可供選擇的資源
  • 301 Moved Permanently: 請求的資源已永久移動到新位置,並且將來任何對此資源的引用都應該使用本響應返回的若干個URI之一
  • 302 Found: 請求的資源現在臨時從不同的URI響應請求。由於這樣的重新導向是臨時的,用戶端應當繼續向原有地址發送以後的請求,HTTP 1.0中的意義是Moved Temporarily,但是很多瀏覽器的實現是按照303的處實現的,所以HTTP 1.1中增加了 303和307的狀態代碼來區分不同的行為
  • 303 See Other (since HTTP/1.1): 對應當前請求的響應可以在另一個URI上被找到,而且用戶端應當採用GET的方式訪問那個資源
  • 304 Not Modified (RFC 7232): 請求的資源沒有改變
  • 305 Use Proxy (since HTTP/1.1): 被請求的資源必須通過指定的代理才能被訪問
  • 306 Switch Proxy: 在最新版的規範中,306狀態代碼已經不再被使用
  • 307 Temporary Redirect (since HTTP/1.1): 請求的資源現在臨時從不同的URI響應請求,和303不同,它還是使用原先的Method
  • 308 Permanent Redirect (RFC 7538): 請求的資源已永久移動到新位置,並且新請求的Method不能改變

Go 的 http 庫在實現的過程中也在不斷的完成和修改其中的Bug,在 1.8版本中解決了前面版本中實現的問題 (你可以在 Go issues中搜尋 redirect 來查看相關的issue)。 本文梳理了 Go 中 Redirect 的相關知識,以便你在遇到轉寄的問題時心中有數。

轉寄策略和預設轉寄次數。

http.Client包含一個CheckRedirect欄位,用來定義轉寄的策略,如果你沒有設定,則預設使用defaultCheckRedirect:

1234567
func (c *Client) checkRedirect(req *Request, via []*Request) error {fn := c.CheckRedirectif fn == nil {fn = defaultCheckRedirect}return fn(req, via)}

這個函數會在執行轉寄之前被調用,可以看到這個函數如果返回err,則不再進行轉寄了。
這個函數的第一個參數req是即將轉寄使用的request,第二個參數 via已經請求的requests。

12345678910111213141516171819202122
for {    ……    //需要轉寄    {        ……        err = c.checkRedirect(req, reqs)if err == ErrUseLastResponse {return resp, nil}//discard previous response        ……if err != nil {ue := uerr(err)ue.(*url.Error).URL = locreturn resp, ue}    }    ……}

預設的轉寄策略是最多10次轉寄, 避免轉寄次數過高或者死迴圈。

123456
func defaultCheckRedirect(req *Request, via []*Request) error {if len(via) >= 10 {return errors.New("stopped after 10 redirects")}return nil}

如果你要實現不同的轉寄策略,你需要定義自己的CheckRedirect

轉寄安全

1)當轉寄的request中包含安全的資訊Header時, 比如AuthorizationWWW-AuthenticateCookie等Header,如果是跨域,則這些頭部不會被複製到新的請求中。

123456789
func shouldCopyHeaderOnRedirect(headerKey string, initial, dest *url.URL) bool {switch CanonicalHeaderKey(headerKey) {case "Authorization", "Www-Authenticate", "Cookie", "Cookie2":ihost := strings.ToLower(initial.Host)dhost := strings.ToLower(dest.Host)return isDomainOrSubdomain(dhost, ihost)}return true}

2)如果設定了一個非空的 cookie Jar, 轉寄響應會修改 cookie jar中的值,但是下一次轉寄的時候, Cookieheader會被處理,忽略那些變動的cookie.

when forwarding the "Cookie" header with a non-nil cookie Jar.
Since each redirect may mutate the state of the cookie jar,
a redirect may possibly alter a cookie set in the initial request.
When forwarding the "Cookie" header, any mutated cookies will be omitted,
with the expectation that the Jar will insert those mutated cookies
with the updated values (assuming the origin matches).
If Jar is nil, the initial cookies are forwarded without change.

具體的你可以查看 makeHeadersCopier的實現。
可以看到每次redirect會刪除上次的Redirect造成的變動,再恢複原始的請求的Coookie。

1234567891011121314151617181920212223242526272829
  if c.Jar != nil && icookies != nil {var changed boolresp := req.Response // The response that caused the upcoming redirectfor _, c := range resp.Cookies() {if _, ok := icookies[c.Name]; ok {delete(icookies, c.Name)changed = true}}if changed {ireqhdr.Del("Cookie")var ss []stringfor _, cs := range icookies {for _, c := range cs {ss = append(ss, c.Name+"="+c.Value)}}sort.Strings(ss) // Ensure deterministic headersireqhdr.Set("Cookie", strings.Join(ss, "; "))}}// Copy the initial request's Header values// (at least the safe ones).for k, vv := range ireqhdr {if shouldCopyHeaderOnRedirect(k, preq.URL, req.URL) {req.Header[k] = vv}}

轉寄規則

當伺服器返回一個轉寄response的時候, client首先使用CheckRedirect函數檢查是否要進行轉寄。

預設會處理下列請求:

  • 301 (Moved Permanently)
  • 302 (Found)
  • 303 (See Other)
  • 307 (Temporary Redirect)
  • 308 (Permanent Redirect)

如果需要轉寄, 對於301302303的狀態代碼, 接下來轉寄的請求會將請求Method轉換成GET method (如果原始請求Method是HEAD則不變,還是HEAD), 而且body為空白, 儘管原始的請求可能包含body。 對於307308狀態代碼,接下來轉寄的請求的Method沒有變化,和原始的請求保持一致, 並且還是使用原始的body內容來發送轉寄請求。

代碼處理的邏輯是由redirectBehavior函數實現的。

1234567891011121314151617181920212223242526
func redirectBehavior(reqMethod string, resp *Response, ireq *Request) (redirectMethod string, shouldRedirect, includeBody bool) {switch resp.StatusCode {case 301, 302, 303:redirectMethod = reqMethodshouldRedirect = trueincludeBody = falseif reqMethod != "GET" && reqMethod != "HEAD" {redirectMethod = "GET"}case 307, 308:redirectMethod = reqMethodshouldRedirect = trueincludeBody = trueif resp.Header.Get("Location") == "" {shouldRedirect = falsebreak}if ireq.GetBody == nil && ireq.outgoingLength() != 0 {shouldRedirect = false}}return redirectMethod, shouldRedirect, includeBody}

以上是介紹的http Redirect相關的內容,它們主要是用戶端的代碼邏輯。 下面兩節介紹一下與http redirect有一點點關係的內容。

RedirectHandler

http定義了一個便利類型: RedirectHandler, 它是一個預定義的http handler,可以將指定的請求轉寄到指定的url中, 主要就是使用設定的 url 和 status code 設定 response 的 Location。

它用作伺服器端的開發中。

1
func RedirectHandler(url string, code int) Handler

注意 code應該是 3xx的狀態代碼, 通常是StatusMovedPermanently, StatusFound 或者 StatusSeeOther

RoundTripper

RoundTripper也用作用戶端的開發。

RoundTripper代表一次http 的請求。 當使用redirect的時候,你可能redirect多次,也就是執行了n次的http請求。

123
type RoundTripper interface {        RoundTrip(*Request) (*Response, error)}

DefaultTransport是預設RoundTripper的實現。它建立網路連接,並且會緩衝以便後續的請求重用。它會使用$HTTP_PROXY$NO_PROXY (或者 $http_proxy, $no_proxy)環境變數來設定代理.

其它的RoundTripper的實現還有NewFileTransport建立的RoundTripper,用來服務檔案系統,將檔案系統映射成http的處理方式。

123456789101112
var DefaultTransport RoundTripper = &Transport{        Proxy: ProxyFromEnvironment,        DialContext: (&net.Dialer{                Timeout:   30 * time.Second,                KeepAlive: 30 * time.Second,                DualStack: true,        }).DialContext,        MaxIdleConns:          100,        IdleConnTimeout:       90 * time.Second,        TLSHandshakeTimeout:   10 * time.Second,        ExpectContinueTimeout: 1 * time.Second,}

Redirect的時候,預設轉寄是http庫自動幫你實現的,如果你想對於轉寄過程做深入的跟蹤的話(簡單跟蹤可以使用httptrace),你可以自訂一個RoundTripper,比如下面的這個:

http://stackoverflow.com/questions/24577494/how-to-get-the-http-redirect-status-codes-in-golang
12345678910111213141516171819
type LogRedirects struct {    Transport http.RoundTripper}func (l LogRedirects) RoundTrip(req *http.Request) (resp *http.Response, err error) {    t := l.Transport    if t == nil {        t = http.DefaultTransport    }    resp, err = t.RoundTrip(req)    if err != nil {        return    }    switch resp.StatusCode {    case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect:        log.Println("Request for", req.URL, "redirected with status", resp.StatusCode)    }    return}

建立Client的時候可以使用下面的代碼:

1
client := &http.Client{Transport: LogRedirects{}}

這樣每次轉寄過程我們都可以追蹤。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.