接着上一节 使用 Yarp 做网关 (一),
完成上一节的练习后,还遗留了一个问题:
如何通过 YarpGateway 访问内部服务的Swagger呢?
外部访问 IdentityService 和 OrderService 是通过 网关:YarpGateway 访问的,使用者这个并不知道这个两个服务的具体地址,也就是不知道如何访问它们的 Swagger,那么:
如何通过 YarpGateway 访问这两个服务的Swagger呢?
使用网关内部服务的 Swagger 信息,其地址为:
http://ip:port/swagger/v1/swagger.json
例如,OrderService 服务的 Swagger 信息为:
http://localhost:7721/swagger/v1/swagger.json
在网关中使用内部服务的 Swagger 终点,再注册 Swagger 终点。
访问 OrderService 服务的 Swagger 信息地址:http://localhost:7711/swagger/v1/swagger.json
返回如下信息:(只列举部分数据)
{ "openapi": "3.0.1", "info": { "title": "Identity Service", "version": "v1" }, "paths": { "/api/identity/users": { "get": { "tags": [ "User" ], "responses": { "200": { "description": "Success", "content": { "text/plain": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/IdentityService.Models.User" } } }, "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/IdentityService.Models.User" } } }, "text/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/IdentityService.Models.User" } } } } } } }, .....
网关要请求内部服务的Swagger 信息,这是跨域请求,所以要求两个服务支持对网关的跨域请求。
在IdentityService 和 OrderService 项目中都做如下修改:
在 appsettins.json
文件中添加跨域配置:
{ "App": { "CorsOrigins": "http://localhost:7700" // 网关地址,支持网关的Yarp gatewary跨域请求 } }
其中,这个地址http://localhost:7700 就是网关的地址。
修改 Program.cs
文件:
代码清单:IdentityService/Program.cs
代码清单:OrderService/Program.cs
...... builder.Services.AddCors(options => { options.AddDefaultPolicy(builder => { builder .WithOrigins( configuration["App:CorsOrigins"] .Split(",", StringSplitOptions.RemoveEmptyEntries) .ToArray() ) .SetIsOriginAllowedToAllowWildcardSubdomains() .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); ...... app.UseRouting(); + app.UseCors(); // 添加跨域支持 app.UseSwagger(); app.UseSwaggerUI(); .....
在网关项目【YarpGateway】中做如下修改:
代码清单:YarpGateway/Program.cs
builder.Services.AddControllers(); //Web MVC ...... builder.Services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "Order Service", Version = "v1" }); options.DocInclusionPredicate((docName, description) => true); options.CustomSchemaIds(type => type.FullName); }); ...... // 添加内部服务的Swagger终点 app.UseSwaggerUIWithYarp(); //访问网关地址,自动跳转到 /swagger 的首页 app.UseRewriter(new RewriteOptions() // Regex for "", "/" and "" (whitespace) .AddRedirect("^(|\\|\\s+)$", "/swagger")); app.UseRouting();
其中,调用方法 app.UseSwaggerUIWithYarp(); 的目的是:添加内部服务的Swagger终点,其代码如下:
代码清单:YarpGateway/Extensions/YarpSwaggerUIBuilderExtensions.cs
using Yarp.ReverseProxy.Configuration; namespace YarpGateway.Extensions; public static class YarpSwaggerUIBuilderExtensions { public static IApplicationBuilder UseSwaggerUIWithYarp(this IApplicationBuilder app) { var serviceProvider = app.ApplicationServices; app.UseSwagger(); app.UseSwaggerUI(options => { var configuration = serviceProvider.GetRequiredService<IConfiguration>(); var logger = serviceProvider.GetRequiredService<ILogger<Program>>(); var proxyConfigProvider = serviceProvider.GetRequiredService<IProxyConfigProvider>(); var yarpConfig = proxyConfigProvider.GetConfig(); var routedClusters = yarpConfig.Clusters .SelectMany(t => t.Destinations, (clusterId, destination) => new { clusterId.ClusterId, destination.Value }); var groupedClusters = routedClusters .GroupBy(q => q.Value.Address) .Select(t => t.First()) .Distinct() .ToList(); foreach (var clusterGroup in groupedClusters) { var routeConfig = yarpConfig.Routes.FirstOrDefault(q => q.ClusterId == clusterGroup.ClusterId); if (routeConfig == null) { logger.LogWarning($"Swagger UI: Couldn't find route configuration for {clusterGroup.ClusterId}..."); continue; } options.SwaggerEndpoint($"{clusterGroup.Value.Address}/swagger/v1/swagger.json", $"{routeConfig.RouteId} API"); options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]); options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]); } }); return app; } }
关键代码:
options.SwaggerEndpoint($"{clusterGroup.Value.Address}/swagger/v1/swagger.json", $"{routeConfig.RouteId} API");
通过 IProxyConfigProvider 得到内部服务的信息,如下图所示:
然后,拼接出内部服务的 Swagger 信息地址,
$"{clusterGroup.Value.Address}/swagger/v1/swagger.json"
最终得到两个服务的Swagger信息地址:
http://localhost:7711/swagger/v1/swagger.json
http://localhost:7721/swagger/v1/swagger.json
最后,根据信息添加Swagger终点:
options.SwaggerEndpoint( $"{clusterGroup.Value.Address}/swagger/v1/swagger.json", $"{routeConfig.RouteId} API" );
其中,
routeConfig.RouteId
: Identity Service 或 Ordering Service
访问网地址:http://localhost:7700
自动跳转到地址:http://localhost:7700/swagger/index.html
右上角有个下拉框,可以选择不同的服务的Swagger,这里切换到 OrderService 的Swagger,如下图所示:
可以在网关 Swagger 调用内部服务接口,如下图所示:
返回: