Open Closed

Microservice problem #8462


User avatar
0
dhaoo created
  • ABP Framework version: v8.1.3
  • UI Type: MVC
  • Database System: EF Core (Oracle,etc..)
  • Tiered (for MVC) or Auth Server Separated (for Angular): no
  • Exception message and full stack trace:
  • Steps to reproduce the issue: hi, I want to use gateway in conjunction with consul to automatically register services. Does ABP have any corresponding examples?

8 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    This is not abp scope. Microservice uses the yarp, you can check its document.

    Thanks.

  • User Avatar
    0
    dhaoo created

    hi

    This is not abp scope. Microservice uses the yarp, you can check its document.

    Thanks.

    hi, About yarp, abp uses the default to read the configuration file, but when I debug it, I find that yarp will automatically update swagger when the configuration file is changed. Is abp framework processed or yarp processed automatically? Now that I have changed yarp to use memory, I see when debugging that yarp's cluster has been updated, but swagger is not automatically updated

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Can you share the code of but when I debug it, I find that yarp will automatically update swagger when the configuration file is changed?

    abp will always set reloadOnChange to ture for appsettings.json.

  • User Avatar
    0
    dhaoo created
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Cors;
    using Microsoft.AspNetCore.Rewrite;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using LCFC.MOM.Shared.Hosting.AspNetCore;
    using LCFC.MOM.Shared.Hosting.Gateways;
    using Swashbuckle.AspNetCore.SwaggerUI;
    using Volo.Abp;
    using Volo.Abp.Modularity;
    using Yarp.ReverseProxy.Configuration;
    using Consul;
    using Autofac.Core;
    
    namespace LCFC.MOM.WebGateway;
    
    [DependsOn(
        typeof(MOMSharedHostingGatewaysModule)
    )]
    public class MOMWebGatewayModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            // Enable if you need hosting environment
            // var hostingEnvironment = context.Services.GetHostingEnvironment();
            var configuration = context.Services.GetConfiguration();
            var hostingEnvironment = context.Services.GetHostingEnvironment();
    
            //context.Services.AddSingleton<ISwaggerConfigHelper, SwaggerConfigHelper>();
           
            context.Services.AddSingleton<IConsulClient>(provider =>
            {
                var consulAddress = configuration["Consul:Address"] ?? "http://localhost:8500"; // 默认 Consul 地址
                return new ConsulClient(config => config.Address = new Uri(consulAddress));
            });
            UpdateRoutesFromConsul("http://localhost:8500", context);
            var consulClient = context.Services.BuildServiceProvider().GetRequiredService<IConsulClient>();
            SwaggerConfigurationHelper
                .ConfigureWithOidc(
                    context: context,
                    authority: configuration["AuthServer:Authority"]!,
                    scopes: consulClient.Agent.Services().Result.Response.Select(i=>i.Value.Service+"Service").ToArray(),
                    apiTitle: "Web Gateway API",
                    discoveryEndpoint: configuration["AuthServer:MetadataAddress"]
                );
            //context.Services.AddSingleton<IProxyConfigProvider, ProxyConfigProvider>();
            // context.Services.AddSingleton<ProxyConfigProvider>();
            // context.Services.AddSingleton<IProxyConfigProvider>(provider => provider.GetRequiredService<ProxyConfigProvider>());
            context.Services.AddCors(options =>
            {
                options.AddDefaultPolicy(builder =>
                {
                    builder
                        .WithOrigins(
                            configuration["App:CorsOrigins"]?
                                .Split(",", StringSplitOptions.RemoveEmptyEntries)
                                .Select(o => o.Trim().RemovePostFix("/"))
                                .ToArray() ?? Array.Empty<string>()
                        )
                        .WithAbpExposedHeaders()
                        .SetIsOriginAllowedToAllowWildcardSubdomains()
                        .AllowAnyHeader()
                        .AllowAnyMethod()
                        .AllowCredentials();
                });
            });
            //context.Services.AddHostedService<ConsulServiceWatcher>();
        }
        private List<AgentService> GetServicesFromConsul(string serviceName,ServiceConfigurationContext context)
        {
            var services = new List<AgentService>();
            var result = context.Services.BuildServiceProvider().GetRequiredService<IConsulClient>()
                .Agent.Services().Result.Response;
    
            foreach (var service in result)
            {
                services.Add(service.Value);
            }
    
            return services;
        }
        public void UpdateRoutesFromConsul(string serviceName,ServiceConfigurationContext context)
        {
            var services = GetServicesFromConsul(serviceName,context);
    
            var routes = new List<Yarp.ReverseProxy.Configuration.RouteConfig>();
            var clusters = new List<ClusterConfig>();
    
            foreach (var service in services)
            {
                // 更新 YARP 路由配置
                routes.Add(new Yarp.ReverseProxy.Configuration. RouteConfig
                {
                    RouteId = service.Service,
                    ClusterId = service.Service,
                    Match = new RouteMatch { Path = $"/api/{service.Service}-service/{{**catch-all}}" }
                });
                routes.Add(new Yarp.ReverseProxy.Configuration.RouteConfig
                {
                    RouteId = service.Service+ "Swagger",
                    ClusterId = service.Service,
                    Match = new RouteMatch { Path = $"/swagger-json/{service.Service}/swagger/v1/swagger.json" },
                    Transforms=new List<IReadOnlyDictionary<string, string>>()
                    {
                        new Dictionary<string, string>
                        {
                            { "PathRemovePrefix", $"/swagger-json/{service.Service}" }
                        }
                    }
                });
                // 更新 YARP 集群配置
                clusters.Add(new ClusterConfig
                {
                    ClusterId = service.Service,
                    Destinations = new Dictionary<string, Yarp.ReverseProxy.Configuration.DestinationConfig>
                    {
                        { service.Service, new Yarp.ReverseProxy.Configuration.DestinationConfig { Address = $"{service.Address}:{service.Port}/" } }
                    }
                });
            }
            context.Services.AddReverseProxy()
                 .LoadFromMemory(routes,clusters);
            // 更新代理配置
            //_proxyConfig.UpdateConfig(routes, clusters);
        }
        public override void OnApplicationInitialization(ApplicationInitializationContext context)
        {
            var app = context.GetApplicationBuilder();
            var env = context.GetEnvironment();
            var configuration = context.GetConfiguration();
            app.UseMiddleware<ConsulServiceWatcher>(); // 注册自定义中间件
            var proxyConfig = app.ApplicationServices.GetRequiredService<IProxyConfigProvider>().GetConfig();
    
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
    
            app.UseCorrelationId();
            app.UseStaticFiles();
            app.UseCors();
            app.UseRouting();
            app.UseAuthorization();
            app.UseSwagger();
            app.UseAbpSwaggerUI(options => { ConfigureSwaggerUI(proxyConfig, options, configuration); });
            app.UseRewriter(CreateSwaggerRewriteOptions());
            app.UseAbpSerilogEnrichers();
            app.UseEndpoints(endpoints => endpoints.MapReverseProxy());
    
        }
    
        private static void ConfigureSwaggerUI(
            IProxyConfig proxyConfig,
            SwaggerUIOptions options,
            IConfiguration configuration)
        {
            foreach (var cluster in proxyConfig.Clusters)
            {
                options.SwaggerEndpoint($"/swagger-json/{cluster.ClusterId}/swagger/v1/swagger.json",
                    $"{cluster.ClusterId} API");
            }
    
            options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
            options.OAuthScopes(proxyConfig.Clusters.Select(i=>i.ClusterId+"Service").ToArray()
            );
        }
    
        private static RewriteOptions CreateSwaggerRewriteOptions()
        {
            var rewriteOptions = new RewriteOptions();
            rewriteOptions.AddRedirect("^(|\\|\\s+)$", "/swagger"); // Regex for "/" and "" (whitespace)
            return rewriteOptions;
        }
    }
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net.Http;
    using System.Threading;
    using System.Threading.Tasks;
    using Consul;
    using LCFC.MOM.Shared.Hosting.AspNetCore;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Primitives;
    using Volo.Abp.Modularity;
    using Yarp.ReverseProxy.Configuration;
    
    namespace LCFC.MOM.WebGateway;
    
    public class ConsulServiceWatcher 
    {
        private readonly IConsulClient _consulClient;
        private readonly IHttpContextAccessor _httpContextAccessor;
        private List<AgentService> _lastServices = new List<AgentService>();
        private Timer _timer;
        private IConfiguration _configuration;
        private readonly RequestDelegate _next;
        private readonly InMemoryConfigProvider _inMemoryConfigProvider;
        public ConsulServiceWatcher(RequestDelegate next,IConsulClient consulClient, 
            IConfiguration configuration,IHttpContextAccessor httpContextAccessor)
        {
            _consulClient = consulClient;
            _httpContextAccessor = httpContextAccessor;
            _next = next;
            _configuration = configuration;
        }
        private void UpdateServices(object state)
        {
            // 获取 Consul 中的服务列表
            var services = GetServicesFromConsul();
    
            // 更新代理配置
            if (!services.Select(i=>i.Service).SequenceEqual(_lastServices.Select(i=>i.Service)))
            {
                // 如果服务有变化,更新路由
                _lastServices = services;
                UpdateRoutesFromConsul(services);
            }
        }
        public async Task InvokeAsync(HttpContext context)
        {
            // 初始化服务监控
            // 每隔 10 秒更新一次服务配置
            //_timer = new Timer(UpdateServices, null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
            UpdateServices(null);
            await _next(context);
        }
    
        private void UpdateRoutesFromConsul(List<AgentService> services)
        {
            var routes = new List<Yarp.ReverseProxy.Configuration.RouteConfig>();
            var clusters = new List<ClusterConfig>();
    
            foreach (var service in services)
            {
                // 更新 YARP 路由配置
                routes.Add(new Yarp.ReverseProxy.Configuration.RouteConfig
                {
                    RouteId = service.Service,
                    ClusterId = service.Service,
                    Match = new RouteMatch { Path = $"/api/{service.Service}-service/{{**catch-all}}" }
                });
    
                routes.Add(new Yarp.ReverseProxy.Configuration.RouteConfig
                {
                    RouteId = service.Service + "Swagger",
                    ClusterId = service.Service,
                    Match = new RouteMatch { Path = $"/swagger-json/{service.Service}/swagger/v1/swagger.json" },
                    Transforms = new List<IReadOnlyDictionary<string, string>>()
                    {
                        new Dictionary<string, string>
                        {
                            { "PathRemovePrefix", $"/swagger-json/{service.Service}" }
                        }
                    }
                });
    
                clusters.Add(new ClusterConfig
                {
                    ClusterId = service.Service,
                    Destinations = new Dictionary<string, Yarp.ReverseProxy.Configuration.DestinationConfig>
                    {
                        { service.Service, new Yarp.ReverseProxy.Configuration.DestinationConfig { Address = $"{service.Address}:{service.Port}/" } }
                    }
                });
            }
            
            var configProvider = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService<InMemoryConfigProvider>();
            configProvider.Update(routes, clusters);
            //configProvider.GetConfig()
            // 手动触发 ChangeToken
    
            var updatedConfig =_httpContextAccessor.HttpContext.RequestServices.GetRequiredService<IProxyConfigProvider>().GetConfig();
            //UpdateOAuthScopes(services.Select(i=>i.Service+"Service").ToArray());
            //UpdateSwaggerUI(updatedConfig);
        }
        private List<AgentService> GetServicesFromConsul()
        {
            var services = new List<AgentService>();
            var result = _consulClient.Agent.Services().Result.Response;
    
            foreach (var service in result)
            {
                services.Add(service.Value);
            }
    
            return services;
        }
        private void UpdateSwaggerUI(IProxyConfig proxyConfig)
        {
            if (proxyConfig.Clusters.Count == 0)
            {
                return;
            }
            // 动态更新 Swagger UI
            var app = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService<IApplicationBuilder>();
            app.UseSwagger();
            app.UseAbpSwaggerUI(options =>
            {
                foreach (var cluster in proxyConfig.Clusters)
                {
                    options.SwaggerEndpoint($"/swagger-json/{cluster.ClusterId}/swagger/v1/swagger.json", $"{cluster.ClusterId} API");
                }
            });
        }
    
    
    }
    
  • User Avatar
    0
    dhaoo created

    I am in configProvider.Update(routes, clusters); Then check that the value of ProxyConfig in IProxyConfigProvider has been attached

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You said: I find that yarp will automatically update swagger when the configuration file is changed?

    Does the abp template project not update swagger automatically?

    If so. Can you share a sample code of yarp that works? I will compare the code with the abp template project.

    I checked the document https://microsoft.github.io/reverse-proxy/articles/config-providers.html

    abp will always set reloadOnChange to ture for appsettings.json.

    Thanks.

  • User Avatar
    0
    dhaoo created

    hi

    You said: I find that yarp will automatically update swagger when the configuration file is changed?

    Does the abp template project not update swagger automatically?

    If so. Can you share a sample code of yarp that works? I will compare the code with the abp template project.

    I checked the document https://microsoft.github.io/reverse-proxy/articles/config-providers.html

    abp will always set reloadOnChange to ture for appsettings.json.

    Thanks.

    The code I shared above is the code in my current project, is reloadOnChange only for appsettings.json? I changed the yarp configuration file from yarp.json to memory in the code, I put in my middleware yarp configuration through the httpContext. RequestServices. GetRequiredService < InMemoryConfigProvider > (), Update (routes, clusters); Method was assigned and I checked that IProxyConfig had a value, but swagger didn't refresh. Do you know why

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    is reloadOnChange only for appsettings.json?

    The appsettings.json is add by asp net core.

    https://github.com/dotnet/aspnetcore/blob/main/src/DefaultBuilder/src/WebApplicationBuilder.cs#L273


    I don't know how Swagger and yarp work together. sorry for that. You can check the official sample of yarp.

Made with ❤️ on ABP v9.1.0-preview. Updated on December 13, 2024, 06:09