日常解惑-记录一次网关问题

前情摘要

网上下载了一个renren-fast-vue作为一个前端项目,用来方便管理后端项目,还下载了renren-fast项目用来为前端项目提供后端的支持(在本次问题中,可以理解为这个后台项目仅仅是用来提供验证码图片的)。同时自己还配置了nacos作为服务的发现中心。

renren-fast-vue的地址是:localhost:8001

renren-fast的地址是localhost:8080/renren-fast

网关的地址是localhost:12000

具体步骤

原先的renren-fast-vue和renren-fast这两个默认就是在一起进行工作的,即vue是去localhost:8080/renren-fast这个地址获取的验证码(及进行后端的相关操作)。

我们修改了renren-fast-vue的地址为,window.SITE_CONFIG['baseUrl'] = 'http://localhost:12000/api',也就是让它指向了配置好的网关,那么网关就需要将其转发到8080端口的renren-fast项目,并且需要进行路径的替换,进行配置的是如下所示:

1
2
3
4
5
6
7
routes:
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}

这条规则说的是,只要路径里面有/api/,就把它替换成在配置中心中的renren-fast的地址并且通过路径替换得到我们想要的真正的验证码地址。

简单来说就是,会将localhost:12000/api/captcha.jpg这个Url转化成localhost:8080/renren-fast/captcha.jpg,然后交给目标服务器来获取验证码图片地址。

最终上线进行测试,emmm,图片没加载出来。通过F12发现是404,也就是前端会发送localhost:12000/api/captcha.jpg这个请求,但是最终得不到验证码图片。

分析

大致分析一下可能出现问题的点:

  • 网关没有收到请求。
  • 网关收到了请求,但是没有进行相应的规则处理。
  • 网关进行了处理,但是最终得到的目标Url错误。
  • 因为网关会向nacos索要信息,也可能是nacos出现了问题。
  • 其它各种杂七杂八的问题。

检查网关

由于真的是刚刚接触spring cloud gateway这个网关,上面的这些问题其实只需要了解过程,然后看下日志就应该非常容易解决,我走了不少弯路,不过记录下这些弯路,也可以让以后的自己少踩点坑,也算划算了。

配置问题

首先我得排除我的网关配置的有没有问题,于是写了一个最简单的匹配规则,看能否生效,确实生效了,也就是说,我的网关至少在最简单的情况下,是可以正常工作的

我之后就把我的目标转向了上面贴出来的那个配置,既然网关是正常工作的,那么我自己的这个配置会不会出现问题呢?然后试了好多次,都不行。后来想想干脆破罐子破摔,我又复制了一条最简单的那个情况下的配置,然后只是修改了uri,发现居然不生效!真是奇了怪了。

就这样查找了好久的原因,我突然看到了bootstrap这个配置文件,也就是说,我现在其实用的根本不是本地的配置,而是用的nacos上的配置…..

然后把远程的这些配置关掉,确保用的是自己的本地的配置文件。发现还是404。

怀疑服务发现

接着由于之前的nacos的问题嘛,我就怀疑是不是我写的lb://renren-fast这里没有从nacos获取到相关的信息,这样导致了网关根本获取不到目的地的ip信息,自然也就无法跳转了。于是我修改了uri这条属性,让它指向了localhost:8080,然后试了一下,200!说明没问题哎!

走的远路

我测试了一下发现是200,自己以为解决了问题,但是刷新发现还是获得不了验证码。还是通过调试发现,获得的居然是document类型的,而且文件大小明显不对。这个就很奇怪了,首先,我认为这个配置的网关路由没有问题,它确实到了它该到的地方,而且对方也给了响应,是200。但是对方虽然给了响应,但是给的响应又不对,这就非常奇怪了。

之后我又断断续续查了不少资料,怀疑了以下这几点:

  • 是不是浏览器的缓存再捣乱? 为了以防万一,我装上了一个叫Cache Killer的插件来搞缓存。
  • 是不是idea的缓存没弄干净? 先删了targets文件夹,然后rebuild一下项目。
  • 是不是配置文件出了问题? 找个官方文档的,仔仔细细复制下来并且校对一遍。
  • 是不是防火墙的原因? 关闭防火墙再试试。

随着我的多次尝试,结果还是一样的。尝试了很久,都没有结果,我甚至开始怀疑起来是不是系统的问题….就在这里绕了很久很久。

走上正轨

搞到这里我觉得不能这样瞎试,不然变量实在是太大了,我为什么要测试这么长长的一条链路呢?我决定从结果开始往回推(其实我一开始确实是这么做的,但是搞了很久没搞出来,反而变量越搞越多,心里就越来越烦躁了…这里其实是静下心来重新理清思绪开始)。

首先我直接先清空了浏览器缓存,然后开始先测试下,我返回验证码的后端它有没有问题。直接在浏览器里输入localhost:8080/renren-fast/captcha.jpg,嗯,确实看到了回显,说明验证码的后端是没有问题的。然后倒退一步,走到网关,我输入localhost:12000/api/captcha.jpg,嗯,出现了404。冷静下来的我,决定先去看下gateway的日志,进入配置文件,输入

1
2
3
logging:
level:
root: debug

打开日志,然后进行调试。在日志里我看到了网关转发的日志,是正确的转化;但是其中有这么一条很奇怪:

1
[id: 0xf7ee8650, L:/0:0:0:0:0:0:0:1:12000 - R:/0:0:0:0:0:0:0:1:56659] Subscribing inbound receiver [pending: 0, cancelled:false, inboundDone: true]

虽然不敢断定是ipv6的锅,但是出于谨慎,我还是关闭了ipv6,发现还是404。

最后我试了下,发现如果通过localhost:12000/api/abcd,得到的结果是200,并不显示任何信息。但是我去访问了一下localhost:8080/renren-fast/abcd,发现得到的结果也是200,但是页面上会回显信息。在结合之前的日志,发现其实网关确实能把结果捕捉到,并且转发到正确的位置,但是对方就是给出不同的答案:一个给值,另外一个不给。

这样又持续了很久,我突然瞥了一眼日志,然后看了下浏览器的地址栏,这该不会是localhost和127.0.0.1之间的问题吧?然后我在浏览器里输入了127.0.0.1:8080/renren-fast/abcd,发现也没有回显!问题找到了!也就是说,对于这两个url,服务器给出的响应居然不同,那么它们俩到底有什么区别呢(其实我还测试了一下本机的内网ip地址)

接下来这句话是我在网上找的:

localhost就算没有网卡,仍然存在。

错误。我自己测试了一下,把lo网卡给down掉,ping 127和localhost都无法通,所以这条应该有问题。

按照之前我的理解,是通过读取/etc/hosts下的文件,然后把localhost转化成了127.0.0.1,从这点推论,也就是说,实际上应该127.0.0.1不能访问的,localhost都不能访问。但是在实际中却出现了127不能访问,但是localhost能访问的。

我继续去stackoverflow上找,看到了一个答案,似乎和我相关:

some applications will treat “localhost” specially. the mysql client will treat localhost as a request to connect to the local unix domain socket instead of using tcp to connect to the server on 127.0.0.1. This may be faster, and may be in a different authentication zone.

如果通过localhost,那么使用的是socket;如果用的127.0.0.1,那么使用tcp来访问。似乎确实能够完美解释哎!但是,我当时其实没有想到这一层…而且事后证明,也确实不是上面这个问题….

问题最后的解决

虽然我之前是没想到上面的这个不同软件对待localhost和127之间的不同,但是我决定修改server的绑定地址,通过显示指定绑定0.0.0.0,来看看效果——失败了。最后死马当活马医,改了个端口,居然成了!!

我特别好奇,于是去看了下端口绑定的东西。TMD居然有个nginx??!!看来是找到罪魁祸首了。

解释

我猜测最后的结果应该是这样,当我向localhost访问索要资源的时候,那个正确的后端返回了数据;而当我使用127的时候,这个时候是这个nginx在给我回消息。至于它们俩为什么能同时监听同一个端口,而且我的应用居然没有报错….这确实值得探索。

总结

这个问题从下午的6点开始,一直调试到第二天1点,历经7小时。其实仔细看来,稍微有点经验的应该分分钟就能搞定了…不过通过这次,还是学到了不少经验:

  • localhost和127之间的事,网上还不少错误的解释…
  • 下次用Postman来进行调试了,burp一直抓不到127的包…
  • 调试的时候心态要好,要有耐心。
  • 尽量控制变量。

所以还遗留一个问题:为什么nginx和我的tomcat能够同时监听8080端口而不报错呢?

这里我只能提供我的猜想了:nginx监听了ipv6的8080,且只能ipv6;而tomcat监听了ipv4的8080,这样首先它们是不冲突的。其次我访问localhost的时候,被映射成了ipv6,找到了nginx,但是nginx不知道为什么回复了一个200。然后我访问127的时候,只能去tomcat,然后tomcat拒绝了。

由于现在已经没有环境了,这个问题也无法复现了,但是确实提供了一个非常不错的思考过程。