不久前用go寫了個http client,去模擬某網站(*.com)的登錄操作。網站的登錄邏輯:1.驗證登錄賬號和密碼;2.下發token。此token通過cookie下發;3.redirect到主頁(/)。主頁對token進行校驗,渲染、展示頁面信息。簡單調用http.DefaultClient.Do(request),並未獲取到預期的response,問題究竟出在了什么地方。
net/http.Client 默認自動處理redirect

我們跟蹤一下庫代碼,看一下詳細的邏輯。通過函數名,為我們獲得一個初步印象:對於“GET”、“HEAD”、“POST”、“PUT”請求,將進行自動重定向。至於“GET”和“HEAD”對於哪些status code進行重定向、“POST”和“PUT”方法又響應哪些status code的重定向呢?關鍵就在於doFollowingRedirects的第二個入參:shouldRedirectGet和shouldRedirectPost函數。

將case中的常量翻譯一下,就是“HEAD”和“GET”方法,響應301、302、303和307;“POST”和“PUT”方法響應302和303。意味着redirect默認是自發執行的,為何登錄跳轉失敗呢?問題出在了cookie上。默認重定向時,並不會保存上次response的cookie,自然不會攜帶相應的cookie去發起redirect后的請求。
redirect 攜帶cookie在http.Client中有一個Jar字段,用於存儲(內存)cookies。

注釋寫得很清楚,如果此字段為空,client發起請求時,將不攜帶cookies且response返回的cookies將被忽略。CookieJar的定義位於net/http的jar.go中,是一個interface。

所謂CookieJar,直白點,就是一個cookie的存儲,很自然提供兩個方法:SetCookies和Cookies,分別用於set和get操作。net/http/cookiejar中給出了一個CookieJar的實現。

顯然Jar中真正用來存儲cookies的就是entries這一成員變量了,為一個雙map結構。map的第一個key為域名后綴,按照pList(PublicSuffixList)指定的規則來獲取;第二個key為cookie name、domain和path拼接而成的一個string。pList可以為空。
無須繼續往下,我們已經知曉只要為client設置Jar字段,即可實現redirect的時候攜帶相應cookie的目的。

自此,問題得以解答。后面再羅嗦一點相關的東西。
jar 的 cookie 匹配規則
實例代碼中,通過cookiejar.New(nil)初始化一個cookie jar,即Jar.pList為nil,使用默認的cookie匹配規則。Jar數據結構的介紹中,說明了pList將影響entries map的第一個key,具體的實現為jarKey這個函數。


PublicSuffixList是一個interface,當Jar中為指定此字段時,將采用默認的匹配規則:""作為key(假如域名為,key則為);否則將使用PublicSuffixList的實現類的規則來挑選key,中提供了一種實現,這里不再深究,感興趣的同學,自行前去觀看。
禁止 redirect如果不希望client代替我們做redirect,有什么辦法呢?Client數據結構中設計了一個CheckRedirect的字段,此字段是一個函數引用。

從注釋中,我們很容易獲知禁止redirect的解決方法:CheckRedirect函數簡單返回一個http.ErrUseLastResponse即可。這個埋點(CheckRedirect調用)發生在什么位置呢?調用鏈:http.Do -> c.doFollowingRedirect -> c.checkRedirect。

checkRedirect位於一個for循環中,循環次數由后端redirect的次數和最大允許的遞歸重試(默認10)次數共同決定。顯然,當checkRedirect返回一個http.ErrUseLastResponse的錯誤后,redirect的循環正常退出。
如何禁止redirect回答完畢。checkRedirect函數,淺顯易懂,就不再多介紹。貼出源代碼,大家一起再來感受和膜拜一下庫作者令人拍案叫絕的設計能力和注釋的書寫能力!

