以 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,是所有人的好朋友...
回覆刪除