Java如何解决发送Post请求报Stream closed问题
作者:胡安民
这篇文章主要介绍了Java如何解决发送Post请求报Stream closed问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
springboot项目还是ssm等java常用框架都会有这样的问题,解决办法通用
问题场景
前端发送Post请求,前端返回400 Bad Request,后端Controller层接口也没进去,然后我就开始分析,是啥问题,我通过后端控制台发现HttpMessageNotReadableException 提示信息,这个不是读取请求的消息错误发生的异常吗?
然后我通过IDEA 的DEBUG拦截这个异常发生的位置,然后将相关的代码从新走了一遍发现在
AbstractMessageConverterMethodArgumentResolver->readWithMessageConverters
EmptyBodyCheckingHttpInputMessage 是内部类
控制台打印warn 信息如下:
org.springframework.http.converter.HttpMessageNotReadableException: I/O error while reading input message; nested exception is java.io.IOException: Stream closed
问题分析
这是因为有人在过滤器或者拦截器中对Request的请求体中的数据流读取了一遍导致的,Springboot准备读取Body数据映射到接口的实体类参数时候失败,发现流已经没有内容了,因为在接口数据流传输使用的都是InputStream 这个流只能被读取一次
解决办法
将InputStream 传输数据,缓存起来,保存到字符串中,之后用的时候将字符串转在转换为流,那么这个字符串就能持续的被复用了
缓存数据
package com.schemautils; /** * 解决获取post请求的请求体body只能读取一次问题 */ import com.alibaba.fastjson.JSONObject; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; public class RequestWrapper extends HttpServletRequestWrapper { private final String body; public RequestWrapper(HttpServletRequest request) { super(request); StringBuilder stringBuilder = new StringBuilder(); BufferedReader bufferedReader = null; InputStream inputStream = null; try { //防止未初始化body,我们手动初始化body ,内部会将body内容初始化到InputStream里 request.getParameterMap(); //然后在读取InputStream inputStream = request.getInputStream(); if (inputStream != null) { bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); char[] charBuffer = new char[128]; int bytesRead = -1; while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { stringBuilder.append(charBuffer, 0, bytesRead); } } else { stringBuilder.append(""); } } catch (IOException ex) { } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } body = stringBuilder.toString(); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); ServletInputStream servletInputStream = new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return byteArrayInputStream.read(); } }; return servletInputStream; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } public JSONObject getBody() { return JSONObject.parseObject(this.body); } }
添加过滤器并且配置缓存类
package com.schemautils; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; //获取请求中的流,将取出来的,再次转换成流,然后把它放入到新request对象中 //必须保证在所有过滤器之前执行,否则就会出现问题(按照首字母进行过滤器优先级A>B>C) @WebFilter(filterName = "ACacheHttpServletRequestFilter", urlPatterns = "/") public class CacheHttpServletRequestFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { ServletRequest requestWrapper = null; if(servletRequest instanceof HttpServletRequest) { requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest); } //获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中 // 在chain.doFiler方法中传递新的request对象 if(null == requestWrapper) { filterChain.doFilter(servletRequest, servletResponse); } else { filterChain.doFilter(requestWrapper, servletResponse); } } @Override public void destroy() { } }
在启动类上开启扫描Filter注解
@SpringBootApplication(scanBasePackages = "com") @ServletComponentScan //开启扫描Filter public class ApplicatioBoot { public static void main(String[] args) { SpringApplication.run(ApplicatioBoot.class,args); } }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。