走进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,和我一起完善单元测试,或编写多语言资源文件,或者加入一些更好的代码设计。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309795.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Blazor WebAssembly 3.2 正式发布

5月 20日&#xff0c;微软 发布了 Blazor WebAssembly 3.2(https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-now-available/) 。Blazor 是 ASP.NET Core 中的一个新框架&#xff0c;支持使用 C#和 HTML 创建交互式 Web 应用程序。Blazor WebAssembly 使用基于…

温故知新:Docker基础知识知多少?

【云原生】| 作者/Edison Zhou这是恰童鞋骚年的第233篇原创文章记得之前曾经粗略的写过一篇Docker的基础及ASP.NET Core部署Docker示例的入门文章&#xff0c;但那个时候刚刚学习对Docker的认知还比较浅&#xff0c;现在重新来温故知新一下。本文预计阅读时间为10min。1容器的用…

[C++11]lambda表达式语法

代码如下: #include <iostream> using namespace std;void func(int x, int y) {int a;int b;[]() {int c a;//使用了外部变量&#xff0c;[]里面加个 int d x;}; }int main() {return 0; }lambda表达式的注意事项: 以上图片来自下面链接: https://subingwen.cn/cpp…

[翻译]欢迎使用C#9.0

本文由公众号[开发者精选资讯](微信号&#xff1a;yuantoutiao)翻译首发&#xff0c;转载请注明来源C# 9.0 is taking shape, and I’d like to share our thinking on some of the major features we’re adding to this next version of the language.C&#xff03;9.0初具规…

操作系统第四章习题

操作系统第四章习题 1.对一个将页表放在内存中的分页系统&#xff1a; (1) 如果访问内存需要0.2μs&#xff0c;有效访问时间为多少? (2) 如果加一快表&#xff0c;且假定在快表中找到页表的几率高达90%&#xff0c;则有效访问时间又是多少&#xff08;假定查快表需花的时间…

[C++11]右值和右值引用

代码如下: #include <iostream> using namespace std;int main() {//左值int num 9;//左值引用int &a num;//右值const int N 5;//右值引用int && b 8;//常量左值引用const int &c num;//常量右值引用const int &&d 6;//const int &&…

我们为什么推荐在Json中使用string表示Number属性值

在这篇简短的文章中&#xff0c;我将解释在使用JSON传输数据时&#xff0c;为什么浮点数或大十进制值应表示为字符串 。long类型引发的诡异情况长话短说&#xff0c;同事在利用swagger对接后端API时&#xff0c;诡异的发现swaggerUI中显示的json属性值并不是api返回的值。[Http…

并查集+基础知识点详解

并查集概念 并查集单看名字大家也能猜到这个算法的作用&#xff0c;是用来对集合进行合并和查找操作 并查集是一种树型的数据结构&#xff0c;用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。——来自百度百科 就是将原本不一样的集合&#xff0c;但是由于某种关系有…

[C++11]move资源的转移

从实现上讲&#xff0c;std::move 基本等同于一个类型转换&#xff1a;static_cast<T&&>(lvalue);&#xff0c;函数原型如下: template<class _Ty> _NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) _NOEXCEPT { …

基于 abp vNext 和 .NET Core 开发博客项目 - 统一规范API,包装返回模型

上一篇文章使用自定义仓储完成了简单的增删改查案例&#xff0c;有心的同学可以看出&#xff0c;我们的返回参数一塌糊涂&#xff0c;显得很不友好。在实际开发过程中&#xff0c;每个公司可能不尽相同&#xff0c;但都大同小异&#xff0c;我们的返回数据都是包裹在一个公共的…

奔小康赚大钱 HDU - 2255( 二分图匹配KM算法详解)

题目 传说在遥远的地方有一个非常富裕的村落,有一天,村长决定进行制度改革&#xff1a;重新分配房子。 这可是一件大事,关系到人民的住房问题啊。村里共有n间房间,刚好有n家老百姓,考虑到每家都要有房住&#xff08;如果有老百姓没房子住的话&#xff0c;容易引起不安定因素&…

记一次排查线上程序内存的忽高忽低,又是大集合惹祸了

一&#xff1a;背景1. 讲故事昨天继续还技术债&#xff0c;优化一轮后的程序拉到线上后内存继续忽高忽低&#xff0c;低的时候20G&#xff0c;高的时候30G&#xff0c;过了一会又下降了几个G&#xff0c;毫无疑问&#xff0c;程序中有什么集合或者什么操作占用了大量内存&#…

[C++11]forward完美转发

// 函数原型 template <class T> T&& forward (typename remove_reference<T>::type& t) noexcept; template <class T> T&& forward (typename remove_reference<T>::type&& t) noexcept;// 精简之后的样子 std::forward…

Pipe HDU - 2150(判断线段相交+向量叉乘线代详解)

题目&#xff1a; 经过激烈的争夺&#xff0c;Lele终于把那块地从Yueyue的手里抢了回来。接下来&#xff0c;Lele要开始建造他的灌溉系统。 通过咨询Lele的好友——化学系的TT&#xff0c;Lele决定在田里挖出N条沟渠&#xff0c;每条沟渠输送一种肥料。 每条沟渠可以看作是一…

win7如何将计算机移至桌面,如何将win7电脑桌面的文件转移到其他盘中?

想必很多朋友都和小编一样&#xff0c;是一个嫌麻烦的人&#xff0c;是一个不怎么爱收拾的人吧?这种人有一个通病&#xff0c;那就是喜欢将一些重要的文件放置在win7 64位纯净版下载的桌面上&#xff0c;这样的话&#xff0c;不仅容易找到&#xff0c;而且方便使用&#xff0c…

凸包算法知识总结

首先&#xff0c;什么是凸包&#xff1f; 假设平面上有p0~p12共13个点&#xff0c;过某些点作一个多边形&#xff0c;使这个多边形能把所有点都“包”起来。当这个多边形是凸多边形的时候&#xff0c;我们就叫它“凸包”。 处理何种问题&#xff1a;凸包可以看成在木板上钉许多…

[C++11]shared_ptr使用的注意事项(内存被重复析构,内存泄漏问题)

shared_ptr使用的注意事项: 1.不能使用一个原始地址初始化多个共享智能指针 2.函数不能返回管理了this的共享智能指针对象 3.共享智能指针不能循环引用 不能使用一个原始地址初始化多个共享智能指针 代码如下: #include <iostream> #include <memory> using name…

一文解读使用WinDbg排查iis 中CPU占用高的站点问题

一、概述在Window服务器部署程序后&#xff0c;可能因为代码的不合理或者其他各种各样的问题&#xff0c;会导致CPU暴增&#xff0c;甚至达到100%等情况&#xff0c;严重危及到服务器的稳定以及系统稳定&#xff0c;但是一般来说对于已发布的程序&#xff0c;没法即时看到出问题…

Power Network POJ - 1459(EK算法模板+详解)

题意&#xff1a; 总共有a个节点&#xff0c;其中有发电站b个、用户c个和调度器a-b-c个三种节点&#xff0c;每个发电站有一个最大发电量&#xff0c;每个用户有个最大接受电量&#xff0c;现在有d条有向边&#xff0c;边有一个最大的流量代表&#xff0c;最多可以流出这么多电…