為.netcore助力--WebApiClient正式釋出core版本

jiulang發表於2020-06-05

1 前言

WebApiClient已成熟穩定,釋出了WebApiClient.JIT和WebApiClient.AOT兩個nuget包,累計近10w次下載。我對它的高可擴充套件性設計相當滿意和自豪,但WebApiClient並不因此而停下腳步,在一年前,我產生了編寫其core版本的想法,將asp.netcore服務端先進的思想融入到core版本,在效能與擴充套件性上得到進一步昇華。
對應的,給它叫了WebApiClientCore的名字,為了對得起名字裡面的Core字,我在框架設計、效能優化上佔用整體開發時間一半以上。

2 框架設計

IActionInvoker

WebApiClient時還沒有IActionInvoker概念,對應的執行邏輯直接在ApiActionContext上實現。現在我覺得,Context應該是一個狀態資料類,而不能也成為一個執行者,因為一個執行者的例項可以無限次地執行多個Context例項。

Refit則更簡單粗暴,將所有實現都在一個RequestBuilderImplementation的類上:你們只要也只能使用我內建的Attribute宣告,一切執行在我這個類裡面包辦,因為我是一個萬能類。

Core版本增加了IActionInvoker概念,從中Context分開,用於執行Context,職責分明。在實現上又分為多種Invoker:Task宣告返回執行者ActionInvoker、ITask宣告返回處理處理者ActionTask,以及聚合的MultiplexedActionInvoker。

Middleware思想

WebApiClient時在處理各個特性、引數驗證、返回值驗證時沒有使用Middleware思想,特別是在處理響應結果和異常短路邏輯難以維護。

Refit還是簡單粗暴,將所有特性的解釋實現都在這個RequestBuilderImplementation的類上,因為我還是一個萬能類。

Core版本增加中介軟體Builder,將請求前的相關Attribute的執行編排Build為一個請求處理委託,將請求後相關Attribute的執行編排Build為一個響應處理委託,然後把兩個委託與真實http請求串在一起,Build出一個完整的請求響應委託。

得益於Middleware,流程中的請求前引數值驗證、結果處理特性短路、異常短路、請求後結果值驗和無條件執行IApiFilterAtrribue等這些複雜的流程變成簡單的管道處理;另外介面也變成支援服務端響應多種格式內容,每種格式內容在一個IApiReturnAttribute上實現和處理,比如請求為Accept: application/json, application/xml,不管伺服器返回xml或json都能處理。

/// <summary>
/// 建立執行委託
/// </summary>
/// <param name="apiAction">action描述器</param>
/// <returns></returns>
public static Func<ApiRequestContext, Task<ApiResponseContext>> Build(ApiActionDescriptor apiAction)
{
    var requestHandler = BuildRequestHandler(apiAction);
    var responseHandler = BuildResponseHandler(apiAction);

    return async request =>
    {
        await requestHandler(request).ConfigureAwait(false);
        var response = await HttpRequest.SendAsync(request).ConfigureAwait(false);
        await responseHandler(response).ConfigureAwait(false);
        return response;
    };
}

Context思想

WebApiClient只有一個ApiActionContext,其Result和Exception屬性在請求前就可以訪問或設定,但實際上就算設定了值,流程也不會短路和中斷,屬於設計失誤。

Refit沒有相關Context概念,因為它不提供給使用者自定義擴充套件Attribute的能力,它內建的Attribute也沒有執行能力,一個RequestBuilderImplementation類夠了。

Core版本將設計了多個Context概念,不同階段有不同的Context,如同asp.netcore不同Filter的Context也不同一樣。對於一個Action,請求階段對應是ApiRequestContext,響應階段是ApiResponseContext;對於Action的引數,對應是ApiParameterContext。每種Context裡面都包含核心的HttpContext屬性,HttpContext包含請求訊息、響應訊息和介面配置選項等。

Interface思想

輸入WebApiClientCore名稱空間,會發現定義了很多Interface,這些Interface都是為了使用者實現自定義特性用的,當然內建的所有特性,都是實現了這些介面而已。如果一個特性實現了多個介面,它就有多種能力,比如內建的HeaderAttribute,它既可以修飾於Interface和Method,也可以修飾引數。

WebApiClientCore的Attribute描述的邏輯,是由Attribute自我實現,所以整個請求的資料裝配邏輯是分散為各個Attribute上,用什麼Attribute就有什麼邏輯,包含框架之外的非內建的自定義Attribute。

Refit的內建Attribute只有欲描述邏輯,沒有實現邏輯,實現邏輯由RequestBuilderImplementation包辦,所以它不需要介面也沒有介面。

3 效能優化

更快的字串替換

像[HttpGet("objects/{id}")]這樣的path引數,在restful中常常遇到,通過Span優化,Core版本在替換path引數值cpu佔用時間降低為原版本的十分之一。

更快的json序列化

得益於Sytem.Text.Json,json序列化和反序列化上效能顯明提升。

更少的緩衝區分配

WebApiClientCore使用了可回收複用的IBufferWriter,在json序列化得到json、json裝配為HttpContent只申請一次Buffer,而且HttpContent在傳送之後,這個Buffer被回收複用。IBufferWriter還於用表單的uri編碼,編碼產生的Buffer不用申請新的記憶體內容,直接寫入表單的HttpContent。

更少的編碼操作

WebApiClientCore的json不再使用utf16的string中間型別,直接將物件序列化為網路請求需要的utf8編碼二進位制json;表單的key和Value編碼時,也不產生string中間型別,而是編碼後的二進位制資料內容,然後寫入表單的IBufferWriter。

更快的快取查詢

WebApiClient建立代理類例項來執行一個請求時,要查詢兩次快取:通過介面型別查詢字典快取的介面代理類,然後例項化代理類;在ApiInterceptor裡面通過MethodInfo查詢字典快取的ApiActionDescriptor。

Refit執行同樣邏輯也使用了兩次字典快取,介面和介面代理類安全字典快取TypeMapping,介面和介面方法描述的字典快取interfaceHttpMethods。

WebApiClientCore取消了字典快取,使用靜態泛型類的欄位作快取,因為欄位訪問遠比字典查詢高效。同時通過巧妙的設計,在代理類攔截方法執行時,直接回傳IActionInvoker替換原來的MethodInfo,IActionInvoker包含了ApiActionDescriptor,而IActionInvoker與代理型別都一起快取在靜態泛型類的欄位,減少了一次必須的字典快取查詢過程。

效能對比

排除掉真實的網路請求IO等待時間,WebApiClientCore使用的cpu時間僅僅為WebApiClient.JIT和Refit的三分之一。

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.836 (1903/May2019Update/19H1)
Intel Core i3-4150 CPU 3.50GHz (Haswell), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=3.1.202
  [Host]     : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
  DefaultJob : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
Method Mean Error StdDev
HttpClient_GetAsync 3.146 μs 0.0396 μs 0.0370 μs
WebApiClientCore_GetAsync 12.421 μs 0.2324 μs 0.2174 μs
Refit_GetAsync 43.241 μs 0.6713 μs 0.6279 μs
Method Mean Error StdDev
HttpClient_PostJsonAsync 5.263 μs 0.0784 μs 0.0733 μs
WebApiClientCore_PostJsonAsync 13.823 μs 0.1874 μs 0.1753 μs
Refit_PostJsonAsync 45.218 μs 0.8166 μs 0.7639 μs

4 Nuget包與文件

Nuget包

<PackageReference Include="WebApiClientCore" Version="1.0.0" />

專案地址與文件

點選專案連結,帶你GET到N種使用技能,不求star,只求提供良好建議。

https://github.com/dotnetcore/WebApiClient

相關文章