Must it be of svg type
Thanks for your reply.
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
I am in configProvider.Update(routes, clusters); Then check that the value of ProxyConfig in IProxyConfigProvider has been attached
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");
}
});
}
}
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
ok,thanks!