写点什么

OpenFeign 引起的 HTTP Status 400 与 Tomcat 吞没数据

作者:Java-fenn
  • 2022 年 9 月 12 日
    湖南
  • 本文字数:2248 字

    阅读完需:约 7 分钟

OpenFeign 拦截器

在微服务中比较常见的场景:前端带了 JWT 令牌请求服务 A,在服务 A 中使用 Feign 远程调用服务 B、服务 C 等,A、B、C 都接入了 Spring Security;此时就会存在这样的需求,如服务 A 调用服务 B、C 时不带有 JWT 令牌就会出现服务调用失败,无法通过服务 B、C 鉴权认证;

此时需要通过 Feign 提供的 RequestInterceptor 拦截器将 A 请求头中所持有的 Token 在 Feign 发起远程调用时继续传递给服务 B、服务 C;

Demo 示例代码:

public class DemoRequestInterceptor implements RequestInterceptor {
private final BearerTokenResolver tokenResolver;@Overridepublic void apply(RequestTemplate template) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request= attributes.getRequest(); //获取当前请求header Enumeration<String> headerNames = request.getHeaderNames(); if (headerNames != null) { while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); String values = request.getHeader(name); //将header沿Feign调用链传递 template.header(name, values); } } }}
复制代码

导致的问题

这种简单粗暴全部将 Header 向下传递的方法将出现意想不到的副作用,导致程序请求调用失败;

上面 Demo 代码,如发生如上图所示的服务调用,将产生服务调用失败,HTTP 400 异常;

以下分析环境为 Spring Boot2.7,使用内置 tomcat 9.0.65;

  1. 客户端 Put 请求,content-length 等于 74

  2. 全部拷贝 UpdateA 请求所携带的 Header 头,服务 A 发起 feign 调用服务 B,Get 请求;

  3. 服务 A 发起 feign 调用服务 C,Put 请求;

服务 A 调用服务 B 成功完成请求,在服务 A 继续发起的 feign 调用服务 C 出现如下异常,Tomcat 无法解析该请求,抛出异常,此时请求头已经被破坏:

java.lang.IllegalArgumentException: Invalid character found in method name [late, ]. HTTP method names must be tokensat org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:421)
复制代码

从异常中明显可以看出 Tomcat 在解析请求行时出现异常,但具体什么原因引起的还将要 debug Tomcat 源码才能发现问题所在;

问题跟踪

在 Tomcat 中从抛出异常的类处添加日志输出,跟踪问题;

1、在 Http11InputBuffer 类的 parseRequestLine 方法中添加打印日志:可看到两个请求的请求头信息:

服务 A 对服务 B 发起的 Get 请求信息,如上所述,Header 头全部带上了:

2022-09-10 15:50:21.642 DEBUG 12028 --- [nio-8085-exec-5] o.a.coyote.http11.Http11InputBuffer :2:parsingRequestLinePhase--Received [GET /infos? pageSize=10&pageNo=1 HTTP/1.1accept: */*accept-encoding: gzip, deflate, brauthorization: Bearer admin11::ab5050d3-3542-4ada-80e9- 6241132de5e5cache-control: no-cacheconnection: keep-alivecontent-length: 74content-type: application/jsoncookie: JSESSIONID=3A1FD18830F43E295F07E8F77C0FA9FFhost: 127.0.0.1:7730postman-token: fe8cfff4-e10b-4286-98da-d6d7bc05c006user-agent: PostmanRuntime/7.29.2
复制代码

服务 A 对服务 C 发起的 Put 请求信息,但请求头并不完整;请求行已不见,请求头也只剩部分,请求体完整,这也是为什么抛出请求行解析失败的原因;

2022-09-10 15:50:39.126 DEBUG 12028 --- [nio-8085-exec-5] o.a.coyote.http11.Http11InputBuffer      :2:parsingRequestLinePhase--Received [late, brauthorization: Bearer admin11::ab5050d3-3542-4ada-80e9- 6241132de5e5cache-control: no-cacheconnection: keep-alivecookie: JSESSIONID=3A1FD18830F43E295F07E8F77C0FA9FFhost: 127.0.0.1:7730postman-token: fe8cfff4-e10b-4286-98da-d6d7bc05c006user-agent: PostmanRuntime/7.29.2Content-Type: application/jsonContent-Length: 34
{"aaabbbbId":123,"varray":["123"]}]
复制代码

第一个 Get 请求,虽然附加了 content-length: 74,但并不影响请求;第二个 Put 请求,请求头直接被破坏;通过 private boolean fill(boolean block)方法的日志发现,Put 请求接收时 HTTP 请求头还是完整的;

在 Parameters 类的 processParameters 方法中并没有对 Get 请求读取 Content-Length 长度的数据,目前只能从 Get 请求完成之后、Put 头解析之前的代码进行跟踪分析,还是在 Http11InputBuffer 类中,在每个请求结束之后都会执行如下方法:

/*** 结束请求,消耗剩余字节**/void endRequest() throws IOException {   //吞没机制是否开启    if (swallowInput && (lastActiveFilter != -1)) {        int extraBytes = (int) activeFilters[lastActiveFilter].end();        byteBuffer.position(byteBuffer.position() - extraBytes);    }}
复制代码

此方法将会根据吞没机制配置与 remaining 字节数对 byteBuffer 数据进行吞没;

根据吞没配置与上个请求完成后所剩余的字节数 remaining,对数据 byteBuffer 数据进行吞没;

在此可看到吞没掉的字节数为 74,为第一个请求头所携带的 contentLength 数据,第二个 Put 请求头所丢失的数据;

如 Feign 调用为 Get、Put、Put 请求;Get、Get、Get 请求将不会有异常情况出现,上述异常情况为:Put、Get、Put 请求;之所以 remaining 会剩余 74 是因为 Tomcat 并不会对 Get 请求从 byteBuffer 读取 Content-Length 所传输的字节数据;当请求没有导致吞没机制发生时就不会出现异常情况;

用户头像

Java-fenn

关注

需要Java资料或者咨询可加我v : Jimbye 2022.08.16 加入

还未添加个人简介

评论

发布
暂无评论
OpenFeign引起的HTTP Status 400与Tomcat吞没数据_Java_Java-fenn_InfoQ写作社区