.NET Core HttpClient调用腾讯云对象存储Web API的"ERROR_CGI_PARAM_NO_SUCH_OP"问题

2017-11-12 iraio

腾讯云提供的对象存储(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("rn--" + boundary + "rn");

我们基于 HttpClient 的实现用的是 MultipartFormDataContent :


var boundary = "---------------" + DateTime.Now.Ticks.ToString("x");
var data = new MultipartFormDataContent(boundary);

前者构建的 Multipart 数据包比后者多出了 rn (回车换行),而 0d0a 正是 rn 的 ASCII 码。根据 Multipart Content-Type 规范 ,这个多出来的 rn 是多余的,所以被解析为  "Preamble: 0d0a" 。

于是修改基于 HttpClient 的实现,也加上这个额外的 rn :


var ms = new MemoryStream();
var bytes = Encoding.UTF8.GetBytes("rn");
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"错误(实际上不加开头 rn 也没关系,问题与这个无关)。

继续仔细对比抓包,发现 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();

用户评论
开源开发学习小组列表