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的设计相关推荐

  1. 初中计算机网络和因特网教案,初中信息技术走进因特网教学设计

    初中信息技术虽非考试学科,但对学生的终生发展具有非常重要的意义,是学生适应现代社会生活的必学的基础学科,接下来学习啦小编为你整理了初中信息技术走进因特网教学设计,一起来看看吧. 初中信息技术走进因特网 ...

  2. 带你走进计算机辅助药物设计(CADD)蛋白质分子对接

    对接的过程中会考虑如下因素: 形状互补 亲疏水性 表面电荷分布 两种蛋白质-蛋白质分子对接: Rigid Docking 刚性对接–目前可用的大多数软件为刚性对接. Flexible Docking ...

  3. 《用户体验设计 本质、策略与经验》一2 设计师

    本节书摘来自异步社区<用户体验设计 本质.策略与经验>一书中的第2章,第2.1节,作者[美]Robert Hoekman Jr.,更多章节内容可以访问云栖社区"异步社区" ...

  4. 基于Bootstrap+html的响应式速递快递物流网页设计

    全套资料下载地址:https://download.csdn.net/download/sheziqiong/85583994?spm=1001.2014.3001.5503 目录 项目设计目标及要求 ...

  5. 基于SSM的服装设计供需系统设计与实现

    摘 要:作为服装设计的重要形式之一,服装具有显著的审美性,是人类情感表达不可忽视的代表形态.但在新时期背景下,随着服装设计的进一步优化,服装设计创新融合强度也随之增强.本文就服装设计供需系统进行深入探 ...

  6. 《响应式web设计》读书笔记(一)入门

    写在开始之前 2013年的夏天,我算是正式加入了前端开发的队伍,作为一名新手,关注了很多知名网站.牛人博客,时时刻刻感觉要学的东西太多太多,一路不断的收藏收藏,攒了好多文章来看,心里还想着在前端世界里 ...

  7. 模拟IC设计到底怎么学?给初学者一点建议

    想必大家都知道,模拟IC设计非常难学.就拿最普遍的晶体管来说,我们分析它的时候必须首先分析直流偏置,其次在分析交流输出电压.可以说,这是一项相当复杂的工作.有些朋友一直吐槽模拟IC设计真的非常难学,那 ...

  8. 淘宝推广方法大全,教你如何做淘宝(转)

    一.关键词:搜索 做关键字优化,让用户在搜索相关产品的时候可以首先看到你. 搜索是买家购买宝贝的主要方式之一,能否利用好搜索,关系重大,意义非凡.那么,如何充分利用搜索来为你服务呢?好的宝贝描述,是买 ...

  9. 【VR】虚拟现实行业初探

    仅以此系列文章献给那些想了解虚拟现实(VR, Virtual Reality)的朋友,专业人士及行业人士慎入. "虚拟现实是利用电脑模拟产生一个三维空间的虚拟世界, 提供使用者关于视觉.听觉 ...

最新文章

  1. shell介绍及基本用法
  2. 6463: Tak and Hotels II(倍增)
  3. 一壶 100℃ 的开水从多高倒进嘴里不会觉得烫?
  4. Windows10 Virtualization Technology虚拟化技术功能
  5. SQL on HBase -- phoenix 之分页查询
  6. 2014年的暑假ACM之旅!
  7. 音视频同步、网络抖动
  8. java contains 效率_List与Set的contains方法效率问题
  9. DBeaver 离线安装
  10. 模型及贴图的细节及优化
  11. 分享下看高清电影的网址
  12. java从入门到出轨
  13. KKT条件的物理意义(转)
  14. 初级软件开发人员进修必备的20本书(上)
  15. UDP编程——TFPT(小文件传输)
  16. python监控文件或目录变化
  17. flex布局---flex-wrap属性,决定排列是否换行
  18. 利用ADS、HFSS设计微带带通滤波器的设计日志
  19. 个人小程序诞生记【第二周】积分排名、生成海报、积分抽奖
  20. ib课程计算机科学内容,让人疑惑的ib课程体系的六大课程是指什么?

热门文章

  1. SpringMVC4零配置--web.xml
  2. Shell 控制并发
  3. Process Explorer 15.2:微软增强型任务管理器
  4. Teams Bot开发系列:Teams的Activity处理
  5. 如何在Windows 10的地图应用程序中获取离线地图
  6. vue mock模拟后台接口数据
  7. 【转】HTML5移动端最新兼容问题解决方案
  8. 2015年网页设计最佳颜色搭配的9种选择
  9. 莫名其妙的主机名 VM_32_234_centos
  10. 与众不同 制作会唱歌的WinRAR - imsoft.cnblogs