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
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,和我一起完善單元測試,或編寫多語言資原始檔,或者加入一些更好的程式碼設計。