周五晚上,有同事反馈端上请求响应 400。应用没有输出业务日志,一时不好排查。上日志平台看了下,拦截器有输出日志,但是 controller 日志没有打印,基本可以确定请求入参格式匹配不上,将 入参的 @RequestBody 注解去掉后问题解决。
结合近段时间遇到的请求 400 问题,整理下此类问题,如何快速定位处理。
400 Bad Request
HTTP 400 状态码,表明客户端发送了一条异常请求。通常有两种类型:
1. 语义有误,当前请求无法被服务器理解。
客户端不应该在未经修改的情况下重复提交这个请求。
2. 请求参数有误。
案例分析
接下来通过几个具体案例来看下 400 的发生场景。
| 案例1:客户端 header 信息过大,导致 400
# 问题现象
客户端收到部分请求响应 400,服务端日志只收到了 options 请求,没有 post 请求。
# 问题处理
由于是少部分请求异常,且服务端没有查到业务日志,一开始处理起来比较缓慢,没有明确的思路该怎么处理。
在排除 options 请求响应异常,网关请求也正常的情况下。查询 应用服务器 nginx 日志,发现有 error 。同时,查询 tomcat 日志,发现有相应的错误输出。
结合端上近期改动,确认是某些场景下 header 信息会过大,超出 tomcat 阈值(tomcat 8 允许的http请求header的最大值是 8KB)。
问题定位后,就好处理了,端上对 header 中超长的字段信息进行优化,确保不会超出容器阈值,发布后问题解决。
|案例2:请求参数全部拼接到 url ,导致 400
# 问题现象
nginx 状态码监控发现有请求400,error 日志提示 "Connection reset by peer) while reading upstream"。
# 问题处理
一开始推测应该是请求 header 超出,但是由于是 spring boot 应用,没有独立的 tomcat 日志文件,不好判断。
实际上,spring boot 内嵌 tomcat ,容器日志会输出到应用日志中。如下:
2023-12-03 16:10:39,148 INFO [DirectJDKLog.log:182] [] -
Error parsing HTTP request header Note: further occurrences of HTTP header parsing errors will be logged at DEBUG level. java.lang.IllegalArgumentException:Request header is too large at org.apache.coyote.http11.Http11InputBuffer.fill(Http11InputBuffer.java:714
这些问题明确了,跟调用方确认,原来是他们把所有请求参数拼接到了 url 上,导致 header 超出。由于多数情况,入参比较少,即便都拼接到url 上,也不会超出 header 大小限制。这也解释了为什么只有部分请求才会 400。
调用方调整请求方式后,问题解决。
|案例3:应用程序入参格式不匹配 ,导致 400
第三个问题就是文章开头提到的,这个应用日志都输出了,排查起来比较简单。应用拦截器有输出日志,但是 controller 日志没有打印,可以确定请求入参格式匹配不上,将 入参的 @RequestBody 注解去掉后问题随即解决。
总结
线上环境,客户端发起的 http 请求一般会流经网关、应用服务器本地代理 nginx、web 容器(如 tomcat),最后到达应用程序。如下图。
http 请求访问链路