• 聊一聊声明式接口调用与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

    如果您认为这篇文章还不错或者有所收获,可以点击右下角的【推荐】按钮,因为你的支持是我继续写作,分享的最大动力!
    声明: 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果您发现博客中出现了错误,或者有更好的建议、想法,请及时与我联系!!如果想找我私下交流,可以私信或者加我微信。
  • 相关阅读:
    Extjs打开window窗口自动加载html网页
    CSS预处理器之SASS用法指南
    HmacSHA256摘要算法
    Base64编解码
    孔子困于陈蔡故事(转载)
    我的2019
    给Oracle字段和表加注释
    【JDBC】使用properties连Oracle数据库,使用DatabaseMetaData获取字段的注释
    [JDBC]查询结果集把字段名和字段值一起竖向输出
    [Java/Reflect]使用反射机制获得一个对象的属性名和属性值
  • 原文地址:https://www.cnblogs.com/catcher1994/p/15542797.html
Copyright © 2020-2023  润新知