zoukankan      html  css  js  c++  java
  • Refit集成consul在asp.net core中的实践

    前言

    github:https://github.com/alphayu/

    Refit、WebApiClient、Feign等都是支持声名式的Restful服务调用的开源组件。

    这个几个组件都综合研究总结了下,Refit fork数多,使用文档易懂,提供的功能基本都满足我的要求。

    同时Refit本身集成了HttpClientFactory(Refit.HttpClientFactory)。

    综上最后还是选择了Refit。

    然而我的项目是使用Consul作为服务注册中心。

    Refit、WebApiClient、Feign 这个几个.Net core 社区比较流行的http客户端Restful资源请求组件都没有集成Consul服务发现功能。

    Steeltoe扩展了Refit的Euerka的服务发现,配合Refit.HttpClientFactory可以很好的声明服务调用。

    在google搜索了下Refit consul关键字,搜索出来的基本都是介绍Refit与Consul的基础使用的文章。

    看来只有靠自己造个轮子了。

    研究了下Steeltoe组件Refit的Euerka的服务发现。

    要集成Consul需要实现一个ConsulHttpMessageHandler,看了下Steeltoel的DiscoveryHttpMessageHandler类代码,关联的文件太多,借鉴它的写法太麻烦了。

    原本想放弃了,接着研究了下Refit的相关代码与httpclientfactory相关文章,豁然开朗。

    原来很容易实现,只是自己之前没有看懂而已。

    只需写一个类继承DelegatingHandler类,覆写SendAsync方法,并把该类注册进去替换缺省的HttpMessageHandler。

    核心代码

    namespace RefitConsul
    {
        public class ConsulDiscoveryDelegatingHandler : DelegatingHandler
        {
            private readonly ConsulClient _consulClient;
            private readonly Func<Task<string>> _token;
            public ConsulDiscoveryDelegatingHandler(string consulAddress
                , Func<Task<string>> token = null)
            {
                _consulClient = new ConsulClient(x =>
                {
                    x.Address = new Uri(consulAddress);
                });
           //获取token的方法,可选参数
                _token = token;
            }
    
            protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request
                , CancellationToken cancellationToken)
            {
                var current = request.RequestUri;
                var cacheKey = $"service_consul_url_{current.Host }";
                try
                {
              //如果声明接口有验证头,在这里统一处理。
    var auth = request.Headers.Authorization; if (auth != null) { if (_token == null) throw new ArgumentNullException(nameof(_token)); var tokenTxt = await _token(); request.Headers.Authorization = new AuthenticationHeaderValue(auth.Scheme, tokenTxt); }
              //服务地址缓存3秒
    var serverUrl = CacheManager.GetOrCreate<string>(cacheKey, entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3); return LookupService(current.Host); }); request.RequestUri = new Uri($"{current.Scheme}://{serverUrl}{current.PathAndQuery}"); return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { CacheManager.Remove(cacheKey); throw; } finally { request.RequestUri = current; } } private string LookupService(string serviceName) {
    //根据服务名获取服务地址
    var servicesEntry = _consulClient.Health.Service(serviceName, string.Empty, true).Result.Response; if (servicesEntry != null && servicesEntry.Any()) {
              //目前只实现了随机轮询
    int index = new Random().Next(servicesEntry.Count()); var entry = servicesEntry.ElementAt(index); return $"{entry.Service.Address}:{entry.Service.Port}"; } return null; } } }

     如何使用

    Refit的基本用法就不记录了,重点写Refit集成Consul如何写代码。

    1、定义一个服务接口

        public interface IAuthApi
        {
            /// <summary>
            /// 不需要验证的接口
            /// </summary>
            /// <returns></returns>
            [Get("/sys/users")]
            Task <dynamic> GetUsers();
    
            /// <summary>
            /// 接口采用Bearer方式验证,Token在ConsulDiscoveryDelegatingHandler统一获取
            /// </summary>
            /// <returns></returns>
            [Get("/sys/session")]
            [Headers("Authorization: Bearer")]
            Task<dynamic> GetCurrentUserInfo();
    
            /// <summary>
            /// 接口采用Bearer方式验证,Token使用参数方式传递
            /// </summary>
            /// <returns></returns>
            [Get("/sys/session")]
            Task<dynamic> GetCurrentUserInfo([Header("Authorization")] string authorization);
        }

    2、在startup文件中注册Refit组件

            public void ConfigureServices(IServiceCollection services)
            {
                services.AddControllers();
    
                //重试策略
                var retryPolicy = Policy.Handle<HttpRequestException>()
                                        .OrResult<HttpResponseMessage>(response => response.StatusCode== System.Net.HttpStatusCode.BadGateway)
                                        .WaitAndRetryAsync(new[]
                                        {
                                            TimeSpan.FromSeconds(1),
                                            TimeSpan.FromSeconds(5),
                                            TimeSpan.FromSeconds(10)
                                        });
                //超时策略
                var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(5);
                //隔离策略
                var bulkheadPolicy = Policy.BulkheadAsync<HttpResponseMessage>(10, 100);
                //回退策略
                //断路策略
                var circuitBreakerPolicy = Policy.Handle<Exception>()
                               .CircuitBreakerAsync(2, TimeSpan.FromMinutes(1));
                //注册RefitClient
                //用SystemTextJsonContentSerializer替换默认的NewtonsoftJsonContentSerializer序列化组件
                //如果调用接口是使用NewtonsoftJson序列化则不需要替换
                services.AddRefitClient<IAuthApi>(new RefitSettings(new SystemTextJsonContentSerializer()))
                        //设置服务名称,andc-api-sys是系统在Consul注册的服务名
                        .ConfigureHttpClient(c => c.BaseAddress = new Uri("http://andc-api-sys"))
                        //注册ConsulDiscoveryDelegatingHandler,
                        .AddHttpMessageHandler(() =>
                        {
                            //http://12.112.75.55:8550是consul服务器的地址
                            //() => Helper.GetToken() 获取token的方法,是可选参数,如果不需要token验证不需要传递。
                            return new ConsulDiscoveryDelegatingHandler("http://12.112.75.55:8550", () => Helper.GetToken());
                        })
                        //设置httpclient生命周期时间,默认也是2分钟。
                        .SetHandlerLifetime(TimeSpan.FromMinutes(2))
                        //添加polly相关策略
                        .AddPolicyHandler(retryPolicy)
                        .AddPolicyHandler(timeoutPolicy)
                        .AddPolicyHandler(bulkheadPolicy);
            }

    3、如何在controller中调用

        public class HomeController : ControllerBase
        {
            private readonly IAuthApi _authApi;
            private readonly string _token = Helper.GetToken().Result;
    
            /// <summary>
            /// RefitConsul测试
            /// </summary>
            /// <param name="authApi">IAuthApi服务</param>
            public HomeController(IAuthApi authApi)
            {
                _authApi = authApi;
            }
    
            [HttpGet]
            public async Task<dynamic> GetAsync()
            {
                //不需要验证的服务
                var result1 = await _authApi.GetUsers();
    
                //需要验证,token采用参数传递
                var result2 = await _authApi.GetCurrentUserInfo($"Bearer {_token}");
    
                //需要验证,token在ConsulDiscoveryDelegatingHandler获取。
                var result3 = await _authApi.GetCurrentUserInfo();
    
                return result3;
            }
        }
    我改变不了世界,代码也改变不了。
  • 相关阅读:
    setState 是异步吗?
    React优化点滴
    JS原型,作用域,this,闭包
    Webpack 模块化打包优化
    JS异步编程
    Web网络安全
    Http2.0和Http3.0
    Http协议基础
    Harris算子以及未来的规划...
    剑指offer 二维数组查找
  • 原文地址:https://www.cnblogs.com/alphayu/p/13775249.html
Copyright © 2011-2022 走看看