小心 HttpClient 中的 FormUrlEncodeContent 的 bug
小心 HttpClient 中的 FormUrlEncodeContent 的 bug
Intro
最近发现活动室预约项目里的上传图片有时候会有问题,周末找时间测试了一下,发现小图片的上传没问题,大图片上传会有问题,而且异常信息还很奇怪,System.UriFormatException: Invalid URI: The Uri string is too long 看这个错误的信息还以为是请求的 url 过长导致的,但是实际请求的 url 很短,诡异的异常信息
测试示例
为了方便大家了解和测试这个bug,我在 Github 上提供了一个示例 https://github.com/WeihanLi/SamplesInPractice/blob/master/HttpClientTest/FormUrlEncodeContentTest.cs
HttpClient 示例代码:
public class FormUrlEncodeContentTest
{
private const string TestUrl = "https://cnblogs.com";
public static async Task FormUrlEncodedContentLengthTest()
{
using (var httpClient = new HttpClient(new NoProxyHttpClientHandler()))
{
using (var response = await httpClient.PostAsync(TestUrl, new FormUrlEncodedContent(new Dictionary<string, string>()
{
{"bigContent", new string('a', 65535)},
})))
{
Console.WriteLine($"response status code:{response.StatusCode}");
}
}
}
public static async Task ByteArrayContentLengthTest()
{
using (var httpClient = new HttpClient(new NoProxyHttpClientHandler()))
{
var postContent = $"bigContent={new string('a', 65535)}";
using (var response = await httpClient.PostAsync(TestUrl, new ByteArrayContent(postContent.GetBytes())))
{
Console.WriteLine($"response status code:{response.StatusCode}");
}
}
}
public static async Task StringContentLengthTest()
{
using (var httpClient = new HttpClient(new NoProxyHttpClientHandler()))
{
var postContent = $"bigContent={new string('a', 65535)}";
using (var response = await httpClient.PostAsync(TestUrl, new StringContent(postContent)))
{
Console.WriteLine($"response status code:{response.StatusCode}");
}
}
}
}
测试代码:
InvokeHelper.OnInvokeException = Console.WriteLine;
await InvokeHelper.TryInvokeAsync(FormUrlEncodeContentTest.FormUrlEncodedContentLengthTest);
Console.WriteLine();
await InvokeHelper.TryInvokeAsync(FormUrlEncodeContentTest.StringContentLengthTest);
Console.WriteLine();
await InvokeHelper.TryInvokeAsync(FormUrlEncodeContentTest.ByteArrayContentLengthTest);
Console.WriteLine("Completed!");
输出结果如下:

揪出异常始末
上传图片的时候会调用一个码云的一个 POST 接口来保存上传的图片,参数是通过 form-data 的方式传递的,在 POST 的时候报异常了,异常信息很诡异,具体信息如下:
System.UriFormatException: Invalid URI: The Uri string is too long.
at System.UriHelper.EscapeString(String input, Int32 start, Int32 end, Char[] dest, Int32& destPos, Boolean isUriStri
ng, Char force1, Char force2, Char rsvd)
at System.Uri.EscapeDataString(String stringToEscape)
at System.Net.Http.FormUrlEncodedContent.Encode(String data)
at System.Net.Http.FormUrlEncodedContent.GetContentByteArray(IEnumerable1 nameValueCollection) at System.Net.Http.FormUrlEncodedContent..ctor(IEnumerable1 nameValueCollection)
这个异常信息看上去像是 url 过长导致的,但是实际的 url 很短只有几百,而且从调用的堆栈上来看是 FormUrlEncodedContent 的 bug,然后根据异常堆栈信息去看了一下源码,部分源码如下:
首先看 FormUrlEncodedContent 做了什么:

然后再找上一层堆栈信息,Uri是一个分部类(partial),你如果直接在 Github 上 Find 的话会找到多个 Uri 相关的文件,最后在 UriExt 中找到了上面的 EscapeDataString 方法:

最后来看最上层的堆栈信息 UriHelper.EsacpeString 方法,找到异常抛出的地方

在 Uri 这个类中可以找到上面定义的 c_MaxUriBufferSize,它的值是 0xFFF0 转成十进制就是 65520

找到问题所在之后,就可以避免这个问题了,再遇到这个问题也就知道是怎么回事了,上面的问题就是 post 的数据太大了,超过了这个限制,所以引发的异常
More
既然知道这个是 FormUrlEncodedContent 的 bug,那么修复它就可以通过避免使用它,可以直接使用 ByteArray Content,或者不需要 Encode 处理直接用 StringContent 也是可以的
后来在 Github 搜 issue 的时候发现也有很多人遇到了这个问题,这个问题会在 net5 中得到修复,详见 PR https://github.com/dotnet/corefx/pull/41686

文中一些源码的链接在文章最后的 Reference 的部分可以找到
Reference
- https://github.com/dotnet/corefx/blob/release/3.1/src/System.Net.Http/src/System/Net/Http/FormUrlEncodedContent.cs#L53
- https://github.com/dotnet/corefx/blob/release/3.1/src/System.Private.Uri/src/System/UriExt.cs#L597
- https://github.com/dotnet/corefx/blob/release/3.1/src/System.Private.Uri/src/System/UriHelper.cs#L134
- https://github.com/dotnet/corefx/blob/release/3.1/src/System.Private.Uri/src/System/Uri.cs
- https://github.com/dotnet/corefx/pull/41686
- https://github.com/dotnet/corefx/tree/release/3.1
- https://github.com/WeihanLi/SamplesInPractice/blob/master/HttpClientTest/Program.cs
小心 HttpClient 中的 FormUrlEncodeContent 的 bug的更多相关文章
- li在IE中底部空行的BUG
li在IE中底部空行的BUG 但是这次li在IE中底部出现的不是3像素而是一整条空白行,如图:HTML代码: <ul> <li><a href="#" ...
- 拼接json时小心C#中bool类型转化
C#中bool类型的值,在ToString时会有如下转化:true—>Ture ; false—>False这是拼接到json串中就会出现如下结果:{ "no": &q ...
- httpClient中的三种超时设置小结
httpClient中的三种超时设置小结 本文章给大家介绍一下关于Java中httpClient中的三种超时设置小结,希望此教程能给各位朋友带来帮助. ConnectTimeoutExceptio ...
- 【IE6的疯狂之九】li在IE中底部空行的BUG
曾经写过[IE6的疯狂之六]li在IE中底部3像素的BUG(增加浮动解决问题),原文地址:http://www.css88.com/archives/421: IE6 BUG大全: http://ww ...
- httpClient 中的post或者get请求
httpClient相对于java自带的请求功能更加强大,下面就以例子的形式给出: //HttpClient Get请求 private static void register() { try { ...
- Java中httpClient中三种超时设置
本文章给大家介绍一下关于Java中httpClient中的三种超时设置小结 在Apache的HttpClient包中,有三个设置超时的地方: /* 从连接池中取连接的超时时间*/ ConnManage ...
- Excel日期中那个著名的bug
一个软件中的bug能够持续多久?答案不一,大多数bug在软件测试阶段就已经被干掉,又有许多死在Preview阶段,抑或正式上线后不久被干掉,有些则伴随软件终生,直到下一代产品发布才寿终正寝,而Exce ...
- QuantLib 金融计算——修复 BatesProcess 中的两个 Bug
QuantLib 金融计算--修复 BatesProcess 中的两个 Bug 我发现了 BatesProcess 中的两个 Bug: 基类 HestonProcess::factors 的返回值取决 ...
- 编写高质量代码改善C#程序的157个建议——建议83:小心Parallel中的陷阱
建议83:小心Parallel中的陷阱 Parallel的For和ForEach方法还支持一些相对复杂的应用.在这些应用中,它允许我们在每个任务启动时执行一些初始化操作,在每个任务结束后,又执行一些后 ...
随机推荐
- Golang 实现 Redis(5): 使用跳表实现 SortedSet
本文是使用 golang 实现 redis 系列的第五篇, 将介绍如何使用跳表实现有序集合(SortedSet)的相关功能. 跳表(skiplist) 是 Redis 中 SortedSet 数据结构 ...
- ASP.NET Core on K8S学习之旅(12)Ingress
本篇已加入<.NET Core on K8S学习实践系列文章索引>,可以点击查看更多容器化技术相关系列文章. 一.关于Ingress Kubernetes对外暴露Service主要有三种方 ...
- Java三大特征:封装 继承 多态
内部类:成员内部类.静态内部类.方法内部类.匿名内部类. 内部类:定义在另外一个类里面的类,与之对应,包含内部类的外部类被称为外部类. 内部类的作用:(1)内部类提供了更好的封装,可以把内部类隐藏在外 ...
- Mysql常用sql语句(13)- having 过滤分组结果集
测试必备的Mysql常用sql语句,每天敲一篇,每次敲三遍,每月一循环,全都可记住!! https://www.cnblogs.com/poloyy/category/1683347.html 前言 ...
- [hdu5534]DP
题目原意:给一棵n个点的树添加边,给定度函数f(d)为一个点的度的函数,求所有点的度函数的和 思路: 函数只与点的度有关,而与点无关,n个点的树有n-1条边,共产生2(n-1)个度,每个点至少有1个度 ...
- 学习python的第一天,python的简单知识
python 是现如今比较火的一种编程语言.在抱着试试的态度我来进行学习下python.要学习python 要先进行环境的安装. 下面是下载链接:https://pan.baidu.com/s/1PW ...
- Qt标准对话框按钮文字等设置为中文
问题描述:QMessageBox.QColorDialog等标准对话框按钮显示都是英文 设置中文方法如下: 1)拷贝Qt安装目录下的qt_zh_CN.qm和qt_zh_CN.ts文件到工程目录中 2) ...
- JQ选择器-选择符合条件的元素,获取对应关系元素
如果你想寻找id以“sub_”开头的元素,你可以使用: $("*[id^='sub_']") 如果你想寻找id以“trim”结尾的元素,你可以使用: $("*[id$=' ...
- java -> StringBuffer与StringBuilder类
字符串缓冲区 StringBuffer类 在学习String类时,API中说字符串缓冲区支持可变的字符串,什么是字符串缓冲区呢?接下来我们来研究下字符串缓冲区. 查阅StringBuffer的API, ...
- clickhouse基本操作一
常用SQL 创建表 1 2 3 4 5 6 7 CREATE TABLE b6logs( eventDate Date, impid UInt64, uid String, idfa String, ...