小心 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 的時候報例外了,例外資訊很詭異,具體資訊和上面的是一樣的:

這個例外資訊看上去像是 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
- https://github.com/OpenReservation/ReservationServer/commit/0262b2a6ce20c3ec12acc9548235757c18b20690#diff-dd926ccf347a255671a64f9e3edd5a88
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/20534.html
標籤:.NET Core
