使用 Yarp 做閘道器(二) : 閘道器 Swagger
Yarp & Swagger
目錄接著上一節 使用 Yarp 做閘道器 (一),
完成上一節的練習後,還遺留了一個問題:
如何通過 YarpGateway 訪問內部服務的Swagger呢?
問題:無法訪問內部服務 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 終點。
訪問: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
檔案
...... 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(); .....
閘道器新增 Swagger
在閘道器專案【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資訊地址:
- IdentityServer 的 Swagger 資訊地址:
http://localhost:7711/swagger/v1/swagger.json
- OrderService 的 Swagger 資訊地址:
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
訪問閘道器 Swagger
訪問網地址:http://localhost:7700
自動跳轉到地址:http://localhost:7700/swagger/index.html
右上角有個下拉框,可以選擇不同的服務的Swagger,這裡切換到 OrderService 的Swagger,如下圖所示:
在閘道器 Swagger 呼叫服務介面
可以在閘道器 Swagger 呼叫內部服務介面,如下圖所示:
返回: