【茶包射手日記】重複使用 WebClient 時注意 Headers 可能變動
2 | 4,344 |
今天踩到一個坑,發現 WebClient 有個我沒注意過的行為。
試著用 WebClient 呼叫 SharePoint 的 REST API,怎麼試都不成功。因為是第一次寫,優先想到的是我漏了某個必要參數或忽略關鍵步驟,而用錯誤訊息爬文,查到的案例幾乎都是未設定 Content-Type 所致,而我很確定有設定 WebClient.Headers.Add("Content-Type", "application/json;odata=verbose") 及 WebClient.Headers.Add("Accept", "application/json;odata=verbose"),而且是兩次呼叫第一次成功,第二次才噴出 <?xml version="1.0" encoding="utf-8"?><m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><m:code>-1,System.Collections.Generic.KeyNotFoundException</m:code><m:message xml:lang="en-US">The given key was not present in the dictionary.</m:message></m:error>
。
鬼打牆好一陣子,直到我改用 PowerShell Invoke-WebRequest,發現一模一樣的 POST 請求內容用 PowerShell 發送可以成功,趕緊用 Fiddler 側錄比較,這才鎖定問題。
試著用以下 ASPX 重現問題,ASPX 預設由 Query String 取參數,當 Content-Type 為 application/json 時,則改由 HTTP Body 讀取 JSON 解析取參數,同時有個自訂 Header X-ApiKey:
<%@Page Language="C#"%>
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
var cookie = Request.Headers["X-ApiKey"];
var x = Request.QueryString["x"];
var y = Request.QueryString["y"];
if (Request.ContentType.StartsWith("application/json"))
{
using (var sr = new System.IO.StreamReader(Request.InputStream)) {
var d = new System.Web.Script.Serialization.JavaScriptSerializer()
.Deserialize<Dictionary<string, string>>(sr.ReadToEnd());
x = d["x"];
y = d["y"];
}
}
Response.Write(string.Format("X-ApiKey = {0}, X = {1}, Y = {2}",
cookie, x, y));
}
</script>
客戶端如下:(實際案例發生在 .NET Framework 4.x,但用 .NET 6 測試也是相同結果)
var wc = new WebClient();
wc.UseDefaultCredentials = true;
wc.Headers.Add("Accept", "*/*");
wc.Headers.Add("Content-Type", "application/json");
wc.Headers.Add("X-ApiKey", "Secret");
var url = "http://localhost/aspnet/postjson/";
var res = wc.UploadString(url, @"{ ""x"": ""1"", ""y"": ""2"" }");
Console.WriteLine(res);
res = wc.UploadString(url, @"{ ""x"": ""3"", ""y"": ""4"" }");
Console.WriteLine(res);
測試第一次 deafult.aspx 有接到 JSON 傳入 x, y 及自訂 X-ApiKey Header,但第二次只有 X-ApiKey,沒有 x, y:
使用 Fiddler 比對兩次送的 HTTP Request 內容,可以發現第二次少了 Accept 及 Content-Type Header:
這可以解釋為什麼我第二次送出 SharePoint REST API 會得缺少 Content-Type 導致的錯誤訊息(不過,KeyNotFoundException 頗具誤導性),且傳回結果是 XML 而非 JSON (因為沒傳 Accept 指定接收 JSON)。
至於為什麼第二次送出時 Content-Type 跟 Accept Header 會消失,但 X-ApiKey 還在?微軟文件是這麼說的:
You should not assume that the header values will remain unchanged, because Web servers and caches may change or add headers to a Web request.
你不應該假設 Header 值會一直保持不變,網站伺服器及 Cache 可能改變或增加 Web Request 的 Header 項目。
結案。
WebClient header values may change after sending request, remeber to reset them if you want to reuse it.
Comments
# by JerryH
找到一篇文章,是不要用WebClient.UploadData method 就不會有問題? https://stackoverflow.com/questions/3865193/how-to-change-header-in-webclient/8472039
# by 小海
這個不錯,寫黑大