聊一聊声明式接口调用与Nacos的结合使用
背景
對于公司內部的 API 接口,在引入注冊中心之后,免不了會用上服務發現這個東西。
現在比較流行的接口調用方式應該是基于聲明式接口的調用,它使得開發變得更加簡化和快捷。
.NET 在聲明式接口調用這一塊,有 WebApiClient 和 Refit 可以選擇。
前段時間有個群友問老黃,有沒有 WebApiClient 和 Nacos 集成的例子。
找了一圈,也確實沒有發現,所以只好自己動手了。
本文就以 WebApiClient 為例,簡單介紹一下它和 Nacos 的服務發現結合使用。
API接口
基于 .NET 6 創建一個 minimal api。
using?Nacos.AspNetCore.V2;var?builder?=?WebApplication.CreateBuilder(args);builder.Services.AddNacosAspNet(x?=> {x.ServerAddresses?=?new?List<string>?{?"http://localhost:8848/"?};x.Namespace?=?"cs";//?服務名這一塊統一用小寫!!x.ServiceName?=?"sample";x.GroupName?=?"Some_Group";x.ClusterName?=?"DC_1";x.Weight?=?100;x.Secure?=?false; });var?app?=?builder.Build(); . app.MapGet("/api/get",?()?=> {return?Results.Ok("from?.net6?minimal?API"); });app.Run("http://*:9991");這個應用是 provider,在啟動的時候,會向 Nacos 進行注冊,可以被其他應用發現并調用。
聲明式接口調用
這里同樣是創建一個 .NET 6 的 WEB API 項目來演示,這里需要引入一個 nuget 包。
<ItemGroup><PackageReference?Include="WebApiClientCore.Extensions.Nacos"?Version="0.1.0"?/> </ItemGroup>首先來聲明一下這個接口。
//?[HttpHost("http://192.168.100.100:9991")] [HttpHost("http://sample")] public?interface?ISampleApi?:?IHttpApi {[HttpGet("/api/get")]Task<string>?GetAsync(); }這里其實要注意的就是 HttpHost 這個特性,正常情況下,配置的是具體的域名或者是IP地址。
我們如果需要通過 nacos 去發現這個接口對應的真實地址的話,只需要配置它的服務名就好了。
后面是要進行接口的注冊,讓這個 ISampleApi 可以動起來。
var?builder?=?WebApplication.CreateBuilder(args);//?添加?nacos?服務發現模塊 //?這里沒有把當前服務注冊到?nacos,按需調整 builder.Services.AddNacosV2Naming(x?=> {x.ServerAddresses?=?new?List<string>?{?"http://localhost:8848/"?};x.Namespace?=?"cs"; });//?接口注冊,啟用?nacos?的服務發現功能 //?注意分組和集群的配置 //?builder.Services.AddNacosDiscoveryTypedClient<ISampleApi>("Some_Group",?"DC_1"); builder.Services.AddNacosDiscoveryTypedClient<ISampleApi>(x?=>? {//?HttpApiOptionsx.UseLogging?=?true; },?"Some_Group",?"DC_1");var?app?=?builder.Build();app.MapGet("/",?async?(ISampleApi?api)?=> {var?res?=?await?api.GetAsync();return?$"client?=====?{res}"?; });app.Run("http://*:9992");運行并訪問 localhost:9992 就可以看到效果了
從上面的日志看,它請求的是 http://sample/api/get,實際上是 http://192.168.100.220:9991/api/get,剛好這個地址是注冊到 nacos 上面的,也就是服務發現是生效了。
info:?System.Net.Http.HttpClient.ISampleApi.LogicalHandler[100]Start?processing?HTTP?request?GET?http://sample/api/get info:?System.Net.Http.HttpClient.ISampleApi.ClientHandler[100]Sending?HTTP?request?GET?http://sample/api/getinfo:?Microsoft.AspNetCore.Hosting.Diagnostics[1]Request?starting?HTTP/1.1?GET?http://192.168.100.220:9991/api/get?-?- info:?Microsoft.AspNetCore.Routing.EndpointMiddleware[0]Executing?endpoint?'HTTP:?GET?/api/get'下面來看看 WebApiClientCore.Extensions.Nacos 這個包做了什么。
簡單剖析
本質上是加了一個 HttpClientHandler,這個 handler 依賴于 sdk 提供的 INacosNamingService。
public?static?IHttpClientBuilder?AddNacosDiscoveryTypedClient<TInterface>(this?IServiceCollection?services,Action<HttpApiOptions,?IServiceProvider>?configOptions,string?group?=?"DEFAULT_GROUP",string?cluster?=?"DEFAULT")where?TInterface?:?class,?IHttpApi {NacosExtensions.Common.Guard.NotNull(configOptions,?nameof(configOptions));return?services.AddHttpApi<TInterface>(configOptions).ConfigurePrimaryHttpMessageHandler(provider?=>{var?svc?=?provider.GetRequiredService<INacosNamingService>();var?loggerFactory?=?provider.GetService<ILoggerFactory>();if?(svc?==?null){throw?new?InvalidOperationException("Can?not?find?out?INacosNamingService,?please?register?at?first");}return?new?NacosExtensions.Common.NacosDiscoveryHttpClientHandler(svc,?group,?cluster,?loggerFactory);}); }在 handler 里面重寫了 SendAsync 方法,替換了 HttpRequestMessage 的 RequestUri,也就是把服務名換成了真正的服務地址。
protected?override?async?Task<HttpResponseMessage>?SendAsync(HttpRequestMessage?request,?CancellationToken?cancellationToken) {var?current?=?request.RequestUri;try{request.RequestUri?=?await?LookupServiceAsync(current).ConfigureAwait(false);var?res?=?await?base.SendAsync(request,?cancellationToken).ConfigureAwait(false);return?res;}catch?(Exception?e){_logger?.LogDebug(e,?"Exception?during?SendAsync()");throw;}finally{//?Should?we?reset?the?request?uri?to?current?here?//?request.RequestUri?=?current;} }具體查找替換邏輯如下:
internal?async?Task<Uri>?LookupServiceAsync(Uri?request) {//?Call?SelectOneHealthyInstance?with?subscribe//?And?the?host?of?Uri?will?always?be?lowercase,?it?means?that?the?services?name?must?be?lowercase!!!!var?instance?=?await?_namingService.SelectOneHealthyInstance(request.Host,?_groupName,?new?List<string>?{?_cluster?},?true).ConfigureAwait(false);if?(instance?!=?null){var?host?=?$"{instance.Ip}:{instance.Port}";//?conventions?here//?if?the?metadata?contains?the?secure?item,?will?use?https!!!!var?baseUrl?=?instance.Metadata.TryGetValue(Secure,?out?_)??$"{HTTPS}{host}":?$"{HTTP}{host}";var?uriBase?=?new?Uri(baseUrl);return?new?Uri(uriBase,?request.PathAndQuery);}return?request; }這里是先查詢一個健康的實例,如果存在,才會進行組裝,這里有一個關于 HTTPS 的約定,也就是元數據里面是否有 Secure 的配置。
大致如下圖:
寫在最后
聲明式的接口調用,對Http接口請求,還是很方便的,可以多試試。
nacos-sdk-csharp 的地址 :https://github.com/nacos-group/nacos-sdk-csharp
nacos-csharp-extensions 的地址:https://github.com/catcherwong/nacos-csharp-extensions
本文示例代碼的地址 :https://github.com/catcherwong-archive/2021/tree/main/WebApiClientCoreWithNacos
總結
以上是生活随笔為你收集整理的聊一聊声明式接口调用与Nacos的结合使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 持续集成、持续交付(CI/CD)开篇,先
- 下一篇: RabbitMQ 处理过慢,原来是一个