91做了一個有趣的Web API Help Page,可以自動產生Web API的方法清單並加註說明,還提供直接使用瀏覽器測試的功能。不過測著測著,遇上鬼打牆:

  • 同一台機器用IE/Firefox測試OK,Chrome測試傳回HTTP 502 Bad Gateway
  • 使用公司的Chrome測試OK
  • 家裡的Chrome測試Azure上的版本出現502,測試本機Server則沒問題
  • Chrome出錯只限於透過Test API發出的GET Request,直接在網址輸入同樣URL則完全沒問題

在線上遇到91,聊到這枚離奇茶包,格外引發我的好奇,骨子裡駭客魂蠢蠢欲動... 等到意會到自己在幹什麼時,眼前是兩個Chrome視窗,一個使用Test API功能傳回HTTP 502,一個則是在網址輸入Test API所GET的URL,回應正常... 使用Chrome Developer Tool,分別截錄下兩個Request的內容

以下是HTTP 502錯誤時的Request

GET /api/Values HTTP/1.1
Host: 91-webapi-sample.azurewebsites.net
Connection: keep-alive
Cache-Control: max-age=0
If-Modified-Since: Thu Jan 01 1970 08:00:00 GMT+0800 (台北標準時間)
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.152 Safari/537.22
Accept: */*
Referer: http://91-webapi-sample.azurewebsites.net/Help/Api/GET-api-Values
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
Accept-Charset: Big5,utf-8;q=0.7,*;q=0.3
Cookie: ARRAffinity=f5105030b49455c354a66ad349ab75b57b342f0a8b721d229021b4f0df2e5a18; WAWebSiteSID=99670baf00114d618cff2295d00d248a

以下是HTTP 200正常回應時的Request

GET /api/Values HTTP/1.1
Host: 91-webapi-sample.azurewebsites.net
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.152 Safari/537.22
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
Accept-Charset: Big5,utf-8;q=0.7,*;q=0.3
Cookie: ARRAffinity=f5105030b49455c354a66ad349ab75b57b342f0a8b721d229021b4f0df2e5a18; WAWebSiteSID=99670baf00114d618cff2295d00d248a

二者差異有限,結果迥異。至此已破案在望,只需比對二者,以消去法抓出關鍵差異即告真相大白。

雖然或許能找到可編輯Request Header的Chrome Addon進行實驗,但我選擇自己用Visual Studio寫支小程式來玩,一方面當作練功,二方面想確保100%擁有Request內容的控制權(其實,以上全是程式魔人為了想寫Code,千方百計編出來的藉口... orz),程式範例如下:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
 
namespace ChromeIssue
{
    class Program
    {
        static void Main(string[] args)
        {
            TcpClient client = new TcpClient();
            client.Connect("91-webapi-sample.azurewebsites.net", 80);
            string fixedChromeReqData = @"GET /api/Values HTTP/1.1
Host: 91-webapi-sample.azurewebsites.net
Connection: keep-alive
Cache-Control: max-age=0
If-Modified-Since: Thu Jan 01 1970 08:00:00 GMT+0800 (台北標準時間)
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.22...省略...
...省略...
Cookie: ARRAffinity=f5105030b4...省略...
 
";
            var stm = client.GetStream();
            byte[] buff = Encoding.UTF8.GetBytes(fixedChromeReqData);
            stm.Write(buff, 0, buff.Length);
            //為了簡化起見,不管Stream資料傳回時機,直接等兩秒後讀結果
            Thread.Sleep(2000);
            using (StreamReader sr = new StreamReader(stm))
            {
                byte[] result = new byte[65535];
                var len = stm.Read(result, 0, result.Length);
                Console.WriteLine(Encoding.UTF8.GetString(result, 0, len));
            }
            Console.Read();
        }
    }
}

為了100%控制HTTP Request內容,我選擇不用高階的WebClient物件,而是自己建TCP連線,對Stream進行讀寫。使用以上程式碼送出HTTP 502情境的Request Header,果真也得到HTTP 502錯誤,Bingo! 接下來的重點是一一拿掉可疑Request Header,若移掉某條Header就正常,人肯定是他殺的! 沒多久,我找出If-Modified-Since: Thu Jan 01 1970 08:00:00 GMT+0800 (台北標準時間)是造成HTTP 502的嫌犯。將Request簡化到只剩
GET /api/Values HTTP/1.1
Host: 91-webapi-sample.azurewebsites.net
If-Modified-Since: Thu Jan 01 1970 08:00:00 GMT+0800 (台北標準時間)

仍然會產生HTTP 502,一旦移掉字尾的"(台北標準時間)",HTTP 502錯誤就消失,罪證確鑿,凶手現身!

追蹤Test API程式碼,發現該Request Header來自WebApiTestClient.js(Web API Test Client package)
Line 168    httpRequest.setRequestHeader("If-Modified-Since", new Date(0));
進一步測試,new Date(0).toString()在中文版Windows的IE/Firefox會傳回"Thu Jan 1 08:00:00 UTC+0800 1970" ,而Chrome則傳回"Thu Jan 01 1970 08:00:00 GMT+0800 (台北標準時間)"。是的,Chrome雞婆地在後方加註(台北標準時間),讓Request Header裡多出了這段中文,導致美國端的Azure主機(應為英文版Windows)解讀失敗發生錯誤。一瞬間,所有離奇的鬼打牆現象都有了解答:

  • 同一台機器用IE/Firefox測試OK,Chrome測試傳回HTTP 502 Bad Gateway
    只有Chrome會在Date().toString()加"(台北標準時間)"
  • 使用公司的Chrome測試OK
    公司的Windows為英文版
  • 家裡的Chrome測試Azure上的版本出現502,測試本機網站則沒問題
    本機網站為中文版(也可能是本機Web Server版本與Azure不同),能順利解讀Header裡的中文
  • Chrome出錯只限於透過Test API發出的GET Request,直接在網址輸入同樣URL則完全沒問題
    問題Request Header是JavaScript額外加入的,直接輸入URL時不會出現 

前一刻滿天疑雲,下一秒忽然雲開見日,很有撞球一桿清台(Clean Table!)的暢快,射茶包迷人之處,莫過於此!


Comments

# by 91

應該要把「黑大一出手,茶包就沒有」十個大字,傳承於世 XD

# by Alex Lee

再度說明:魔鬼總在細節中

# by KKBruce

黑大一出手,茶包就沒有 <-- 連合刻個匾額,送黑大。

# by 貓咪圓滾滾

我的也有parameter error 可是看了好久91的測試程式後還是不會用 但這也不算悲哀 因為最悲慘的是我竟看不到polar bear了 它們不見了QQ (黑大極度震驚 : 妳沒看到上面高手對我的稱讚嗎?? 沒想到竟有人只想上來我的網站看北極熊!! 哈哈)

# by 91

回報一下,新版的WebApiTestClient package 已經拿掉那一行 header 的 javascript 程式碼,因此不會有這問題了。 (不過這茶包更大的價值是,在塞request header時,真的要小心 javascript & browser & 語系的問題) 報告完畢!

# by 貓咪圓滾滾

謝謝黑大!! T_T 果然不定時上黑大的部落格就能發現好棒的資訊(踢飛 哈哈)

Post a comment