Transfer-Encoding和Content-Length
目录
简介
我开发 pusher 的时候对接了 Qmsg酱, 中间遇到了一些问题, 是关于 Transfer-Encoding
和 Content-Length
这两个 Header.
问题处理
我通过 HttpClient 发送请求的代码如下
var httpResponseMessage = await httpClient.PostAsJsonAsync(url,data)
报错信息
System.Net.Http.HttpRequestException: An error occurred while sending the request.
---> System.Net.Http.HttpI0Exception: The response ended prematurely. (ResponseEnded)
at System.Net.Http.HttpConnection.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
...
通过网络搜索发现了 这个文章转发 和 stack overflow问题, 手动组装了一个 StringContent
, 使用 SendAsync
正常发送了请求.
原因分析
这里用到了我的 TestServer 服务, 地址是 test.kentxxq.com 访问 test.kentxxq.com/request
会返回你的请求信息.
下面来对比 PostAsJsonAsync
和 SendAsync
这两种方式发出的 web 请求有什么区别.
// SendAsync
var httpResponseMessage = await httpClient.PostAsJsonAsync(url,data)
// SendAsync
using var req = new HttpRequestMessage(HttpMethod.Post, url);
req.Content = new StringContent(JsonSerializer.Serialize(data), Encoding.UTF8, MediaTypeNames.Application.Json);
var httpResponseMessage = await httpClient.SendAsync(req);
PostAsJsonAsync
和 SendAsync
请求的区别在于 Header
{
"Headers":{
"Transfer-Encoding": "chunked" // PostAsJsonAsync
"Content-Length": "45" // SendAsync
},
...
}
Content-Length
代表传递的body/form/文件
的大小. 否则服务器不知道你传输的数据是否结束.- 代表客户端不知道数据的具体大小, 采用分片传输. 假设你在传输一个 10 g 的文件, 你可以直接开始 chunked 传输, 而不需要提前计算这个文件的大小, 从而加快了传输速度
- 也就是说除了没有消息体的请求 (例如 get ,head 请求), 必须在
Content-Length
或Transfer-Encoding
里2 选 1, 否则服务器可能无法正确响应
当我使用 curl 或 postman 等工具的时候, 会带上 Content-Length
. 结果会和 SendAsync
一样可以正常工作, 所以问题出在了 Transfer-Encoding
通过搜索找到了 Transfer-Encoding - HTTP | MDN 文档, 在第一行就写着 http2
禁用除了 trailers
以外的所有 Transfer-Encoding
标头. 而 Qmsg 恰好就使用了 http2
.
httpClient 的正确姿势
// 1. 先创建HttpRequestMessage
using var req = new HttpRequestMessage(HttpMethod.Post, url);
// 2 设置content,这会影响到Content-Length
// 采用 JsonContent, 即使设置req.Headers.TransferEncodingChunked = false,因为没有Content-Length, 导致TransferEncodingChunked也没有生效
// 根据这个issues https://github.com/dotnet/runtime/issues/30283 ,可以通过LoadIntoBufferAsync把内容加载到memory中,这样JsonContent也能使用Content-Length发送
// req.Content = JsonContent.Create(data);
// await req.Content.LoadIntoBufferAsync();
// 2 设置content,这会影响到Content-Length
// 采用 StringContent. 手动序列化成string, 就会带上 Content-Length
req.Content = new StringContent(JsonSerializer.Serialize(data), Encoding.UTF8, MediaTypeNames.Application.Json);
// 3 发送请求,拿到结果
var httpResponseMessage = await httpClient.SendAsync(req);