问题背景
HttpServletRequest.getReader()
HttpServletRequest.getInputStream() 不能在过滤器中读取一次二进制流(字符流),又在另外一个Servlet中读取一次,即一个InputSteam(BufferedReader)对象在被读取完成后,将无法再次被读取。二进制流被读取后,字节流/字符流的下标将发生变化,假如程序中重新调用一遍getReader/getInputStream 将会提示异常
解决方案
思路很简单,既然HttpServletRequest的请求体无法通过getReader/getInputStream再次调用,那我们只需要包装一层,通过Wrapper对象去集成HttpServletRequest的所有能力,并将请求体抽离出来,这样每次读取我们定义的请求体,问题就解决了。这个方法非常灵活。
画个流程图给大家解释下
通过上述流程图,可以得到关键的两个信息
1、定义包装类HttpServletRequestWrapper
2、全局过滤器filter,把HttpServletRequest包装成
ps:幸好的是,java语言设计者也考虑到了这种场景,已经帮我们准备好Wrapper类,直接集成使用即可。
代码示例
包装类定义(RepeatedlyReadRequestWrapper.java)
package com.whale.finance.filter;import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;/*** @author yanyq* @desc 重复读取HttpServletRequest reader/inputstream* @date 2023/7/27*/
public class RepeatedlyReadRequestWrapper extends HttpServletRequestWrapper {private final String body;/**** @param request*/public RepeatedlyReadRequestWrapper(HttpServletRequest request) throws IOException {super(request);StringBuilder sb = new StringBuilder();InputStream ins = request.getInputStream();BufferedReader isr = null;try{if(ins != null){isr = new BufferedReader(new InputStreamReader(ins));char[] charBuffer = new char[128];int readCount;while((readCount = isr.read(charBuffer)) != -1){sb.append(charBuffer,0,readCount);}}}catch (IOException e){throw e;}finally {if(isr != null) {isr.close();}}sb.toString();body = sb.toString();}@Overridepublic BufferedReader getReader() {return new BufferedReader(new InputStreamReader(this.getInputStream()));}@Overridepublic ServletInputStream getInputStream() {final ByteArrayInputStream byteArrayIns = new ByteArrayInputStream(body.getBytes());ServletInputStream servletIns = new ServletInputStream() {@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) {}@Overridepublic int read() {return byteArrayIns.read();}};return servletIns;}
}
过滤器定义(ReadBodyHttpServletFilter.java)
package com.whale.finance.filter;import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @author yanyq* @desc 重复读取HttpServletRequest reader/inputstream* @date 2023/7/27*/
@Component
@WebFilter("/*")
public class ReadBodyHttpServletFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {RepeatedlyReadRequestWrapper requestWrapper = new RepeatedlyReadRequestWrapper(httpServletRequest);filterChain.doFilter(requestWrapper, httpServletResponse);}@Overridepublic void destroy() {}
}
进行验证测试
/*** 测试request.getReader*/@PostMapping("/list")public void test(HttpServletRequest request) {request.getReader(); // 不报错return;}
Reference
https://blog.csdn.net/feeltouch/article/details/103275416