小心 HttpClient 中的 FormUrlEncodeContent 的 bug
小心 HttpClient 中的 FormUrlEncodeContent 的 bug
Intro
最近發(fā)現(xiàn)活動(dòng)室預(yù)約項(xiàng)目里的上傳圖片有時(shí)候會(huì)有問(wèn)題,周末找時(shí)間測(cè)試了一下,發(fā)現(xiàn)小圖片的上傳沒(méi)問(wèn)題,大圖片上傳會(huì)有問(wèn)題,而且異常信息還很奇怪,System.UriFormatException: Invalid URI: The Uri string is too long 看這個(gè)錯(cuò)誤的信息還以為是請(qǐng)求的 url 過(guò)長(zhǎng)導(dǎo)致的,但是實(shí)際請(qǐng)求的 url 很短,詭異的異常信息
測(cè)試示例
為了方便大家了解和測(cè)試這個(gè)bug,我在 Github 上提供了一個(gè)示例 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}");}}} }測(cè)試代碼:
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!");輸出結(jié)果如下:
揪出異常始末
上傳圖片的時(shí)候會(huì)調(diào)用一個(gè)碼云的一個(gè) POST 接口來(lái)保存上傳的圖片,參數(shù)是通過(guò) form-data 的方式傳遞的,在 POST 的時(shí)候報(bào)異常了,異常信息很詭異,具體信息和上面的是一樣的:
這個(gè)異常信息看上去像是 url 過(guò)長(zhǎng)導(dǎo)致的,但是實(shí)際的 url 很短只有幾百,而且從調(diào)用的堆棧上來(lái)看是 FormUrlEncodedContent 的 bug,然后根據(jù)異常堆棧信息去看了一下源碼,部分源碼如下:
首先看 FormUrlEncodedContent 做了什么:
然后再找上一層堆棧信息,Uri是一個(gè)分部類(partial),你如果直接在 Github 上 Find 的話會(huì)找到多個(gè) Uri 相關(guān)的文件,最后在 UriExt 中找到了上面的 EscapeDataString 方法:
最后來(lái)看最上層的堆棧信息 UriHelper.EsacpeString 方法,找到異常拋出的地方
在 Uri 這個(gè)類中可以找到上面定義的 c_MaxUriBufferSize,它的值是 0xFFF0 轉(zhuǎn)成十進(jìn)制就是 65520
找到問(wèn)題所在之后,就可以避免這個(gè)問(wèn)題了,再遇到這個(gè)問(wèn)題也就知道是怎么回事了,上面的問(wèn)題就是 post 的數(shù)據(jù)太大了,超過(guò)了這個(gè)限制,所以引發(fā)的異常
More
既然知道這個(gè)是 FormUrlEncodedContent 的 bug,那么修復(fù)它就可以通過(guò)避免使用它,可以直接使用 ByteArray Content,或者不需要 Encode 處理直接用 StringContent 也是可以的
后來(lái)在 Github 搜 issue 的時(shí)候發(fā)現(xiàn)也有很多人遇到了這個(gè)問(wèn)題,這個(gè)問(wèn)題會(huì)在 net5 中得到修復(fù),詳見(jiàn) 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
總結(jié)
以上是生活随笔為你收集整理的小心 HttpClient 中的 FormUrlEncodeContent 的 bug的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何训练解决问题的能力?
- 下一篇: MySql轻松入门系列——第一站 从源码