新版本 Swashbuckle swagger 組件中的 "坑"

新版本 Swashbuckle swagger 組件中的 Servers 坑

Intro

上週作了公司的項目升級,從 2.2 更新到 3.1, swagger 直接更新到了最新,swagger 用的組件是 Swashbuckle.AspNetCore,而後遇到一個 swagger 的問題, 在本地測試是沒問題的,可是部署在測試環境以後就會有問題,主要是 swagger 界面會多一個 servers 的選項,可能會致使 swagger 不能正常使用,下面詳細介紹一下git

Swagger "bug" reproduce

大概的問題是這樣的,在本地環境是好的,在測試環境部署是有問題,測試環境部署以後的 swagger 界面大體以下:github

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

這會致使咱們使用 swagger 調試 API 的時候會走一個錯誤的 server 地址,實際請求的地址是 sever 地址加上 api path,能夠看一個示例app

Dig the Source

Swashbuckle.AspNetCore 是開源的,咱們就是扒一扒它的實現源碼吧,咱們用的是 5.6.3 版本,直接看 5.6.3 tag 對應的代碼,能夠找到 swagger 的中間件dom

https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/v5.6.3/src/Swashbuckle.AspNetCore.Swagger/SwaggerMiddleware.cside

在這裏咱們能夠看到,再返回給客戶端以前 open api 文檔響應以前咱們是能夠看到,是會通過 PreSerializeFilters 處理的,咱們再詳細看一下 swaggerProvider.GetSwagger 的實現測試

實現代碼在這裏(能夠經過服務註冊找到對應的實現,也能夠直接找對應接口的實現)ui

https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/v5.6.3/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L31spa

兩者結合來看,servers 會根據用戶請求來獲取一個 server 地址,而當有 X-Forwarded-Host 請求頭的時候若是沒有按照 swagger 指定的規則這樣進行請求頭的轉發就會致使有問題,而咱們的測試環境也正是由於如此,測試環境有一層 LB,通過 LB 轉發了 X-Forwarded-HostX-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;
}

解決方案

從上面的源碼中基本就能夠分析出問題的緣由來,解決的辦法我以爲有下面幾種:

  1. LB 轉發的時候帶上 X-Forwarded-Port 請求頭,轉發原始請求的端口號(須要 LB 轉發本身可以控制,咱們若是要配置還須要讓 DevOps 的童鞋幫忙弄,若是徹底是本身控制的就比較方便【推薦】)

  2. 在使用 Swagger 中間件以前把 X-Forwarded-Port 請求頭設置爲 443(不夠靈活,若是訪問 LB 是 http 或者有特別的端口號就會有問題)

  3. 在使用 swagger 中間件以前把 X-Forwarded-Host 請求頭移除掉,這樣就不會有 servers 這個屬性了(感受不夠優雅)

  4. 註冊一個 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

相關文章
相關標籤/搜索