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

新版本 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. puTTY远程登录时,连接不上

    可能接收远程登录的SSH服务没启动 解决办法,控制台输入,service sshd start

  2. org.springframework.beans.factory.BeanCurrentlyInCreationException

    昨天下午的时候,给公司的项目打了个版,发现一直报502错误了,最后在服务器日志上看了一下异常信息,发现报了以下异常信息,导致项目启动就报错了(pc:该项目在我电脑本地启动不报错,之前也没报错). 错误 ...

  3. 如何将炫酷的报表直接截图发送邮件——在Superset 0.37使用Schedule Email功能

    Superset的图表是非常炫酷的,但是原来的版本只能在web端查看,而最新的0.37版本,可以将图表截图直接发送成邮件,非常的方便. 本文将详细介绍Superset 0.37 定时邮件功能.安装过程 ...

  4. java实现详细的身份证验证、能正确验证身份证正确性

    IdCardExpUtil.java package com.javazs.util; import java.text.ParseException; import java.text.Simple ...

  5. C++中的In 和 Out用法

    参考:https://zhidao.baidu.com/question/541219383.html In 这是一个宏,它的实际意义就是告诉你,这个变量或参数是输入值,即你必须给这个变量填写好以后提 ...

  6. JavaScript打印给定区间年份的闰年

    要求: 用户输入需要判断的年份区间,开始年份和结束年份,输出该区间内所有的闰年. 代码实现: function isRunYear(year) { // 是闰年返回true,否则返回false var ...

  7. RESTfull是什么

    经常做接口测试,会看很多接口文档,那怎么识别研发的接口设计是否足够规范,是否符合一些行业标准或准则.那认识了解RESTfull,可以让我们更具有专业性.让我们对接口文档的阅.接口合理性设计识别,做到有 ...

  8. 最新最最最简单的Axure傻瓜式破解版教程(带下载地址)

    Axure 破解版下载 下载地址在文章末尾 Axure是什么? Axure RP是一款专业的快速原型设计工具.Axure(发音:Ack-sure),代表美国Axure公司:RP则是Rapid Prot ...

  9. idea如何新建一个springmvc 工程

    java 版本 1.8.0_261 idea 版本2020.1 Tomcat 9  maven 3.6 新建工程 File->new->project 默认会下载springframewo ...

  10. android的adb命令整理

    adb.exe的路径在Android\Sdk\platform-tools 把这个路径加入到系统的path环境下. 先用usb连接设备,比如一台android手机 adb tcpip 5555 adb ...