问题场景
现在服务上面有一个get请求,这个请求比较特殊,他的URL里面有一个属性的值包含大括号。现在这个请求通过postman把大括号进行一次encode然后调用到网关是没问题的。但是如果在内网环境通过ribbon去调用的话我进行一次encode以后网关这边会报400,也就是Bad Request。
排查
-
先确定一下这个请求是否到了网关
状态码为400的场景出现比较少,一般都会定性为网络问题,但是我们可以其他请求可以打过来,可以确定不是网络问题,但是网关没有任何日志输出,所以我们降低了网关输入日志的级别,发现在网关源码里面报错了,而且没有抛出来。但我们达到了目的,至少这个请求到了网关。
-
网关收到的请求有什么不一样的
我们对比了Postman和ribbon请求的不同,发现ribbon请求的URL根本没有进行编码,那这个时候就怀疑是不是feign进行了一次解码操作,如果进行了一次解码操作我们进行两次编码即可,但是经过测试发现两次编码以后他传到网关的时候,feign就不在进行解码操作了,这个时候就比较奇怪了,feign是如何知道编码一次还是两次呢?幸好可以本地调试,于是打上断点开始调试,最主要查看URL是从什么时候发生改变的。
-
定位解码位置
首先我们使用的feign的版本是8.18.0,最终定位到是在feign的RequestTemplate中的pullAnyQueriesOutOfUrl方法中
我们首先看到在548行,在这里queryLine里面存放了请求地址问号以后的请求参数,而parseAndDecodeQueries方法就是先对这些内容进行了一次解密操作,然后通过&符号进行切割得到一个map。接着在566行的时候他会把key和value放到一个全局的变量中,也就是queries。但是这里的query方法最终对调用到encodeIfNotVariable方法
我们可以看到如果value里面包含了大括号,他就不会进行再次编码了,这里就是我们上面现象的根本问题。
但是为什么要这么处理呢?我看到github上面也有好几个人提了这个问题,不过我看最新的版本依然没有修复。后来我看到RFC6570里面有规定
我们可以看到对于一些不安全字符是原封传递的。