開門見山地說一下問題的緣由:調用 web api 時請求頭中多了雙引號,請求體中少了雙引號。html
騰訊雲提供的對象存儲(COS)C# SDK 是基於 .NET Framework 用 WebRequest 實現的,咱們直接將這個實現遷移到 .NET Core 是能夠正常調用,但後來咱們基於 HttpClient 實現,調用 web api 時老是返回 "ERROR_CGI_PARAM_NO_SUCH_OP" 錯誤。web
用 Wireshark 抓包後發現,基於 WebRequest 的實現的請求包開頭比基於 HttpClient 的實現多了個 "Preamble: 0d0a"。json
1)基於 WebRequest 的實現api
2)基於 HttpClient 的實現app
檢查代碼後發現,在構建 multipart/form-data 時,騰訊雲官方基於 WebRequest 的實現是這樣構建數據包的開頭的:url
var boundary = "---------------" + DateTime.Now.Ticks.ToString("x"); var beginBoundary = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
咱們基於 HttpClient 的實現用的是 MultipartFormDataContent :spa
var boundary = "---------------" + DateTime.Now.Ticks.ToString("x"); var data = new MultipartFormDataContent(boundary);
前者構建的 Multipart 數據包比後者多出了 \r\n (回車換行),而 0d0a 正是 \r\n 的 ASCII 碼。根據 Multipart Content-Type 規範,這個多出來的 \r\n 是多餘的,因此被解析爲 "Preamble: 0d0a" 。code
因而修改基於 HttpClient 的實現,也加上這個額外的 \r\n :orm
var ms = new MemoryStream(); var bytes = Encoding.UTF8.GetBytes("\r\n"); ms.Write(bytes, 0, bytes.Length); (await data.ReadAsStreamAsync()).CopyTo(ms); ms.Position = 0; var sc = new StreamContent(ms); sc.Headers.ContentType = data.Headers.ContentType; request.Content = sc;
但加上後依然是"ERROR_CGI_PARAM_NO_SUCH_OP"錯誤(實際上不加開頭的 \r\n 也不要緊,問題與這個無關)。htm
繼續仔細對比抓包,發現 HttpClient 的實現中 form-data 部署少了雙引號,好比 name=op ,基於 WebRequest 的實現用的是 name="op"
但加上後依舊是"ERROR_CGI_PARAM_NO_SUCH_OP"錯誤。
再繼續對比抓包,發現 HttpClient 的實現這 Content-Type 中比 WebRequest 的實現多了2個雙引號
1) Content-Type: multipart/form-data; boundary="---------------8d5289300ea3a0d"
2) Content-Type: multipart/form-data; boundary=---------------8d527aeed341201
去找這2個雙引號以後,問題終於解決了。
最終基於 .NET Core HttpClient 的實現代碼以下("Preamble: 0d0a"沒有影響,不須要加):
var request = new HttpRequestMessage(HttpMethod.Post, url); request.Headers.Authorization = new AuthenticationHeaderValue("Authorization", signature); var boundary = "---------------" + DateTime.Now.Ticks.ToString("x"); var data = new MultipartFormDataContent(boundary); data.Add(new ByteArrayContent(Encoding.UTF8.GetBytes("upload")), "\"op\""); var streamContent = new StreamContent(uploadStream); streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "\"fileContent\"", FileName = "\"" + fileName + "\"" }; streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); data.Add(streamContent); data.Headers.Remove("Content-Type"); data.Headers.Add("Content-Type", "multipart/form-data; boundary=" + boundary); request.Content = data; var response = await _httpClient.SendAsync(request); var json = await response.Content.ReadAsStringAsync();