完美解决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(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。