小心 HttpClient 中的 FormUrlEncodeContent 的 bug 小心 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章