新版本 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-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