文章出處

開門見山地說一下問題的原因:調用 web api 時請求頭中多了雙引號,請求體中少了雙引號。

騰訊云提供的對象存儲(COS)C# SDK 是基于 .NET Framework 用 WebRequest 實現的,我們直接將這個實現遷移到 .NET Core 是可以正常調用,但后來我們基于 HttpClient 實現,調用 web api 時總是返回 "ERROR_CGI_PARAM_NO_SUCH_OP" 錯誤。

用 Wireshark 抓包后發現,基于 WebRequest 的實現的請求包開頭比基于 HttpClient 的實現多了個 "Preamble: 0d0a"。

1)基于 WebRequest 的實現

2)基于 HttpClient 的實現

檢查代碼后發現,在構建 multipart/form-data 時,騰訊云官方基于 WebRequest 的實現是這樣構建數據包的開頭的:

var boundary = "---------------" + DateTime.Now.Ticks.ToString("x");
var beginBoundary = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");

我們基于 HttpClient 的實現用的是 MultipartFormDataContent :

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" 。

于是修改基于 HttpClient 的實現,也加上這個額外的 \r\n :

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 也沒關系,問題與這個無關)。

繼續仔細對比抓包,發現 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();

文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()