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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM