Refit集成consul在asp.net core中的实践

  • A+
所属分类:.NET技术
摘要

github:https://github.com/AlphaYu/RefitConsulRefit、WebApiClient、Feign等都是支持声名式的Restful服务调用的开源组件。


前言

github:https://github.com/AlphaYu/RefitConsul

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;         }     }