zoukankan      html  css  js  c++  java
  • 走进WebApiClientCore的设计

    WebApiClient

    WebApiClientNCC开源社区的一个项目,是目前微服务里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,和我一起完善单元测试,或编写多语言资源文件,或者加入一些更好的代码设计。

  • 相关阅读:
    弱引用的字典WeakDictionary(转)
    XBox360调试程序失败:Unable to start debugging.Connection to Xbox360 development kit lost
    托管C++中的范型和模板的区别
    智能指针的缺陷
    托管C++笔记(二)原创
    C#通过WMI操作本地共享文件夹
    很随便的随笔
    "The system cannot execute the specified program"
    C++多重继承
    托管C++笔记(一)原创
  • 原文地址:https://www.cnblogs.com/kewei/p/12923191.html
Copyright © 2011-2022 走看看