1. 坑位
最近在重構認證代碼,認證過程相當常規:
POST /open-api/v1/user-info?client_id&timstamp&rd=12345&sign=***&method=hmac
content-type: application/json
payload: { "token":"AA2917B0-C23D-40AB-A43A-4C4B61CC7C74"}
平台顯示 :簽名校驗失敗, 排查到平台收到的Post Payload並非預期,閱讀本文,解鎖正確使用Content-Type標頭的姿勢。
2. 步步為營
下面是構造HttpClient對象、發起請求的代碼:
// 初始化HttpClientFactory
context.Services.AddHttpClient("platform", c =>
{
c.BaseAddress = new Uri("https://alpha-engage.demohost.com/");
c.DefaultRequestHeaders.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
})...
// 產生命名HttpClient,發起請求
var client = _clientFactory.CreateClient("platform");
var response = await client.PostAsync($"open-api/v1/user-token/info?{req.AuthString()}",new StringContent(req.ReqPayload.ToString(),Encoding.UTF8) );
平台日志顯示,收到的請求payload:
{\"token\":\"AA2917B0-C23D-40AB-A43A-4C4B61CC7C74\"}
額,平台收到的JSON數據被轉碼了,沒有識別出JSON?
明眼人一看,HttpClient請求沒有設置Content-Type
,接收端沒有識別出payload是JSON,接收時進行了轉碼,生成了錯誤簽名。
① Content-Type是一個Entity Header,指示資源的media type ,可用在請求或者響應中。
② 以上代碼中new StringContent(req.ReqPayload.ToString(),Encoding.UTF8)指定了Encoding=UTF-8,卻沒有指定mediaType
,源碼默認值:text/plain
。
當我嘗試添加Content-Type
時(下面黃色背景行代碼):
context.Services.AddHttpClient("platform", c =>
{
c.BaseAddress = new Uri("https://alpha-engage.demohost.com/");
c.DefaultRequestHeaders.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));//ACCEPT header
c.DefaultRequestHeaders.Add("content-type", "application/json");
})
此時拋出以下異常:
InvalidOperationException: Misused header name. Make sure request headers are used with
HttpRequestMessage, response headers with HttpResponseMessage, and
content headers with HttpContent objects.
納尼,HttpContent Headers是啥?Chrome dev tools顯示只有兩種Header啊?
3. 爬坑
官方資料顯示: HTTP Headers被分為如下四類:
--- | 信息 | 舉例 | 對應.NET類型 |
---|---|---|---|
General Header | 可同時作用在請求/響應中,但是與傳輸數據無關 | Upgrade、Connection | --- |
Request Header | 將要獲取的資源或客戶端本身的信息 | Accept、Authorization | System.Net.Http.Headers.HttpRequestHeaders |
Response Header | 響應信息 | Location、ETag | System.Net.Http.Headers.HttpResponseHeaders |
Entity Header | 實體Body額外的信息 | Content-Length、Content-Type | System.Net.Http.Headers.HttpContentHeaders |
Content-Type
屬於Entity Header中的一種。
即便Entity Header既不是請求標頭也不是響應標頭,它們還是會包含在請求/響應標頭術語中(此說法來自官方)。
所以我們在Chrome DevTools沒有看到Entity Headers分組,卻常在請求/響應標頭中看到Content-Type標頭。
回到上面的異常,.NET 嚴格區分四種標頭,所以我上面
c.DefaultRequestHeaders.Add("content-type", "application/json");
嘗試在請求頭添加Content-Type標頭, 姿勢錯誤, .NET會提示InvalidOperationException異常。
4. 填坑
給這個常規的Post請求 設置正確的Content-Type,
① 對HttpRequestMessage
對象Content屬性添加Header
using (var request = new HttpRequestMessage())
{
request.Method = new HttpMethod(method);
request.RequestUri = new Uri(url);
request.Content = new StringContent(payload);
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await _httpClient.SendAsync(request);
return response;
}
使用HttpClient.SendAsync(request)
② 對StringContent重載構造函數傳參
StringContent
某個重載構造函數可直接傳參,設置media type
,
var response = await client.PostAsync($"open-api/v1/user-token/info?{req.AuthString()}",new StringContent(req.ReqPayload.ToString(),Encoding.UTF8,"application/json") );
干貨旁白
- 小編對於Http協議有知識漏洞,搬磚時一直關注Chrome DevTools,忽略了還有Entity Header一說。
- Content-Type 這個實體標頭,會出現了請求/響應標頭,指示資源的媒體類型。
- .NTE針對4種HTTP Header強化了區別,在實際開發中要靈活使用。