小心 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

小心 HttpClient 中的 FormUrlEncodeContent 的 bug的更多相关文章

  1. li在IE中底部空行的BUG

    li在IE中底部空行的BUG 但是这次li在IE中底部出现的不是3像素而是一整条空白行,如图:HTML代码: <ul> <li><a href="#" ...

  2. 拼接json时小心C#中bool类型转化

    C#中bool类型的值,在ToString时会有如下转化:true—>Ture ; false—>False这是拼接到json串中就会出现如下结果:{ "no": &q ...

  3. httpClient中的三种超时设置小结

    httpClient中的三种超时设置小结   本文章给大家介绍一下关于Java中httpClient中的三种超时设置小结,希望此教程能给各位朋友带来帮助. ConnectTimeoutExceptio ...

  4. 【IE6的疯狂之九】li在IE中底部空行的BUG

    曾经写过[IE6的疯狂之六]li在IE中底部3像素的BUG(增加浮动解决问题),原文地址:http://www.css88.com/archives/421: IE6 BUG大全: http://ww ...

  5. httpClient 中的post或者get请求

    httpClient相对于java自带的请求功能更加强大,下面就以例子的形式给出: //HttpClient Get请求 private static void register() { try { ...

  6. Java中httpClient中三种超时设置

    本文章给大家介绍一下关于Java中httpClient中的三种超时设置小结 在Apache的HttpClient包中,有三个设置超时的地方: /* 从连接池中取连接的超时时间*/ ConnManage ...

  7. Excel日期中那个著名的bug

    一个软件中的bug能够持续多久?答案不一,大多数bug在软件测试阶段就已经被干掉,又有许多死在Preview阶段,抑或正式上线后不久被干掉,有些则伴随软件终生,直到下一代产品发布才寿终正寝,而Exce ...

  8. QuantLib 金融计算——修复 BatesProcess 中的两个 Bug

    QuantLib 金融计算--修复 BatesProcess 中的两个 Bug 我发现了 BatesProcess 中的两个 Bug: 基类 HestonProcess::factors 的返回值取决 ...

  9. 编写高质量代码改善C#程序的157个建议——建议83:小心Parallel中的陷阱

    建议83:小心Parallel中的陷阱 Parallel的For和ForEach方法还支持一些相对复杂的应用.在这些应用中,它允许我们在每个任务启动时执行一些初始化操作,在每个任务结束后,又执行一些后 ...

随机推荐

  1. Java——Java集合那些事

    集合概述: 集合和数组都可以保存多个对象,但是数组的长度不可变,集合可以保存数量变化的数据.java中的集合类主要由两个接口派生出,Collection和Map Collection接口和Iterat ...

  2. 线段树 区间合并 F - Sequence operation

    F - Sequence operation 题解:这个题目不是一个特别难的题目,但是呢,写了好久,首先线段树难敲,其次就是bug难找,最后这个代码都被我改的乱七八糟的了,这个有两个地方要注意一下,一 ...

  3. 不需要爬虫也能轻松获取 unsplash 上的图片

    我经常会使用 unsplash, 这里面的图片非常清爽,我的大多数文章的图片都是在这个网上找的,虽然也有同类型网站,但是用过一段时间以后基本都放弃了,图片质量参差不齐,筛选过程太费劲. 但是 unsp ...

  4. Excel+Python:分组求和

    Excel选中区域,排除序号.姓名等列,复制Ctrl+C. Python,import pandas as pd,读取剪切板并赋值给变量df,df.groupby('部门').sum().若要避免部门 ...

  5. ipad4密码忘记锁定了如何破解

    ipad4更新后被要求输入密码,但很长一段时间后忘记了,一直想不起来,也没有忘记密码的选项,以下是简单的破解方法. 注意:没有备份的资料是要被清空的 一.windows10系统,安装iTunes安装 ...

  6. Spring全家桶之spring boot(四)

    spring boot拦截器.过滤器.servlet和健康检查机制  spring boot拦截器 spring boot配置拦截器与原来大致相同,只是需要在拦截器的配置类上添加@Configurat ...

  7. 死磕synchronized底层实现

    点赞再看,养成习惯,微信搜索[三太子敖丙]第一时间阅读. 本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点.资料以及我的系列文章. 前言 ...

  8. git:error: Your local changes to the following files would be overwritten by merge:

    最近用git在服务器.github.本地更新代码的时候,因为频繁修改偶尔出现这个错误 覆盖本地的代码: git stash git pull git stash pop 保留对服务器上的修改: git ...

  9. js 前端实现文件流下载的几种方式

    后端是用Java写的一个下载的接口,返回的是文件流,需求:点击,请求,下载 利用iframe实现文件流下载 //把上一次创建的iframe删掉,不然随着下载次数的增多页面上会一堆的iframe var ...

  10. 详细讲解使用Sublime Text 3进行Markdown编辑和实时预览

    所需安装的插件 Markdown Editing // Markdown编辑和语法高亮 Markdown Preview// Markdown导出html预览 LiveReload// 时时预览 安装 ...