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

解决方案

从上面的源码中基本就可以分析出问题的原因来,解决的办法我觉得有下面几种:

  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

新版本 swagger 组件中 Servers 的 坑的更多相关文章

  1. Asp.Net Core WebApi中接入Swagger组件(初级)

    开发WebApi时通常需要为调用我们Api的客户端提供说明文档.Swagger便是为此而存在的,能够提供在线调用.调试的功能和API文档界面. 环境介绍:Asp.Net Core WebApi + S ...

  2. 在vue组件中style scoped中遇到的坑

    在uve组件中我们我们经常需要给style添加scoped来使得当前样式只作用于当前组件的节点.添加scoped之后,实际上vue在背后做的工作是将当前组件的节点添加一个像data-v-1233这样唯 ...

  3. vue js 在组件中对数组使用splice() 遇到的坑。。。

    遇到的问题: 用el-dialog写了个子组件 要实现在子组件中增删数据 点击确定后把值返回给父组件 父组件在每次点开子组件时都会把自己的值传进去. //父组件传值 this.$refs.transf ...

  4. 微信小程序中的自定义组件 以及 相关的坑

    Step1 我们初始化一个小程序(本示例基础版本库为 1.7 ),删掉里面的示例代码,并新建一个 components 文件夹,用于存放我们以后开发中的所用组件,今天我们的目的是实现一个 首页 组件, ...

  5. vue 组件中数组的更新

    今天写项目时遇到的问题,瞬间就卡在那了 来还原一下: parent.vue: <template> <div> <button @click="change&q ...

  6. angular2的ElementRef在组件中获取不到

    angular2的ElementRef在组件中获取不到 angular2不推荐操作dom,但是实际应用中不可避免的需要使用到dom操作,怎么操作,官方文档提供了一系列api(ElementRef,Vi ...

  7. vue中遇到的坑!!!!!

    一 .vue安装的坑 报错时的常见问题 1.cnpm install 模块名 –save-dev(关于环境的,表现为npm run dev 启动不了)cnpm install 模块名 –save(关于 ...

  8. 总结微信小程序开发中遇到的坑

    总结微信小程序开发中遇到的坑,一些坑你得一个一个的跳啊,/(ㄒoㄒ)/~~ 1,页面跳转和参数传递实例 首先说一下我遇到的需求有一个我的消息页面,里面的数据都是后端返回的,返回的数据大致如下,有一个是 ...

  9. vue中的css作用域、vue中的scoped坑点

    一.css作用域 之前一直很困扰css的作用域问题,即使是模块化编程下,在对应的模块的js中import css进来,这个css仍然是全局的.导致在css中需要加上对应模块的html的id/class ...

随机推荐

  1. 大数据平台Hadoop集群搭建

    一.概念 Hadoop是由java语言编写的,在分布式服务器集群上存储海量数据并运行分布式分析应用的开源框架,其核心部件是HDFS与MapReduce.HDFS是一个分布式文件系统,类似mogilef ...

  2. 深度预警:深入理解HBase的系统架构

    HBase的构成 物理上来说,HBase是由三种类型的服务器以主从模式构成的.这三种服务器分别是:Region server,HBase HMaster,ZooKeeper. 其中Region ser ...

  3. Redis 作者 Antirez 与 Contributor Mattsta 之间关于 CRC 的 Battle

    大家好,我是 yes. 昨天表弟说有个学妹问他 Redis 为什么要用 CRC16(key) mod 16384 来计算 key 所处槽的位置,我想这 CRC 一般都是用来校验的,通过多项式转换成二进 ...

  4. makefile从入门到入门

    makefile文件是用来帮助编译和管理C++项目代码的,需要配合make命令使用.makefile里也可以执行shell操作,具备一部分.sh脚本的功能. makefile格式 makefile内容 ...

  5. Java知识系统回顾整理01基础06数组01创建数组

    一.数组定义 定义:数组是一个固定长度的,包含了相同类型数据的 容器 二.声明数组 int[] a; 声明了一个数组变量. []表示该变量是一个数组 int 表示数组里的每一个元素都是一个整数 a 是 ...

  6. Burp时间到期之复活

    Burp昨天到期了,找了好久终于找到可以用的了,分享给大家. https://pan.baidu.com/s/1hsEhUYS r6ls PS:https://github.com/mxcxvn/Bu ...

  7. MATLAB鼠标事件

    来源:https://blog.csdn.net/weixin_39090239/article/details/80586930 前记: 人机交互的方式--键盘开关.鼠标.触摸屏.体感传感器(Kin ...

  8. 【题解】CF1426E Rock, Paper, Scissors

    题目戳我 \(\text{Solution:}\) 考虑第二问,赢的局数最小,即输和平的局数最多. 考虑网络流,\(1,2,3\)表示\(Alice\)选择的三种可能性,\(4,5,6\)同理. 它们 ...

  9. Geography's sum up

    1.世界气候: 热带草原气候,热带雨林气候,热带沙漠气候,热带草原气候 温带季风气候,温带大陆性气候,亚热带季风和湿润性气候,温带海洋性气候 寒带气候,高原山地气候. 2.亚洲气候: 1.大陆性气候分 ...

  10. python反序列化学习记录

    pickle与序列化和反序列化 官方文档 模块 pickle 实现了对一个 Python 对象结构的二进制序列化和反序列化. "pickling" 是将 Python 对象及其所拥 ...