完美解决request请求流只能读取一次的问题
解决request请求流只能读取一次的问题
实际开发碰到的问题
解决request请求流中的数据二次或多次使用问题
实际开发碰到的问题
springboot项目中,为了防止sql注入,采用Filter拦截器对所有请求流中的json数据进行校验,请求数据没问题则继续向下执行,在后边的代码中应用到请求参数值时,发现request中的json数据为空;
除上边描述的情况,尝试过两次从request中获取json数据,第二次同样是获取不到的。
解决request请求流中的数据二次或多次使用问题
继承HttpServletRequestWrapper,将请求体中的流copy一份,覆写getInputStream()和getReader()方法供外部使用。每次调用覆写后的getInputStream()方法都是从复制出来的二进制数组中进行获取,这个二进制数组在对象存在期间一直存在,这样就实现了流的重复读取。
//增强类 publicclassBodyReaderHttpServletRequestWrapperextendsHttpServletRequestWrapper{ //保存流 privatebyte[]requestBody=null; publicBodyReaderHttpServletRequestWrapper(HttpServletRequestrequest)throwsIOException{ super(request); requestBody=StreamUtils.copyToByteArray(request.getInputStream()); } @Override publicServletInputStreamgetInputStream()throwsIOException{ finalByteArrayInputStreambais=newByteArrayInputStream(requestBody); returnnewServletInputStream(){ @Override publicintread()throwsIOException{ returnbais.read(); } @Override publicbooleanisFinished(){ returnfalse; } @Override publicbooleanisReady(){ returnfalse; } @Override publicvoidsetReadListener(ReadListenerreadListener){ } }; } @Override publicBufferedReadergetReader()throwsIOException{ returnnewBufferedReader(newInputStreamReader(getInputStream())); } } //过滤器 @Component @WebFilter publicclassRequestSqlValidFilterimplementsFilter{ privatefinalLoggerlogger=LoggerFactory.getLogger(this.getClass()); @Override publicvoidinit(FilterConfigfilterConfig)throwsServletException{ } @Override publicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{ ServletRequestrequestWrapper=newBodyReaderHttpServletRequestWrapper((HttpServletRequest)request); //请求参数合法,无sql注入 if((sqlValid(request,response))){ chain.doFilter(requestWrapper,response);//requestWrapper中保存着供二次使用的请求数据 }else{ logger.error("RequestSqlValidFiltersqlValidparamerror"); } } @Override publicvoiddestroy(){ }
补充知识:【javaweb】解决流读完一次就不能再次获取body数据的问题
问题来自我工作业务上的需求:前端请求时需要将json用RSA算法加密,数据经过后端过滤器进行自动解密,这样做的好处是以后不需要在每一个方法里都手动解密一次,增加代码的简洁性、可维护性。
但这样一来便会面临一个问题:http的request请求的输入流在过滤器中就已经被读取了(因为需要读取并解密requestbody里被前端加密了的json数据),流只能被读取一次,这样一来数据便传不进controller里,导致接下来的业务无法进行。
于是我上网找了一些资料并成功解决了这个问题,基本思路是封装原生的HttpServletRequest请求,将其输入流里的数据保存在字节数组里,最后重写getInputStream方法,使其之后每次读取数据都是从字节数组里读取的。
第一步:写一个Request包装类BodyReaderHttpServletRequestWrapper
publicclassBodyReaderHttpServletRequestWrapperextendsHttpServletRequestWrapper{ privatebyte[]body; publicBodyReaderHttpServletRequestWrapper(HttpServletRequestrequest)throwsIOException{ super(request); body=HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8")); } @Override publicBufferedReadergetReader()throwsIOException{ returnnewBufferedReader(newInputStreamReader(getInputStream())); } @Override publicServletInputStreamgetInputStream()throwsIOException{ finalByteArrayInputStreambais=newByteArrayInputStream(body); returnnewServletInputStream(){ @Override publicintread()throwsIOException{ returnbais.read(); } @Override publicbooleanisFinished(){ returnfalse; } @Override publicbooleanisReady(){ returnfalse; } @Override publicvoidsetReadListener(ReadListenerreadListener){ } }; } publicvoidsetInputStream(byte[]body){ this.body=body; }
里面涉及一个HttpHelper类,顺便也贴出来
publicclassHttpHelper{ /** *获取请求Body *@paramrequest *@return */ publicstaticStringgetBodyString(ServletRequestrequest){ StringBuildersb=newStringBuilder(); InputStreaminputStream=null; BufferedReaderreader=null; try{ inputStream=request.getInputStream(); reader=newBufferedReader(newInputStreamReader(inputStream,Charset.forName("UTF-8"))); Stringline=""; while((line=reader.readLine())!=null){ sb.append(line); } }catch(IOExceptione){ e.printStackTrace(); }finally{ if(inputStream!=null){ try{ inputStream.close(); }catch(IOExceptione){ e.printStackTrace(); } } if(reader!=null){ try{ reader.close(); }catch(IOExceptione){ e.printStackTrace(); } } } returnsb.toString(); } }
第二步:编写包装Filter
这个filter类可以将经过的原生Request请求自动包装成BodyReaderHttpServletRequestWrapper
/** *desc:用于包装原生request,解决流读完一次就不能再次获取body数据的问题 *CreatedbyLonon2018/3/9. */ publicclassRequestWrapperFilterimplementsFilter{ privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(RequestWrapperFilter.class); @Override publicvoidinit(FilterConfigfilterConfig)throwsServletException{ } @Override publicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{ ServletRequestrequestWrapper=null; if(requestinstanceofHttpServletRequest){ requestWrapper=newBodyReaderHttpServletRequestWrapper((HttpServletRequest)request); } if(null==requestWrapper){ LOGGER.error("包装request失败!将返回原来的request"); chain.doFilter(request,response); }else{ LOGGER.info("包装request成功"); chain.doFilter(requestWrapper,response); } } @Override publicvoiddestroy(){ } }
第三步:在web.xml上配置过滤器
RequestWrapperFilter com.kx.security.filter.RequestWrapperFilter RequestWrapperFilter /*
这里要注意的是这个包装过滤器可能要写在web.xml配置文件里某些过滤器的前面,比如解密过滤器,否则被解密过滤器先读取流的话,包装过滤器就读取不了流了。
至此一个简单的解决方法就完成啦!
以上这篇完美解决request请求流只能读取一次的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。