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 的问题。