一、What
最近又偶遇一诡异棘手之问题!
一个用于获取公钥数据的接口,不定期偶尔频频出现“参数不存在”的问题!
一度怀疑这是前端的锅,虽然前端同学再三以人格担保!
经过非仔细观察,发现每每出现问题时,“再点一下就好了”!
错误信息简单明确,是大家熟知的参数缺失异常:Required request parameter 'xxx' for method parameter type String is not present
But why? what's wrong?
Anyway,这是再普通不过的一个GET接口!
二、When
作为专业人士,咱自不能流于表象,当寻根溯源,探寻问题背后之本质!唯有寻得问题背后之根由,方可得正确解决问题之道!
Well, firstly, let's look look, what the hell a http go?
抱着“大胆假设,小心求证”之精神,以“抽丝剥茧”之排除大法,when the 参数 lost?!
- HTTP请求:传参正确,排除
- Nginx:接收正常,排除
- Gateway:url & queryString正常
- Controller:参数没了。。
So,What's up?
二、Where
种种迹象表明:问题当发生在服务内部而非外部网络或请求转发丢失!
进一步分析,发现在问题请求中request.queryString()正常,而request.getParameter()值却是没有获取到!
众所周知的,SpringBoot默认内置tomcat容器,SpringMVC则通过request.getParameter方法获取并绑定Controller接口参数!
因此,初步判断在tomcat获取parameter参数时出现了不明原因问题!
Well,parameter参数的获取过程是怎样的?
- SpringMVC框架通过DispatcherServlet实现
- Tomcat接收到外部请求,将由connector通过Processor受理http请求
- SpringMVC通过request.getParameter获取并绑定Controller接口参数
- request.getParameter方法在请求处理过程中仅在第一次调用时通过解析queryString获取parameters参数值,并设置didQueryParameter=true标识已解析处理
- Http请求处理完成,processor通过release方法释放连接重置参数属性,request.recycle方法重置request参数属性(注意:这里连接器及request对象并不会被销毁,connector再次受理新的请求时,将复用连接器、processor及request对象而非创建)
1、SpringBoot从request获取parameter参数
RequestParamMethodArgumentResolver#resolveName
2、tomcat封装解析参数
request通过Parameters获取parameter参数:
org.apache.catalina.connector.Request#getParameterValues
3、Parameters从queryString解析封装parameter参数
org.apache.tomcat.util.http.Parameters#handleQueryParameters
可以发现,参数在解析处理后会设置didQueryParameters参数为true,
4、请求处理结束,还原
五、Why
tomcat机制:
- tomcat可支持多个service实例(这不是重点)
- 每个service实例维护了一个connector池
- 当service接收到一个http请求时,则从连接池中获取connector进行响应处理
连接器Connector通过Processor对应http请求进行响应处理!
Processor封装了request与response对象:在请求处理开始时进行初始化封装(仅封装参数属性,并不创建对象),请求处理完成后则进行释放重置(仅重置参数属性,并不销毁对象)!
本次问题的根本原因则在于线程中引用了request对象,并在线程中调用了request.getParameter()方法使参数属性didQuerParameter错误而导致http请求无法正确获取参数值!
- 假设第一次受理http请求的连接器为connector1
- 请求request在子线程thread1中被引用
- connector1完成http请求并执行release释放连接,这时request.didQueryParameters值为false
- 如果子线程thread1处理任务的时间较长,调用了getParameter方法,这时request.didQueryParameters值将再次被更新为true
- 当tomcat再次通过connector1受理新的http请求时,由于request.didQueryParameters=true,这时新请求调用getParameter方法将不会再解析queryString,因而无法正确获取parameter参数值
测试验证问题,问题重现与预期一致!
六、How
修复线程中引用request相关代码,问题解决!
- 牢记tomcat复用connector这一机制,在编码过程中应特别注意!
- 不要在线程中引用request等任意tomcat相关组件或属性!
参考:spring boot偶发性丢失POST请求参数问题的解决 - checkboxMan的个人空间 - OSCHINA - 中文开源技术交流社区