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
{
staticvoid 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 = newbyte[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!)的暢快,射茶包迷人之處,莫過於此!