走进WebApiClientCore的设计
WebApiClient
WebApiClient是NCC開源社區的一個項目,是目前微服務里http接口調用的一把鋒利尖刀,項目早期設計與開發的時候,是基于.netframework的,然后慢慢加入netstandard和netcoreapp多個框架的支持,設計能力出眾,AOP能力唾手可得易如反掌。
WebApiClientCore
WebApiClient很優秀,它將不同框架不同平臺都實現了統一的api;WebApiClient不夠優秀,它在.netcore下完全可以更好,但它不得不兼容.net45開始所有框架而有所犧牲。所以WebApiClientCore橫空出世,它是WebApiClient.JIT的.netcore替代版本,目前尚屬于alpha階段,計劃只支持.netcore平臺,并緊密與.netcore新特性緊密結合。
WebApiClientCore的變化
使用System.Text.Json替換Json.net,提升序列化性能
移除HttpApiFactory和HttApiConfig功能,使用Microsoft.Extensions.Http的HttpClientFactory
移除AOT功能,僅保留依賴于Emit的運行時代理
高效的ActionInvoker,對返回Task<>和ITask<>作不同處理
所有特性都都變成中間件,基于管道編排各個特性并生成Action執行委托
良好設計的HttpContext、ApiRequestContext、ApiParameterContext和ApiResponseContext
WebApiClientCore執行流程設計
1 接口代理類生成設計
Cretate<THttpApi>() -> BuildProxyType() -> CreateProxyInstance(ActionInterceptor)
1.1 HttpApiProxyTypeBuilder
在HttpApi.Create()時,先調用HttpApiProxyTypeBuilder來生成THttpApi接口的代理類,HttpApiProxyTypeBuilder是基于Emit方案,Build出來的代理類在每個方法調用時觸發一次攔截器ActionInterceptor的Intercept()方法,將調用參數傳給攔截器。
1.2 HttpApiProxyBuilder
給定一個代理類的類型(Type),快速生成代理類的實例,這個Builder實際是生成并保存了代理類構造器的高效調用委托,屬于反射優化。
2 ActionInterceptor的設計
ActionInterceptor.Intercept(MethodInfo) -> CreateActionInvoker() -> ActionInvoker.Invoke()
ActionInterceptor在攔截到方法調用時,根據方法的MethodInfo信息,創建ActionInvoker,然后調用ActionInvoker.Invoke()執行。當然,ActionInvoker并不是總是創建的,因為它的創建是有成本的,ActionInterceptor使用了緩存ActionInvoker的方案。
2.1 MultiplexedActionInvoker
WebApiClientCore支持加Task<>和ITask<>兩種異步聲明,MultiplexedActionInvoker實際上包裝了ActionInvoker和ActionTask兩個字段,當聲明為Task<>時,調用ActionInvoker執行,當聲明為ITask<>是,返回創建實現了ITask<>接口的ActionTask實例。
2.2 ActionInvoker
ActionInvoker是一個ApiActionDescriptor的執行器,其實現了IActionInvoker.Invoke(ServiceContext context, object[] arguments)接口。關于Descriptor的設計模式,我們在asp.netcore的各種AtionContext里可以發現,有了ApiActionDescriptor,再給它各個參數值,Action就很容易執行起來了。
3 RequestDelegate生成設計
ActionInvoker在拿到各個參數值之后,并不是直接從ApiActionDescriptor查找各個特性來執行,而是在執行前就把執行流程編譯好,得到一個執行委托,這個委托叫RequestDelegate,其原型為Func<ApiRequestContext, Task<ApiResponseContext>> Build(ApiActionDescriptor apiAction)。抽象成傳入請求上下文件,返回響應上下文,當真正執行時,調用這個委托即可。如果你熟悉asp.netcore,那么應該很容易理解下面代碼的思路:
/// <summary>
/// 提供Action的調用鏈委托創建
/// </summary>
static class RequestDelegateBuilder
{
/// <summary>
/// 創建執行委托
/// </summary>
/// <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 SendRequestAsync(request).ConfigureAwait(false);
await responseHandler(response).ConfigureAwait(false);
return response;
};
}
/// <summary>
/// 創建請求委托
/// </summary>
/// <param name="apiAction"></param>
/// <returns></returns>
private static InvokeDelegate<ApiRequestContext> BuildRequestHandler(ApiActionDescriptor apiAction)
{
var builder = new PipelineBuilder<ApiRequestContext>();
// 參數驗證特性驗證和參數模型屬性特性驗證
builder.Use(next => context =>
{
var validateProperty = context.HttpContext.Options.UseParameterPropertyValidate;
foreach (var parameter in context.ApiAction.Parameters)
{
var parameterValue = context.Arguments[parameter.Index];
ApiValidator.ValidateParameter(parameter, parameterValue, validateProperty);
}
return next(context);
});
// action特性請求前執行
foreach (var attr in apiAction.Attributes)
{
builder.Use(attr.OnRequestAsync);
}
// 參數特性請求前執行
foreach (var parameter in apiAction.Parameters)
{
var index = parameter.Index;
foreach (var attr in parameter.Attributes)
{
builder.Use(async (context, next) =>
{
var ctx = new ApiParameterContext(context, index);
await attr.OnRequestAsync(ctx, next).ConfigureAwait(false);
});
}
}
// Return特性請求前執行
foreach (var @return in apiAction.Return.Attributes)
{
if (@return.Enable == true)
{
builder.Use(@return.OnRequestAsync);
}
}
// Filter請求前執行
foreach (var filter in apiAction.FilterAttributes)
{
if (filter.Enable == true)
{
builder.Use(filter.OnRequestAsync);
}
}
return builder.Build();
}
/// <summary>
/// 創建響應委托
/// </summary>
/// <param name="apiAction"></param>
/// <returns></returns>
private static InvokeDelegate<ApiResponseContext> BuildResponseHandler(ApiActionDescriptor apiAction)
{
var builder = new PipelineBuilder<ApiResponseContext>();
// Return特性請求后執行
foreach (var @return in apiAction.Return.Attributes)
{
if (@return.Enable == false)
{
continue;
}
builder.Use(async (context, next) =>
{
if (context.ResultStatus == ResultStatus.None)
{
await @return.OnResponseAsync(context, next).ConfigureAwait(false);
}
else
{
await next().ConfigureAwait(false);
}
});
}
// 驗證Result是否ok
builder.Use(next => context =>
{
try
{
ApiValidator.ValidateReturnValue(context.Result, context.HttpContext.Options.UseReturnValuePropertyValidate);
}
catch (Exception ex)
{
context.Exception = ex;
}
return next(context);
});
// Filter請求后執行
foreach (var filter in apiAction.FilterAttributes)
{
if (filter.Enable == true)
{
builder.Use(filter.OnResponseAsync);
}
}
return builder.Build();
}
/// <summary>
/// 執行http請求
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private static async Task<ApiResponseContext> SendRequestAsync(ApiRequestContext context)
{
try
{
var apiCache = new ApiCache(context);
var cacheValue = await apiCache.GetAsync().ConfigureAwait(false);
if (cacheValue != null && cacheValue.Value != null)
{
context.HttpContext.ResponseMessage = cacheValue.Value;
}
else
{
using var cancellation = CreateLinkedTokenSource(context);
var response = await context.HttpContext.Client.SendAsync(context.HttpContext.RequestMessage, cancellation.Token).ConfigureAwait(false);
context.HttpContext.ResponseMessage = response;
await apiCache.SetAsync(cacheValue?.Key, response).ConfigureAwait(false);
}
return new ApiResponseContext(context);
}
catch (Exception ex)
{
return new ApiResponseContext(context) { Exception = ex };
}
}
/// <summary>
/// 創建取消令牌源
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private static CancellationTokenSource CreateLinkedTokenSource(ApiRequestContext context)
{
if (context.CancellationTokens.Count == 0)
{
return CancellationTokenSource.CreateLinkedTokenSource(CancellationToken.None);
}
else
{
var tokens = context.CancellationTokens.ToArray();
return CancellationTokenSource.CreateLinkedTokenSource(tokens);
}
}
}
WebApiClientCore的特性設計
WebApiClientCore的核心特性為以下4種,每種功能各不一樣,在設計上使用了中間件的思想,每一步執行都可以獲取到context對象和下一個中間件next對象,開發者在實現自定義Attribute時,可以選擇性的進行短路設計。
1 IApiActionAttribute
表示Action執行前會調用,調用時接收到ApiRequestContext
/// <summary> /// 定義ApiAction修飾特性的行為 /// </summary> public interface IApiActionAttribute : IAttributeMultiplable {/// <summary>/// 請求前/// </summary>/// <param name="context">上下文</param>/// <param name="next">下一個執行委托</param>/// <returns></returns>Task OnRequestAsync(ApiRequestContext context, Func<Task> next); }2 IApiParameterAttribute
表示參數執行前會調用,調用時接收到ApiParameterContext
/// <summary> /// 定義Api參數修飾特性的行為 /// </summary> public interface IApiParameterAttribute {/// <summary>/// 請求前/// </summary>/// <param name="context">上下文</param>/// <param name="next">下一個執行委托</param>/// <returns></returns>Task OnRequestAsync(ApiParameterContext context, Func<Task> next); }3 IApiReturnAttribute
執行前和執行后都會收到,設置為上下文的Result或Exception,會短路執行
/// <summary> /// 定義回復內容處理特性的行為 /// </summary> public interface IApiReturnAttribute : IAttributeMultiplable, IAttributeEnable {/// <summary>/// 請求前/// </summary>/// <param name="context">上下文</param>/// <param name="next">下一個執行委托</param>/// <returns></returns>Task OnRequestAsync(ApiRequestContext context, Func<Task> next);/// <summary>/// 響應后/// </summary>/// <param name="context">上下文</param>/// <param name="next">下一個執行委托</param>/// <returns></returns>Task OnResponseAsync(ApiResponseContext context, Func<Task> next); }5 IApiFilterAttribute
執行前和執行后都會收到,在IApiReturnAttribute之后執行
/// <summary>
/// 定義ApiAction過濾器修飾特性的行為
/// </summary>
public interface IApiFilterAttribute : IAttributeMultiplable, IAttributeEnable
{
/// <summary>
/// 請求前
/// </summary>
/// <param name="context">上下文</param>
/// <param name="next">下一個執行委托</param>
/// <returns></returns>
Task OnRequestAsync(ApiRequestContext context, Func<Task> next);
/// <summary>
/// 響應后
/// </summary>
/// <param name="context">上下文</param>
/// <param name="next">下一個執行委托</param>
/// <returns></returns>
Task OnResponseAsync(ApiResponseContext context, Func<Task> next);
}
結束語
代碼可以寫得很爛,但設計必須高大上,希望WebApiClientCore可以在聲明式客戶端領域繼續引領其它開源庫,同時讓使用它的開發者為之贊嘆。
如果你希望為望WebApiClientCore出力,可以Fork它然后pull request,和我一起完善單元測試,或編寫多語言資源文件,或者加入一些更好的代碼設計。
總結
以上是生活随笔為你收集整理的走进WebApiClientCore的设计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Sql Server之旅——第四站 你必
- 下一篇: Blazor WebAssembly 3