這個問題應該確確實實是一個Chrome的BUG,我在自己的編程環境中發現,並在多個服務器,多個編程語言的運行環境,以及多個瀏覽器下都測試過,都看到有2次請求出現。為了證明不是自己環境的問題,我也特意去找了一些其他站點,用它里面的一些會產生重定向的請求來測試。比如這個請求地址http://wenda.golaravel.com/account/openid/qq_login/,這是golaravel問答模塊進行qq登錄的鏈接地址,它會重定向到qq授權登錄的頁面。
使用fiddler監控wenda.golaravel.com這個站點,然后在Chrome里面訪問以上地址,回到fiddler查看監控結果:
從這個結果可以明確地看到,Chrome對這個地址發出了2次http請求,並且這兩次http請求都從服務器拿到了Response。從fiddler的Statistics欄里面,可以對比看出這兩個請求過程的一些信息:
第一個是:
第二個是:
從這個對比結果可以看到,這兩個請求並不是一種串行的關系,而是並行的關系。但是這個問題的特性還不止這么簡單,目前我發現的一些現象如下:
1)不是每次都會出現這個情況,我只能說大部分測試操作都出現這種情況,但是少部分情況是正常的:訪問302的地址,對原地址只發起了一次請求,第二次是對302 Location指定的地址的請求,這是正常的;
2)這兩個請求並不是每次都能成功從服務器拿到響應,有的時候其中的一個會報500的錯誤,或者是顯示failed,此時如果觀察fiddler的log記錄,會看到有下面的一些關鍵信息:
Session #143 raised exception System.Net.Sockets.SocketException //or This request was retried after a Receive operation failed.
3)大部分情況下,都是這兩個請求先發起,然后再對302 Location重定向請求;但是極少的情況會遇到302重定向請求夾在這兩個請求中間。
目前我並沒有找到這個問題產生的原因,網上相關的資料太少了,所以我只能從多個方面來驗證是否是Chrome本身的問題,最后得出的結論也跟我的猜想一致,這就是Chrome較新版本的一個BUG,我在當前時間點的最新版本的Chrome瀏覽器里面看到這個現象的:
下面我會詳細地證明它是否為Chrome的一個BUG。
1. 問題的發現
我使用laravel框架寫了一個很簡單的程序,就幾個頁面,做微信登錄的demo用的,所以頁面里的微信登錄按鈕,實際上就是一個需要重定向到微信授權頁面的地址。我在瀏覽器里面訪問這個地址的時候,在本地windows下的apache服務器的access.log里面,發現這一個訪問操作產生了三次http請求:
從這個日志里很明顯地看到有2次對/login/weixin的請求,這個就是重定向請求的原請求記錄。發現這個問題之后,我並不認為是服務器或者瀏覽器的問題,而是首先懷疑是否是自己代碼里面有重定向loop的情況。所以第一步就去仔細地檢查自己的代碼:
/** * 微信登錄 * @param Request $request * @return mixed */ public function weixin(Request $request) { $q_str = $request->getQueryString(); return Socialite::with('weixin') //設置授權的回調地址,通過命名路由的形式 ->setRedirectUrl(route('notify_weixin') . ($q_str ? '?' . $q_str : '')) ->redirect(); }
最后的redirect方法是:
/** * 重定向並將state參數寫到cookie里面去,而不是采用session * @return string */ public function redirect() { $state = $this->getState(); $response = new RedirectResponse($this->getAuthUrl($state), 302, [ 'Set-Cookie' => implode('', [ $this->state_cookie_name, '=', $this->getEncryptState($state), "; path=/; domain=", $this->getDomain(), "; expires=" . gmstrftime("%A, %d-%b-%Y %H:%M:%S GMT", time() + $this->state_cookie_time), "; Max-Age=" . $this->state_cookie_time, "; httponly" ]) ]); return $response; }
這個代碼里的RedirectResponse是laravel框架提供的類。從這個代碼,我並沒有看出什么問題,因為沒有多次創建Response的處理。所以開始懷疑是否是新版的laravel框架的問題,因為我用的是最新的laravel框架。所以我又用公司的php環境測試了一下,公司的php環境用的yii這個php框架,結果發現,公司的環境在本地依然存在這個問題。
為了排除是框架的問題,我又自己寫了兩個最簡單的php頁面:
我把demo1部署到demo1.com,demo2部署到demo2.com,然后訪問demo1.com來測試。最后也還是遇到了這個問題。在這一次測試里面,我還發現了有兩個關鍵點:
1)如果把302改成301重定向,那么chrome就不會產生兩次請求。但是實際上301用在這里是不對的,因為它的含義是原地址的資源已經永久轉移到別的位置了。
2)這個問題導致的兩次對原地址的請求,大部分情況下,服務器都能收到並進行處理。(demo1里面的打印信息,在這個問題出現的時候,每次都打印兩個時間信息,說明服務器響應了2次)。這意味着用戶的一次訪問操作,瀏覽器發出了2個請求,服務器對同一個用戶操作進行了2次處理。這並不是個沒有影響的事,比如當這個請求對某個資源的狀態做了一些持續性的改變時,如數據累計,那就意味着用戶一次操作,就會累計2次,這樣這些數據結果可能就有問題了。這也是我把這個問題詳細記錄說明的主要原因,我覺得開發中應該注意到這個問題的存在,以便在排除一些疑難的數據問題的時候,可以思考到這個層面上來。
到此為止,基本上已經排除是php框架的問題了。接下來考慮的問題產生對象主要是編程語言,服務器以及瀏覽器。
2. 排查是否為編程語言的問題
為了驗證是否為php語言本身的問題,我又用express框架寫了以下簡單代碼,並運行在node的環境里面來測試:
app.get('/', function (req, res) { res.redirect('/redirect'); }); app.get('/redirect', function (req, res) { res.send('success'); });
最后測試發現這個問題,在Nodejs里面同樣存在。所以也可以排除是php語言的問題了。
3. 排查是否為服務器的問題
因為這個問題本身是在本地windows服務器里面發現的,所以我懷疑是否為本機apache的問題,所以我又把相關的代碼部署到遠程的nginx服務器上,最后同樣測試到這個問題的存在。
加上上一步在nodejs里面測試的時候,實際上是用本機的node服務器運行的,綜合這兩個服務器測試結果,也能證明並不是apache服務器的問題。
4. 排查是否為瀏覽器的問題
我的機子上有360瀏覽器,firefox,IE11,Edge瀏覽器和最新的Chrome瀏覽器的。最后測試發現只有Chrome瀏覽器里面有這個問題,其它瀏覽器測試,在fiddler里面都只能看到對原地址僅發起了一次http請求,在每個瀏覽器我都重復做了大概十多次訪問操作才得出這個結論。為了進一步驗證Chrome的版本問題,我又特意刪了當前版本,下載了一個Chrome46的瀏覽器測試,結果沒有發現這個問題。到此為止,也就基本上可以認為是Chrome瀏覽器的問題。
5. 進一步排查是否為本機運行環境的問題
為了進一步排查是自己開發環境的問題,我專門到網上找了一些其他站點的302請求地址做測試。除了本文開頭提供的地址,下面這個地址也可以測試:
最后,根據以上的排查內容,可以認定這個問題是Chrome的一個BUG,我已經report給他們了,回不回復不重要,最重要的是下一個版本這個問題是否能夠解決。所以接下來這個問題,我的處理方式是:跟進chrome的版本更新情況,並在新版本中進行測試。希望它能在后續的版本中得到解決。
其實在以上排查過程中,還有一些情況值的考慮,比如操作系統環境,網絡環境的影響,畢竟http請求本身處於這兩個大的因素之下,所以也不能完全排除它們的原因。所以,要是感興趣的朋友,覺得這個問題值得研究的話,非常希望你能把自己的測試結果反饋過來,如果這個問題在你的機器上也存在,那么就能增加本文的客觀性和正確性,就能幫助更多的人;如果很多人都沒有測試出這個問題,就說明本文的結論有誤,這篇文章應該作廢才行。