Ocelot+Naco搭建的.net core 微服务下封装通用的服务之间的调用中间件

发布时间 2023-06-09 13:56:49作者: .net小峰

在 Ocelot + Nacos 搭建的 .NET Core 微服务中,可以封装一个通用的服务调用中间件,该中间件可以使用 Ocelot 作为 API 网关路由微服务请求,并通过 Nacos 服务发现来实现微服务的动态调用。

构建中间件ServiceProxyMiddleware

以下是一个构建通用服务调用中间件的示例代码:

public class ServiceProxyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IConfiguration _configuration;
    private readonly IServiceDiscoveryProvider _serviceDiscoveryProvider;

    public ServiceProxyMiddleware(RequestDelegate next, IConfiguration configuration, IServiceDiscoveryProvider serviceDiscoveryProvider)
    {
        _next = next;
        _configuration = configuration;
        _serviceDiscoveryProvider = serviceDiscoveryProvider;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // 获取请求服务名称
        var serviceName = context.Request.Headers["ServiceName"];

        if (string.IsNullOrEmpty(serviceName))
        {
            // 如果请求中没有ServiceName头,则直接跳过
            await _next(context);
            return;
        }

        // 获取服务实例地址
        var serviceInstances = await _serviceDiscoveryProvider.GetInstancesAsync(serviceName);

        // 轮询方式选择一个服务实例
        string serviceInstance = null;
        if (serviceInstances != null && serviceInstances.Count() > 0)
        {
            serviceInstance = serviceInstances.RandomOrDefault()?.ServiceUrl;
        }

        if (string.IsNullOrEmpty(serviceInstance))
        {
            context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return;
        }

        // 转发请求到微服务
        await ForwardRequestToServiceAsync(context, serviceInstance);

        await _next(context);
    }

    private async Task ForwardRequestToServiceAsync(HttpContext context, string serviceInstance)
    {
        using (var httpClient = new HttpClient())
        {
            httpClient.BaseAddress = new Uri(serviceInstance);

            var requestMessage = new HttpRequestMessage();
            requestMessage.Method = new HttpMethod(context.Request.Method);
            requestMessage.RequestUri = new Uri(httpClient.BaseAddress + context.Request.Path);
            requestMessage.Content = new StreamContent(context.Request.Body);
            if (context.Request.Headers != null)
            {
                foreach (var h in context.Request.Headers)
                {
                    requestMessage.Headers.TryAddWithoutValidation(h.Key, h.Value.ToArray());
                }
            }

            HttpResponseMessage response = await httpClient.SendAsync(requestMessage);

            // 将微服务的响应直接返回
            context.Response.StatusCode = (int)response.StatusCode;
            foreach (var header in response.Headers)
            {
                context.Response.Headers[header.Key] = header.Value.ToArray();
            }

            var stream = await response.Content.ReadAsStreamAsync();
            await stream.CopyToAsync(context.Response.Body);
        }
    }
} 

 

在上述代码中,ServiceProxyMiddleware 是微服务调用中间件的主体。当请求到达 API 网关时,该中间件会从传入请求的头部信息中获取服务名称,并使用 IServiceDiscoveryProvider 接口封装的服务发现功能获取服务的实例地址,并使用 HttpClient 将请求转发到微服务的实例地址,最后将微服务的响应直接返回到 API 网关。

我们还可以通过创建一个包含 IServiceDiscoveryProvider 接口的默认实现的服务集合,将 ServiceProxyMiddleware 用于微服务调用中间件的引用注入到程序的服务容器中。这样,在需要发出调用的应用程序中,可以轻松地使用相同的控件调用通用的服务调用中间件。

注入服务

例如,在 ConfigureServices 方法中,我们可以注册该服务:

public void ConfigureServices(IServiceCollection services)
{
    // 注入Nacos服务发现功能
    services.AddSingleton<IServiceDiscoveryProvider, NacosServiceDiscoveryProvider>(sp =>
    {
        var configuration = sp.GetService<IConfiguration>();
        var options = new NacosDiscoveryOptions();
        configuration.GetSection("Nacos").Bind(options);

        return new NacosServiceDiscoveryProvider(options);
    });

    // 注入服务调用中间件
    services.AddSingleton<ServiceProxyMiddleware>();
}

  最后,我们将 ServiceProxyMiddleware 用于微服务调用中间件的引用注入到程序的服务容器中,并在 Configure 方法中添加以下代码:

使用中间件

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 省略代码...

    // 使用服务调用中间件
    app.UseMiddleware<ServiceProxyMiddleware>();
}

  这样,在需要连接到任何微服务的应用程序中。

调用中间件

在需要连接到任何微服务的应用程序中,可以通过以下代码来调用通用服务调用中间件:

public class MyService
{
    private readonly HttpClient _httpClient;

    public MyService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    // 向指定微服务发起请求,其中 serviceName 是服务名称、apiPath 是 API 的地址,返回响应消息体 string 类型
    public async Task<string> GetMicroserviceResponseAsync(string serviceName, string apiPath)
    {
        var request = new HttpRequestMessage(HttpMethod.Get, apiPath);
        request.Headers.Add("ServiceName", serviceName);

        var response = await _httpClient.SendAsync(request);

        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsStringAsync();
    }
}

  

IServiceDiscoveryProvider接口的实现

IServiceDiscoveryProvider 接口定义了从服务发现源中获取微服务实例的方法。在 Ocelot + Nacos 架构中,我们可以创建一个接口实现,用于从 Nacos 服务注册中心中获取服务实例。

以下是一个基于 Nacos 的 IServiceDiscoveryProvider 接口实现:

public class NacosServiceDiscoveryProvider : IServiceDiscoveryProvider
{
    private readonly INacosNamingClient _namingClient;

    public NacosServiceDiscoveryProvider(NacosDiscoveryOptions options)
    {
        _namingClient = new NacosNamingClient(Options.Create(options));
    }

    public async Task<List<ServiceInstance>> GetInstancesAsync(string serviceName)
    {
        // 从 Nacos 服务注册中心获取指定服务名称的所有实例
        var instances = await _namingClient.ListInstances(serviceName);

        // 将 Nacos 服务注册中心返回的实例数据映射为 ServiceInstance 实体
        var serviceInstances = new List<ServiceInstance>();
        if (instances != null)
        {
            foreach (var instance in instances)
            {
                var serviceInstance = new ServiceInstance
                {
                    ServiceName = serviceName,
                    ServiceUrl = instance.ToUrlString()
                };
                serviceInstances.Add(serviceInstance);
            }
        }

        return serviceInstances;
    }
}

  

在上述代码中,NacosServiceDiscoveryProvider 是接口 IServiceDiscoveryProvider 的实现。在构造函数中,我们使用 NacosDiscoveryOptions 配置项初始化 NacosNamingClient,并将其保存为类成员变量。在 GetInstancesAsync 方法中,我们使用 _namingClient.ListInstances 方法从 Nacos 服务注册中心获取特定服务名称的所有实例,然后将实例数据转换为 ServiceInstance 实体,并使用实体列表封装的所有实例返回结果。

在上述代码中,ServiceInstance 是一个映射到服务实例的实体类,可以根据自己的需要自定义它的属性和逻辑。

 

以下是 ServiceInstance 类的一个示例定义:

public class ServiceInstance
{
    public string ServiceName { get; set; }
    public string ServiceUrl { get; set; }
}

  以上是基于 Nacos 的 IServiceDiscoveryProvider 接口实现,它可以从 Nacos 服务注册中心中获取服务实例,并在需要的时候使用这些实例作为目标微服务的地址。