本文主要是介绍request.getInputStream()只能被读取一次,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
需求背景:需要对每个Http请求都要记录入参和出参
在spring boot中,我们很容易就想到要用Interceptor拦截器去做拦截HttpServletRequest请求。
RequestLogInterceptor中实现preHandle()方法,然后在该方法内读取请求参数。RequestLogInterceptor.java 代码:
/*** 在SpringMVC中的 Interceptor拦截器才用实现 HandlerInterceptor的方式,实现其中的三个方法* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {long startTimeMillis = System.currentTimeMillis();//请求日志HttpRequestLog requestLog = new HttpRequestLog();// 获取并设置 RequestIDrequestLog.setRequestId(String.valueOf(HttpServletRequestUtil.getRequestID(request)));// 本次请求的 parentID = 上游 SpanIDrequestLog.setParentSpanId(HttpServletRequestUtil.getParentSpanID(request));// 当前请求的 SpanID = UpstreamSpanID + 1requestLog.setSpanId(HttpServletRequestUtil.getIncrSpanID(request));requestLog.setProto(request.getProtocol());requestLog.setMethod(request.getMethod());requestLog.setUrl(String.valueOf(request.getRequestURL()));requestLog.setApi(request.getRequestURI());requestLog.setClientIP(request.getRemoteAddr());requestLog.setClient(request.getHeader("User-Agent"));requestLog.setServer(AppConfig.systemCode);requestLog.setRequest(HttpServletRequestUtil.getRequestInfo(request));// c.Next() 之前无响应数据requestLog.setResponse(null);requestLog.setResult(RequestResult.SUCCESS);requestLog.setCreatedBy("0");requestLog.setCreatedName("");requestLog.setCreatedAt(String.valueOf(System.currentTimeMillis()));// 跟go不同,需要在request的全生命周期中保存requestLog、startTimeMillis变量RequestContextHolder.currentRequestAttributes().setAttribute("requestLog", requestLog, RequestAttributes.SCOPE_REQUEST);RequestContextHolder.currentRequestAttributes().setAttribute("startTimeMillis", startTimeMillis, RequestAttributes.SCOPE_REQUEST);return true;}/*** 从request获取请求参数或者请求参数multipart数据* @param request* @return* @throws ServletException* @throws IOException*/public static Request getRequestInfo(HttpServletRequest request) throws ServletException, IOException {Request requestLog = new Request();requestLog.setContentType(request.getContentType());requestLog.setContentLength(Long.parseLong(String.valueOf(request.getContentLength())));// request的请求头需要遍历Enumeration<String> headerNames = request.getHeaderNames();HashMap<String,String[]> headerMap = new HashMap<>(16);while (headerNames.hasMoreElements()) {String headerName = headerNames.nextElement();String headerValue = request.getHeader(headerName);headerMap.put(headerName,new String[]{headerValue});}// 获取所有的请求头requestLog.setHeader(headerMap);// 根据请求的 Content-Type 决定 Request 的取值if ("application/json".equals(request.getContentType())) {// 通过getInputStream() 获取请求体body里面的数据,因为request.getInputStream()只能被执行一次,因此做了RequestWrapper和RequestWrapperFilter去缓存servletInputStream(),这样controller层的@RequestBody就能读到body数据了ServletInputStream inputStream = request.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));String line;StringBuilder body = new StringBuilder();while ((line = reader.readLine()) != null) {body.append(line);}requestLog.setBody(body.toString());}else if (request.getContentType().contains("multipart/form-data")) {requestLog.setForm(request.getParts());}else {}return requestLog;}
但是问题是:我们都知道 request.getInputStream()只能被执行一次读取请求参数,下次再执行一次就会获得null。所以要怎么才能让Controller层的@RequestBody读取到请求参数并写给inputVO呢?
用RequestWrapperFilter。
通过getInputStream() 获取请求体body里面的数据,因为request.getInputStream()只能被执行一次,因此做了RequestWrapper和RequestWrapperFilter去缓存servletInputStream(),这样controller层的@RequestBody就能读到body数据了
RequestWrapper.java 代码如下:
public class RequestWrapper extends HttpServletRequestWrapper {private final String body;public RequestWrapper(HttpServletRequest request) {super(request);StringBuilder stringBuilder = new StringBuilder();BufferedReader bufferedReader = null;ServletInputStream inputStream = null;try {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();}@Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());ServletInputStream servletInputStream = new ServletInputStream() {@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) {}@Overridepublic int read() throws IOException {return byteArrayInputStream.read();}};return servletInputStream;}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(this.getInputStream()));}public String getBody() {return this.body;}
}
RequestWrapperFilter.java代码如下
@Component
@WebFilter(urlPatterns = "/**")
public class RequestWrapperFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {Filter.super.init(filterConfig);}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {ServletRequest requestWrapper = null;if(servletRequest instanceof HttpServletRequest) {requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);}if(requestWrapper == null) {//防止流读取一次就没有了,将流传递下去filterChain.doFilter(servletRequest, servletResponse);} else {filterChain.doFilter(requestWrapper, servletResponse);}}@Overridepublic void destroy() {Filter.super.destroy();}
}
这篇关于request.getInputStream()只能被读取一次的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!