Shiro导致Request.getReader无法获取数据
kimmking@163.com
2013-12-23
今天发现一个很奇怪的问题,我们系统里用REST方式做前后端的访问,具体就是所有的请求都是POST,URL对应处理的action,HTTP body里的json是请求参数;后端程序里从Request.getReader拿到json参数,然后调用相应的action来处理。有一个登陆接口,在页面ajax和android里请求是正常的,但是在iphone里请求出错。调试了下,发现request.getReader拿不到数据。
通过tcpmon代理查看HTTP请求报文,对比下正确和错误的REST请求,感觉最大的问题可能是出现在Content-type上,进而发现application/x-www-form-urlencoded时,会出现这个问题,但是text/html,text/xml等格式都没有问题。这就能确定是Content-Type引起的了。
为什么Content-Type会引起这个问题呢?
我们知道request.getInputStream\getReader,都只能调用一次,调用过了,buffer里的数据就没有了。而request.getParameterXX方法也可能会解析buffer,如果这个调用过了,再调用getReader也会没有数据了。难道Content-Type不同的时候导致调用了这个?
调试下,追了追shiro和tomcat的代码,发现果然如此。
请求进来先进过shiro的filter,filter里试图拿到当前subject,先从cookie试着拿sessionId,不行就从URL中试着拿sessionId,还没有的话就从request.getParameter里获取sessionId,这里调用到了tomcat的request.getParameter实现:如果Content-Type是multipart/form-data或者application/x-www-form-urlencoded,则直接解析http body……问题就出现了。
简单的说就是,如果请求是application/x-www-form-urlencoded格式的话,shiro的filter在我们的servlet或action处理之前,就可能直接把request的body给读取并清空了。
通过这个实现我们也可以看到:除了这两个Content-Type之外的格式都不会有问题,登陆后sessionId放到cookie里了也不会有问题。
按理讲,我们用json应该手动设置Content-Type:application/json;因为application/x-www-form-urlencoded就是为了在body里使用类似QueryString的key-value格式。所以写代码的时候要注意了,如果某些HTTP库的实现里,默认POST是form格式的,如果你要自己处理HTTP body就需要手工的设置自己想要的Content-Type了。
知道怎么回事了,就很好处理了。在xcode里,将Content-Type改成text/html或application/json即可。
进一步的来看,shiro处理考虑到sessionId在cookie和url里,还考虑到一种情况:session藏在页面form的hidden里,通过form的post方式,把key-value作为http body来post到服务器端,shiro可能通过解析httpbody拿到sessionId,这个对session来说用处不大,因为我们一般都放到cookie或url的jsessionId里,但是对于某些需要自己处理http body的场景,shiro的filter显然破坏掉了后续对body的request.getReader读取。