前言
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; } }
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/160481.html
標籤:.NET技术
上一篇:求助,請大蝦們幫我看看,謝謝
