背景

对于公司内部的 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/get
info: 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的结合使用相关推荐

  1. 怎么用feign远程调用别人的接口_spring cloud-openFeign声明式远程调用

    spring cloud-openFeign声明式远程调用 1.概述1.1.是什么Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上申明注解Git ...

  2. SpringCloud Feign声明式服务调用

    SpringCloud Feign声明式服务调用 1. 加入pom依赖 2. Application.java上声明@EnableFeignClients 3. @FeignClient声明接口调用服 ...

  3. SpringCloud实战5-Feign声明式服务调用

    SpringCloud实战5-Feign声明式服务调用 在前面的文章中可以发现当我们通过RestTemplate调用其它服务的API时,所需要的参数须在请求的URL中进行拼接,如果参数少的话或许我们还 ...

  4. SpringCloud之声明式服务调用 Feign(三)

    一 Feign简介 Feign是一种声明式.模板化的HTTP客户端,也是netflix公司组件.使用feign可以在远程调用另外服务的API,如果调用本地API一样. 我们知道,阿里巴巴的doubbo ...

  5. springcloud(七)-Feign声明式REST调用

    前言 前面我们使用的RestTemplate实现REST API调用,代码大致如下: public User findById(@PathVariable Long id) {return restT ...

  6. 两个子集pom互相调用_声明式服务调用组件Feign

    什么是Feign?    Feign是SpringCloud组件中的一个轻量级RESTful的HTTP服务客户端.Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务. 什么是 ...

  7. Spring Cloud与微服务学习总结(11)——spring cloud-openFeign 声明式远程调用总结

    一.概述 1.1.是什么 Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上申明注解 Github:https://github.com/spri ...

  8. Spring Cloud Feign 1(声明式服务调用Feign 简介)

    Spring Cloud Feign基于Netflix Feign 同时整合了Spring Cloud Ribbon和Spring Cloud Hytrix,除了提供两者的强大功能外,它还提供了一种声 ...

  9. 04.声明式服务调用:Spring Cloud Feign(Greenwich.SR2)

    1.Feign是什么 Feign是整合了Ribbon与Hystrix外,还提供了声明式的Web服务客户端定义方式.采用了声明式API接口的风格,将Java Http客户端绑定到它的内部.Feign的首 ...

最新文章

  1. 最佳实践: 勿在 Servlet 中实现 SingleThreadModel
  2. 2015 年你在科研上有哪些值得分享的进展和感悟?
  3. 评估微型计算机的主要指标,微型计算机的工作过程和主要性能指标.doc
  4. Win Form登录机制的实现
  5. 漫画 | 程 序 员 脱 单 指 南
  6. Python操作redis(普通操作,连接池,封装)
  7. 分布式应用CAP理论
  8. Python学习记录——持续更新
  9. Qgis3.2编译移植成功文档完整版
  10. C++ wchar_t
  11. 【渝粤教育】国家开放大学2018年春季 3717-22T天然气管道长输技术 参考试题
  12. android 支付宝 记账本,使用支付宝记账----懒人的最佳记账模式
  13. xsd文件规则和语法
  14. obs源码分析【二】:录制功能剖析
  15. 有哪些管理类书籍值得推荐?
  16. 在移动通信中如何测试5G网络?
  17. js监听iframe是否加载完毕
  18. JavaScript基础知识总结 18:dom基本操作
  19. 最新vue实战视屏 vue2.x thinkPHP5.0后台 nodejs+mong0DB
  20. 计算机英语感想1500,英语学习心得1500字五篇

热门文章

  1. Android应用程序消息处理机制(Looper、Handler)分析(5)
  2. Simple TCP Server Client Socket C
  3. 关于wpf,datagrid,双向数据绑定用法解决方案
  4. js之 foreach, map, every, some
  5. springMVC--XML解析
  6. Server.MapPath()的用法
  7. bzoj1045 糖果传递
  8. [矩形并-扫描线-线段树]Picture
  9. JSFL 获取当前脚本路径,执行其他脚本
  10. InstallSield更新包快速入门文档----感谢原作者ㄣ齊¨彡仯乄的无私提供