以 requestb.in 解開 HTTP Request 之密
公司有個特別的 D 系統,我們需要透過 ASP.NET Web API 去存取它的 XML Web Service 來提供資源。這個 D 系統本身有個特別的限制,就是存取之前使用者需要先進行頁面 Login,然後才能存取 XML Web Service。Login 頁面很單純,就是一個帳號與密碼的組合,沒有其他特別驗證碼等保護。也就是使用者使用者輸入帳號密碼,而 ASP.NET Web API 透過使用者提供的帳號密碼透過程式方式進行登入,我們開發的 ASP.NET Web API 服務從一開始的 Beta 至 RC,這部分的程式碼都沒什麼問題,直到那令人鬼打牆的關鍵人物(暫稱他苦命的呆伯特好了)出現。
呆伯特一直說無法Login
簡單說明一下,此 ASP.NET Web API 服務主要使用者在美國,美國下班台灣上班的黃金交接點,我們私下請呆伯特(美國)幫忙測試此 ASP.NET Web API 服務的同事,但幾星期以來僅呆伯特都一直反應無法 Login 服務,由日誌看出來,他確實是登入失敗。但所有幫忙測試的人員只有呆伯特會出現此狀況。D 系統我們無任何權限,我們能做的也只是不斷調整 ASP.NET Web API 程式並不斷請呆伯特幫忙 Login 與測試。但每每得到"不行"時,心情都低落到不行(測到呆伯特都生氣了),不過最後得到一條重要資訊,呆伯特的密碼含有數個的特殊符號。
我們為加解密演算法補上特殊符號的測試程式碼,先確保特殊符號在加密與解密過程正常。其中小心\與"這兩個符號,在C#的字串中,需要使用\\與\"進行轉義。
以這條線索測試到最後終於有了曙光。
.NET WebRequest - Web Login 程式
public void useNetWebRequest(string url, string name, string pw) { CookieContainer cookies = new CookieContainer(); var request = (HttpWebRequest)WebRequest.Create(url); request.CookieContainer = cookies; request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; using (var requestStream = request.GetRequestStream()) using (var writer = new StreamWriter(requestStream)) { writer.Write("Username={0}&Password={1}", name, pw); } using (var responseStream = request.GetResponse().GetResponseStream()) using (var reader = new StreamReader(responseStream)) { var result = reader.ReadToEnd(); result.Dump(); } }
這是我用 LINQPad 覆寫的測試程式碼。它使用我的帳號密碼能正常進行登入,當我將密碼改為數個特殊符號時,確實會出錯。至此,才確定問題點(追到快哭了 >_<)。但 D 系統看得到碰不到,無法得知 D 系統在接收特殊符號到底發生了什麼事?
一整個低潮中...
requestb.in
http://requestb.in 是一個接收 HTTP 請求的服務,服務內容很簡單,就是把接收到的 HTTP 請求內容呈現出來,我通常使用在 ASP.NET Web API 去串接其他服務時,當你需要確認發出的 HTTP 請求內容是否正確時,那麼就可以使用 requestb.in 來查看。
這是 useNetWebRequest
打出去的結果,看完一整個抓頭皮呀,可以清楚看到特殊符號的傳遞並無問題。
看著上面的程式碼與結果圖,我苦思許多,那是一段使用 WebRequest 類別程式碼,WebRequest 是相當古早的用法,如果改用現在常用的 Framework 或 API 會是一樣的結果嗎?
http://restsharp.org/ - Web Login 程式
看得到的路都被擋死,只好自己開路。
public void useRestSharp(string url, string name, string pw) { CookieContainer cookies = new CookieContainer(); var client = new RestClient(url); client.CookieContainer = cookies; var request = new RestRequest("", Method.POST); request.AddHeader("content-type", "application/x-www-form-urlencoded"); request.AddParameter("Username", name); request.AddParameter("Password", pw); var response = client.Execute(request); var result = response.Content; result.Dump(); }
RestSharp 是我個人蠻喜歡使用的一套 REST 呼叫用 API,比起 ASP.NET Web API 時期重新包裝的 HttpClient,RestSharp 語義非常清楚、直接與簡潔(等一下有 HttpClient版,你可以看看)。
RestSarp 在 RAW BODY 有個明顯的差異,特殊符號都經過 Ascii 編碼,快快上傳到測試區,老天爺呀,呆伯特不用在呆下去了。
這程式碼解決了我幾週來看的困擾。原來是 WebRequest 版程式原汁原味的傳遞特殊符號,猜測,D 系統的 Login 無法正確處理未編碼符號所造成。
System.Net.Http.HttpClient - Web Login 程式
public void useHttpClient(string url, string name, string pw) { var cookie = new CookieContainer(); using (var handler = new HttpClientHandler() { CookieContainer = cookie }) using (var client = new HttpClient(handler) { BaseAddress = new Uri(url) }) { var content = new FormUrlEncodedContent(new[] { new KeyValuePair("Username", name), new KeyValuePair ("Password", pw) }); var result = client.PostAsync(url, content).Result; result.EnsureSuccessStatusCode(); var resultContent = result.Content.ReadAsStringAsync().Result; cookie.Dump(); resultContent.Dump(); } }
HttpClient 打出來的結果與 RestSharp 一致,符號都是編碼過的。
小結
新版的 RESTful Framework API 在處理符號這件事情上都有考慮周全。也順便透過這個案例為大家介紹一下 requestb.in 這個服務。最後小小的注意一下,requestb.in 服務如果一開始的 Private 未勾選,那麼它會是公開狀態,也就是任何人都能看到測試結果,請不要把正式資料打上去測試哦。或是可以參考他們另一個付費專案 https://www.runscope.com/
希望呆伯特不要以為我們在搞他,我們是很認真在抓蟲,還好蟲不在 D 系統,不然,就可以請拳四郎打我一拳。也感謝有呆伯特的出現,才能在上線測試前就找出這前細小差異造成的錯誤。
HttpUtility.ParseQueryString 產生的 NameValueCollection,是所有人的好朋友...
回覆刪除