完美解决request请求流只能读取一次的问题
作者:如来神掌十八式
解决request请求流只能读取一次的问题
实际开发碰到的问题
解决request请求流中的数据二次或多次使用问题
实际开发碰到的问题
springboot项目中,为了防止sql注入,采用Filter拦截器对所有请求流中的json数据进行校验,请求数据没问题则继续向下执行,在后边的代码中应用到请求参数值时,发现request中的json数据为空;
除上边描述的情况,尝试过两次从request中获取json数据,第二次同样是获取不到的。
解决request请求流中的数据二次或多次使用问题
继承HttpServletRequestWrapper,将请求体中的流copy一份,覆写getInputStream()和getReader()方法供外部使用。每次调用覆写后的getInputStream()方法都是从复制出来的二进制数组中进行获取,这个二进制数组在对象存在期间一直存在,这样就实现了流的重复读取。
//增强类 public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper { //保存流 private byte[] requestBody = null; public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); requestBody = StreamUtils.copyToByteArray(request.getInputStream()); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } @Override public BufferedReader getReader() throws IOException{ return new BufferedReader(new InputStreamReader(getInputStream())); } } //过滤器 @Component @WebFilter public class RequestSqlValidFilter implements Filter { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest)request); //请求参数合法,无sql注入 if((sqlValid(request, response))){ chain.doFilter(requestWrapper, response);//requestWrapper中保存着供二次使用的请求数据 }else { logger.error("RequestSqlValidFilter sqlValid param error"); } } @Override public void destroy() { }
补充知识:【java web】解决流读完一次就不能再次获取body数据的问题
问题来自我工作业务上的需求:前端请求时需要将json用RSA算法加密,数据经过后端过滤器进行自动解密,这样做的好处是以后不需要在每一个方法里都手动解密一次,增加代码的简洁性、可维护性。
但这样一来便会面临一个问题:http的request请求的输入流在过滤器中就已经被读取了(因为需要读取并解密request body 里被前端加密了的json数据),流只能被读取一次,这样一来数据便传不进controller里,导致接下来的业务无法进行。
于是我上网找了一些资料并成功解决了这个问题,基本思路是封装原生的HttpServletRequest请求,将其输入流里的数据保存在字节数组里,最后重写getInputStream方法,使其之后每次读取数据都是从字节数组里读取的。
第一步:写一个Request包装类BodyReaderHttpServletRequestWrapper
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper { private byte[] body; public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8")); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } public void setInputStream(byte[] body){ this.body = body; }
里面涉及一个HttpHelper类,顺便也贴出来
public class HttpHelper { /** * 获取请求Body * @param request * @return */ public static String getBodyString(ServletRequest request) { StringBuilder sb = new StringBuilder(); InputStream inputStream = null; BufferedReader reader = null; try { inputStream = request.getInputStream(); reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return sb.toString(); } }
第二步:编写包装Filter
这个filter类可以将经过的原生Request请求自动包装成BodyReaderHttpServletRequestWrapper
/** * desc : 用于包装原生request, 解决流读完一次就不能再次获取body数据的问题 * Created by Lon on 2018/3/9. */ public class RequestWrapperFilter implements Filter{ private static final Logger LOGGER = LoggerFactory.getLogger(RequestWrapperFilter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ServletRequest requestWrapper = null; if(request instanceof HttpServletRequest) { requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) request); } if(null == requestWrapper) { LOGGER.error("包装request失败!将返回原来的request"); chain.doFilter(request, response); } else { LOGGER.info("包装request成功"); chain.doFilter(requestWrapper, response); } } @Override public void destroy() { } }
第三步:在web.xml上配置过滤器
<filter> <filter-name>RequestWrapperFilter</filter-name> <filter-class>com.kx.security.filter.RequestWrapperFilter</filter-class> </filter> <filter-mapping> <filter-name>RequestWrapperFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
这里要注意的是这个包装过滤器可能要写在web.xml配置文件里某些过滤器的前面,比如解密过滤器,否则被解密过滤器先读取流的话,包装过滤器就读取不了流了。
至此一个简单的解决方法就完成啦!
以上这篇完美解决request请求流只能读取一次的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。