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