on
k8s中通过服务名请求tomcat出现http 400
最近在给项目组进行 gitops 部署迁移,可谓是屎山蝶泳。就遇到了一个比较有意思的问题。
- 前端进行跨域调用后端域名
xx.super-api.com
时发生报错,响应码为 http 400。 - 排查 istio 之后发现是 java 代码在 Host 为服务名的时候会出现问题。
问题排查
- 首先尝试了去掉了 istio-proxy,但问题依旧。
- 通过别的容器通过服务名调用,直接调用
xx.super-api
请求 400,调用xx.super-api.svc.cluster.local
正常 - 在容器内直接通过 127.0.0.1 进行调用,服务正常。
- 在容器内直接通过 curl 127.0.0.1 进行调用,手动添加通过-H 配置 host。
容器内调用
在容器内手动设置 host 进行调用 curl -i -X POST 127.0.0.1 -H 'host:xx.super-api.com'
能正常响应,而调用curl -i -X POST 127.0.0.1:8080 -H 'host:xx.super-api'
则发生了报错。
源码分析
通过分析代码,解析验证 host 的主要代码位于org.apache.tomcat.util.http.parser.HttpParser.readHostDomainName
,这个方法利用DomainParseState
保存 host 进行的流程。在 tomcat 8.5.31
中,当 host 的最后一段遇到 -
时,则会抛出 host 不合法(如 xx.super-api.com
合法,而 xx.super-api
不合法)。
在 DomainParseState
的代码中,主要看 ALL_ALPHA
、ALPHA
、HYPHEN
的 allownEnd 取值。
当解析 xx.super-api
到-
时,state 会变成HYPHEN
,这时候再解析 e
,state 将会变成ALPHA
,最终完成解析时因为 state 为HYPHEN
且segmentIndex(当)大于 0,则会抛出异常,导致请求 400。
arthas 追踪请求参数
按照如上的源码分析,可以解释的通为什么服务内手动指定 host 调用时发生的异常。但前端跨越调用的情况下, host 应该是xx.super-api.com
,而这从源码看是一个合法的 host,为什么还会出现 400。通过 arthas 分析请求参数。
|
|
通过 curl,请求的 host 为 xx.super-api.com
,但同时通过 arthas 解析到的 host 为 xx.super-api
。推测是经过网关进行处理后变成了服务名,而不是原始的 host。
排查网关
通过追逐网关的流量。确定是因为 VirtualService 没有填写 host,导致流量无法正常使用 VirtualService 配置的规则,转发到了特殊的服务,这个特殊的兼容服务会通过服务名在调用到 svc,导致丢失 host。
总结
- 升级 tomcat 版本。项目使用的框架基于 sprinboot 1.5.14.RELEASE,内置的 tomcat 版本为 8.5.31,此版本的 tomcat 在对我们目前使用的服务名规则会判断为不合法导致请求 400,而稍微新一些的如 8.5.32 已经没有这个问题。
- VirtualService 配置 host,解决流量不能正确路由到 VirtualService 的问题。