如何解决SpringBoot v2.2以上重复读取Request Body内容
这篇文章主要介绍"如何解决SpringBoot v2.2以上重复读取Request Body内容",在日常操作中,相信很多人在如何解决SpringBoot v2.2以上重复读取Request Body内容问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"如何解决SpringBoot v2.2以上重复读取Request Body内容"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
目录
SpringBoot v2.2以上重复读取Request Body内容
一、需求
二、解决方案
三、遇到问题
四、问题排查
解决方案
Springboot读取Request参数的坑
后端拿参数相关
关于流
SpringBoot v2.2以上重复读取Request Body内容
一、需求
项目有两个场景会用到从Request的Body中读取内容。
打印请求日志
提供Api接口,在api方法执行前,从Request Body中读取参数进行验签,验签通过后在执行api方法
二、解决方案
2.1 自定义RequestWrapper
public class MyRequestWrapper extends HttpServletRequestWrapper { private final String body; public MyRequestWrapper(HttpServletRequest request) throws IOException { super(request); this.body = RequestReadUtils.read(request); } public String getBody() { return body; } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes()); return new ServletInputStream() { ...略 }; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); }}
RequestReadUtils(网上抄的)
private static final int BUFFER_SIZE = 1024 * 8; public static String read(HttpServletRequest request) throws IOException { BufferedReader bufferedReader = request.getReader(); for (Enumerationiterator = request.getHeaderNames(); iterator.hasMoreElements();) { String type = iterator.nextElement(); System.out.println(type+" = "+request.getHeader(type)); } System.out.println(); StringWriter writer = new StringWriter(); write(bufferedReader,writer); return writer.getBuffer().toString(); } public static long write(Reader reader,Writer writer) throws IOException { return write(reader, writer, BUFFER_SIZE); } public static long write(Reader reader, Writer writer, int bufferSize) throws IOException { int read; long total = 0; char[] buf = new char[bufferSize]; while( ( read = reader.read(buf) ) != -1 ) { writer.write(buf, 0, read); total += read; } return total; }
2.2 定义Filter
@WebFilterpublic class TestFilter implements Filter{ @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain){ HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; MyRequestWrapper wrapper = WebUtils.getNativeRequest(request, MyRequestWrapper.class); chain.doFilter(wrapper == null ? new MyRequestWrapper(request) :wrapper,servletRequest); }}
三、遇到问题
使用的SpringBoot v2.1.x版本
Form提交无问题
获取RequestBody无问题
使用SpringBoot v2.2.0以上版本(包括v2.3.x)
Form提交无法获取参数
获取RequestBody无问题
四、问题排查
经过排查,v2.2.x对比v2.1.x的不同在于一下代码差异:
BufferedReader bufferedReader = request.getReader();-----------------char[] buf = new char[bufferSize];while( ( read = reader.read(buf) ) != -1 ) { writer.write(buf, 0, read); total += read;}
当表单提交时
v2.1.x无法read到内容,读取结果为-1
v2.2.x、v2.3.x能够读取到内容
当表单提交时(x-www-form-urlencoded),inputStream读取一次后后续不会触发wrapper的getInputStream操作,所以Controller无法获取到参数。
解决方案
MyRequestWrapper改造
public class MyRequestWrapper extends HttpServletRequestWrapper { private final String body; public MyRequestWrapper(HttpServletRequest request) throws IOException { super(request); this.body = getBodyString(request); } public String getBody() { return body; } public String getBodyString(final HttpServletRequest request) throws IOException { String contentType = request.getContentType(); String bodyString = ""; StringBuilder sb = new StringBuilder(); if (StringUtils.isNotBlank(contentType) && (contentType.contains("multipart/form-data") || contentType.contains("x-www-form-urlencoded"))) { MapparameterMap = request.getParameterMap(); for (Map.Entry next : parameterMap.entrySet()) { String[] values = next.getValue(); String value = null; if (values != null) { if (values.length == 1) { value = values[0]; } else { value = Arrays.toString(values); } } sb.append(next.getKey()).append("=").append(value).append("&"); } if (sb.length() > 0) { bodyString = sb.toString().substring(0, sb.toString().length() - 1); } return bodyString; } else { return IOUtils.toString(request.getInputStream()); } } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes()); return new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public int read() { return bais.read(); } @Override public void setReadListener(ReadListener readListener) { } }; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); }}
Springboot读取Request参数的坑
后端拿参数相关
默认配置时,
getInputStream()和getReader()一起使用会报错
使用两遍getInputStream(),第二遍会为空
当存在@RequestBody等注解时,springMVC已读取过一遍流,默认单独使用getInputStream()或getReader()都为空。
解决:写filter继承HttpServletRequestWrapper,缓存InputStream,覆盖getInputStream()和getReader()方法,使用ByteArrayInputStream is = new ByteArrayInputStream(body.getBytes());读取InputStream。
注意:springboot中,过滤器只需@Component即可生效,另外可在FilterRegistrationBean中配置路径和优先级。
对于拦截器,必须在InterceptorRegistry中调用addInterceptor方法。(路径可链式添加)
关于流
只能读一遍,类似管子。
只承担传输职责,而与处理和存储无关。
对于byte流而言,进行重复读取易于实现,但指针不重置,应是为了与InputStream接口定义保持一致。
到此,关于"如何解决SpringBoot v2.2以上重复读取Request Body内容"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!