新版本 Swashbuckle swagger 組件中 Servers 的 坑
Intro
上周做了公司的專案升級,從 2.2 更新到 3.1, swagger 直接更新到了最新,swagger 用的組件是 Swashbuckle.AspNetCore,然后遇到一個 swagger 的問題,
在本地測驗是沒問題的,但是部署在測驗環境之后就會有問題,主要是 swagger 界面會多一個 servers 的選項,可能會導致 swagger 不能正常使用,下面詳細介紹一下
Swagger "bug" reproduce
大概的問題是這樣的,在本地環境是好的,在測驗環境部署是有問題,測驗環境部署之后的 swagger 界面大致如下:

很明顯這個 servers 是有問題的,我們實際訪問的地址是 https://testserver/swagger 這樣的地址,但是 swagger 內部拼出來的 server 地址和實際訪問的地址是不符的,swagger 生成的 open api 檔案里也會有一個 servers 的屬性,示例如下:

這會導致我們使用 swagger 除錯 API 的時候會走一個錯誤的 server 地址,實際請求的地址是 sever 地址加上 api path,可以看一個示例

Dig the Source
Swashbuckle.AspNetCore 是開源的,我們就是扒一扒它的實作原始碼吧,我們用的是 5.6.3 版本,直接看 5.6.3 tag 對應的代碼,可以找到 swagger 的中間件
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/v5.6.3/src/Swashbuckle.AspNetCore.Swagger/SwaggerMiddleware.cs
在這里我們可以看到,再回傳給客戶端之前 open api 檔案回應之前我們是可以看到,是會經過 PreSerializeFilters 處理的,我們再詳細看一下 swaggerProvider.GetSwagger 的實作
實作代碼在這里(可以通過服務注冊找到對應的實作,也可以直接找對應介面的實作)
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/v5.6.3/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L31
二者結合來看,servers 會根據用戶請求來獲取一個 server 地址,而當有 X-Forwarded-Host 請求頭的時候如果沒有按照 swagger 指定的規則這樣進行請求頭的轉發就會導致有問題,而我們的測驗環境也正是因為如此,測驗環境有一層 LB,經過 LB 轉發了 X-Forwarded-Host 和 X-Forwarded-Proto 請求頭,但是沒有轉發 X-Forwarded-Port 所以經過 swagger 的處理之后,就從 https://testserver 變成了 https://testserver:80 這樣
private string GetHostOrNullFromRequest(HttpRequest request)
{
if (!request.Headers.TryGetValue("X-Forwarded-Host", out StringValues forwardedHost))
return null;
var hostBuilder = new UriBuilder($"http://{forwardedHost[0]}");
if (request.Headers.TryGetValue("X-Forwarded-Proto", out StringValues forwardedProto))
hostBuilder.Scheme = forwardedProto[0];
if (request.Headers.TryGetValue("X-Forwarded-Port", out StringValues forwardedPort))
hostBuilder.Port = int.Parse(forwardedPort[0]);
return hostBuilder.Uri.ToString().Trim('/');
}
private string GetBasePathOrNullFromRequest(HttpRequest request)
{
var pathBuilder = new StringBuilder();
if (request.Headers.TryGetValue("X-Forwarded-Prefix", out StringValues forwardedPrefix))
pathBuilder.Append(forwardedPrefix[0].TrimEnd('/'));
if (request.PathBase.HasValue)
pathBuilder.Append(request.PathBase.Value.TrimEnd('/'));
return (pathBuilder.Length > 0)
? pathBuilder.ToString()
: null;
}
解決方案
從上面的原始碼中基本就可以分析出問題的原因來,解決的辦法我覺得有下面幾種:
- LB 轉發的時候帶上
X-Forwarded-Port請求頭,轉發原始請求的埠號(需要 LB 轉發自己能夠控制,我們如果要配置還需要讓 DevOps 的童鞋幫忙弄,如果完全是自己控制的就比較方便【推薦】) - 在使用 Swagger 中間件之前把
X-Forwarded-Port請求頭設定為443(不夠靈活,如果訪問 LB 是 http 或者有特別的埠號就會有問題) - 在使用 swagger 中間件之前把
X-Forwarded-Host請求頭移除掉,這樣就不會有 servers 這個屬性了(感覺不夠優雅) - 注冊一個
PreSerializeFilter把 Servers 清空,實作代碼如下(【推薦】,沒有 servers 屬性的時候完全按請求 swagger 的 baseUrl 來作為 api 的前綴,示例代碼如下)
app.UseSwagger(c =>
{
c.PreSerializeFilters.Add((doc, _) =>
{
doc.Servers?.Clear();
});
});
更新之后就沒有 servers 屬性了,和之前的版本保持一致了
More
我們使用的是 5.6.3 版本,應該從 5.6.0 開始都有這個問題,如果遇到了這個問題不要慌哈,參考上面的解決方案即可
我覺得 swagger 這樣的實作方式不太友好,更好的實作應該結合微軟的 ForwardHeaders 中間件來實作,Swagger 組件作者表示已經有計劃,打算在 6.0 的時候更新結合微軟的中間件來實作,詳細可以參考 Github 上的 Issue https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1814
Reference
- https://github.com/domaindrivendev/Swashbuckle.AspNetCore
- https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/v5.6.3/src/Swashbuckle.AspNetCore.Swagger/SwaggerMiddleware.cs
- https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/v5.6.3/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L31
- https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1814
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/197779.html
標籤:.NET Core
上一篇:這種方法中的方法怎么寫
