- 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)
-
0
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
-
0
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
toture
forappsettings.json
. -
0
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"); } }); } }
-
0
I am in configProvider.Update(routes, clusters); Then check that the value of ProxyConfig in IProxyConfigProvider has been attached
-
0
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
toture
forappsettings.json
.Thanks.
-
0
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
toture
forappsettings.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
-
0
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.