聊一聊声明式接口调用与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 =>
{
// HttpApiOptions
x.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的结合使用的更多相关文章
- Spring Cloud07: Feign 声明式接口调用
一.什么是Feign Feign也是去实现负载均衡,但是它的使用要比Ribbon更加简化,它实际上是基于Ribbon进行了封装,让我们可以通过调用接口的方式实现负载均衡.Feign和Ribbon都是由 ...
- spring cloud 系列第4篇 —— feign 声明式服务调用 (F版本)
源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 一.feign 简介 在上一个用例中,我们使用ribbon+restTem ...
- springcloud-feign组件实现声明式的调用
11.使用feign实现声明式的调用 使用RestTemplate+ribbon已经可以完成对服务端负载均衡的调用,为什么还要使用feign? @RequestMapping("/hi&qu ...
- Spring Cloud Feign 声明式服务调用
目录 一.Feign是什么? 二.Feign的快速搭建 三.Feign的几种姿态 参数绑定 继承特性 四.其他配置 Ribbon 配置 Hystrix 配置 一.Feign是什么? 通过对前面Sp ...
- SpringCloud学习笔记(3):使用Feign实现声明式服务调用
简介 Feign是一个声明式的Web Service客户端,它简化了Web服务客户端的编写操作,相对于Ribbon+RestTemplate的方式,开发者只需通过简单的接口和注解来调用HTTP API ...
- Spring Cloud Eureka 分布式开发之服务注册中心、负载均衡、声明式服务调用实现
介绍 本示例主要介绍 Spring Cloud 系列中的 Eureka,使你能快速上手负载均衡.声明式服务.服务注册中心等 Eureka Server Eureka 是 Netflix 的子模块,它是 ...
- SpringCloud系列-利用Feign实现声明式服务调用
上一篇文章<手把手带你利用Ribbon实现客户端的负载均衡>介绍了消费者通过Ribbon调用服务实现负载均衡的过程,里面所需要的参数需要在请求的URL中进行拼接,但是参数太多会导致拼接字符 ...
- Spring Cloud第七篇 | 声明式服务调用Feign
本文是Spring Cloud专栏的第七篇文章,了解前六篇文章内容有助于更好的理解本文: Spring Cloud第一篇 | Spring Cloud前言及其常用组件介绍概览 Spring Cloud ...
- 6.使用Feign实现声明式REST调用
使用Feign实现声明式REST调用 6.1. Feign简介 Feign是一个声明式的REST客户端,它的目的就是让REST调用更加简单. Feign提供了H ...
随机推荐
- HTML 网页开发、CSS 基础语法——三. HTML概念
1.纯文本格式 纯文本格式,就是没有任何文本修饰的,没有任何粗体,下划线,斜体,图形,符号,或者特殊字符,及特殊打印格式的文本,只保存文本,不保存其格式设置. ①格式对比 纯文本格式 富文本格式 最常 ...
- 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 百篇博客分析OpenHarmony源码 | v44.02
百篇博客系列篇.本篇为: v44.xx 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...
- Win10环境下多JDK切换以及could not find java.dll异常解决
备注:主要为JDK1.7和JDK1.8之间进行切换 1.每次进行JDK切换时,都需要修改JAVA_HOME 2.编辑path环境变量,如图所示,将%JAVA_HOME%\jre\bin和%JAVA_H ...
- 实践node.js构建vue项目
一.首先安装下载node.js 1.Node.js 官方网站下载:https://nodejs.org/en/,自行选择合适自己的下载安装即可 2.验证安装 打开cmd,输入node –v和 npm ...
- mysql8 主从搭建
主:192.168.10.2 从:192.168.10.3 主:1.登录mysql,授权账号,让从数据库可以进行复制. mysql CREATE USER 'repl'@'192.168.10.3' ...
- Mysql读写分离集群的搭建且与MyCat进行整合
1. 概述 老话说的好:不熟悉的东西不要不懂装懂,做人要坦诚,知道就是知道,不知道就是不知道. 言归正传,今天我们来聊聊 Mysql主从读写分离集群是如何搭建的,并且聊一下如何用 MyCat 去访问这 ...
- React Native之新架构中的Turbo Module实现原理分析
有段时间没更新博客了,之前计划由浅到深.从应用到原理,更新一些RN的相关博客.之前陆续的更新了6篇RN应用的相关博客(传送门),后边因时间问题没有继续更新.主要是平时空余时间都用来帮着带娃了,不过还是 ...
- 题解 [BJOI2019]奥术神杖
题目传送门 题目大意 给出一个残缺的字符串,每个位置都 \(\in[0,9]\).有 \(m\) 中贡献,即 \(s,k\),表示该字符串中没出现一次 \(s\),贡献便乘上 \(k\).最后对贡献求 ...
- CF125E MST company (凸优化+MST)
qwq自闭的一个题 我来修锅辣!!!!!! 这篇题解!可以\(hack\)全网大部分的做法!!! 首先,我们可以把原图中的边,分成两类,一类是与\(1\)相连,另一类是不与\(1\)相连. 原题就转化 ...
- 笨方法学python中执行argv提示ValueError: not enough values to unpack (expected 4, got 1)
解决方法:选择Terminal中输入执行ex13.py 1 2 3 执行结果如下图