1. 程式人生 > >新版本 swagger 元件中 Servers 的 坑

新版本 swagger 元件中 Servers 的 坑

# 新版本 Swashbuckle swagger 元件中 Servers 的 坑 ## Intro 上週做了公司的專案升級,從 2.2 更新到 3.1, swagger 直接更新到了最新,swagger 用的元件是 `Swashbuckle.AspNetCore`,然後遇到一個 swagger 的問題, 在本地測試是沒問題的,但是部署在測試環境之後就會有問題,主要是 swagger 介面會多一個 servers 的選項,可能會導致 swagger 不能正常使用,下面詳細介紹一下 ## Swagger "bug" reproduce 大概的問題是這樣的,在本地環境是好的,在測試環境部署是有問題,測試環境部署之後的 swagger 介面大致如下: ![](https://img2020.cnblogs.com/blog/489462/202010/489462-20201031191424921-1405123249.png) 很明顯這個 servers 是有問題的,我們實際訪問的地址是 `https://testserver/swagger` 這樣的地址,但是 swagger 內部拼出來的 server 地址和實際訪問的地址是不符的,swagger 生成的 open api 文件裡也會有一個 servers 的屬性,示例如下: ![](https://img2020.cnblogs.com/blog/489462/202010/489462-20201031193726749-1357282092.png) 這會導致我們使用 swagger 除錯 API 的時候會走一個錯誤的 server 地址,實際請求的地址是 sever 地址加上 api path,可以看一個示例 ![](https://img2020.cnblogs.com/blog/489462/202010/489462-20201031192158370-1107183618.png) ## Dig the Source `Swashbuckle.AspNetCore` 是開源的,我們就是扒一扒它的實現原始碼吧,我們用的是 5.6.3 版本,直接看 5.6.3 tag 對應的程式碼,可以找到 swagger 的中介軟體 在這裡我們可以看到,再返回給客戶端之前 open api 文件響應之前我們是可以看到,是會經過 `PreSerializeFilters` 處理的,我們再詳細看一下 `swaggerProvider.GetSwagger` 的實現 實現程式碼在這裡(可以通過服務註冊找到對應的實現,也可以直接找對應介面的實現) 二者結合來看,servers 會根據使用者請求來獲取一個 server 地址,而當有 `X-Forwarded-Host` 請求頭的時候如果沒有按照 swagger 指定的規則這樣進行請求頭的轉發就會導致有問題,而我們的測試環境也正是因為如此,測試環境有一層 LB,經過 LB 轉發了 `X-Forwarded-Host` 和 `X-Forwarded-Proto` 請求頭,但是沒有轉發 `X-Forwarded-Port` 所以經過 swagger 的處理之後,就從 `https://testserver` 變成了 `https://testserver:80` 這樣 ``` csharp 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 的字首,示例程式碼如下) ``` csharp 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
## Reference - - - -