解决spring http输入流和输出流只能读取一次
1.需求:给某些请求接口记录日志,记录请求的数据和响应的数据和请求所花费的时间。这里采用非侵入式编程,也业务代码进行解耦。按照spring AOP 的编程思想。
2.编程设计:在spring 拦截器中植入日志代码。因为其刚好满足非侵入,且能捕获到请求和响应的数据。
3.了解spring 拦截器和过滤器的运行原理
先执行过滤器,然后执行拦截器。
4. 分析:当在拦截器中获取请求的输入流和响应的输出流的时候发现,只能读取一次,拦截器在具体的业务代码之前执行,导致请求的输入流被拦截器使用,到controller 的时候将获取不到数据。当响应的数据流被拦截器的postHandler 使用之后,输出到客户端没有响应的数据。
流程:先创建过滤器,过滤器是链式执行,配置的web.xml 的中。
WrapperFilter 在web.xml 中的配置
<filter> <filter-name>sosWrapperFilter</filter-name> <filter-class>com.xiao.handler.filter.WrapperFilter</filter-class> </filter> <filter-mapping> <filter-name>sosWrapperFilter</filter-name>
<!-- 过滤的url地址 -->
<url-pattern>/myTest/*</url-pattern> </filter-mapping>
增加一个拦截器配置,配置需要过滤器的请求地址,走 spring mvc的拦截器,拦截的是请求地址(不包含上下文 eg:http://localost:8080/xiao)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/test/*"/> <bean class="com.xiao.interceptor.SosOrderInterceptor"/> </mvc:interceptor> </mvc:interceptors> </beans>
开始编写过滤器,拦截器
过滤器
/** * * @describ request 请求过滤器增强 * @date 2019-03-06 * @author coder_xiao */ public class WrapperFilter implements Filter { private static Log log = LogFactory.getLog(WrapperFilter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { CacheContentRequestWrapper requestWrapper=null; CacheContentResponseWrapper responseWrapper=null; HttpServletRequest request=(HttpServletRequest)servletRequest; HttpServletResponse response=(HttpServletResponse) servletResponse; try { requestWrapper = new CacheContentRequestWrapper(request); responseWrapper = new CacheContentResponseWrapper(response); }catch (Exception e){ log.error("获取requestWrapper失败!",e); }finally { if((requestWrapper!=null)&&(responseWrapper!=null)) { filterChain.doFilter(requestWrapper, responseWrapper); responseWrapper.reWriteResponse(responseWrapper,(HttpServletResponse) servletResponse); }else{ filterChain.doFilter(servletRequest, servletResponse); } } } @Override public void destroy() { } }
拦截器
public class SosOrderInterceptor implements HandlerInterceptor { private final static Log logger = LogFactory.getLog(SosOrderInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception { BufferedReader bufferedInput=null; String data=null; try{ String urlPath=request.getRequestURI(); String contentType=request.getContentType(); Boolean typeIndex=contentType==null?false:contentType.indexOf("application/json")>-1; Boolean createIndex=urlPath.indexOf("createOrder")>-1; Boolean buildIndex=urlPath.indexOf("buildDraftOrder")>-1; Boolean urlIndex=(urlPath.indexOf("createSosOrder")+urlPath.indexOf("buildDraftSosOrder"))>-2; //POST 请求 contentType=application/json if(typeIndex&&urlIndex) { //支持mark and reset 为使流复用 CacheContentRequestWrapper contentRequestWrapper= new CacheContentRequestWrapper(request); data = contentRequestWrapper.getBodyString(); String requestId=UUIDUtil.uuid(); request.setAttribute("requestId",requestId); BuildAudit audit=BeanTool.getBean(BuildAudit.class); //创建订单 if (createIndex) { logger.info("开始调用创建订单"); audit.buildData(data,"1","1","创建订单",requestId); } //创建草稿 if (buildIndex) { logger.info("创建草稿单!"); audit.buildData(data,"2","1","创建草稿单",requestId); } } }catch (Exception e){ e.printStackTrace(); logger.error(e); }finally { if(bufferedInput!=null) { bufferedInput.close(); } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object o, ModelAndView modelAndView) throws Exception { logger.info("postHandle"); try{ String urlPath=request.getRequestURI(); String contentType=request.getContentType(); Boolean typeIndex=contentType==null?false:contentType.indexOf("application/json")>-1; Boolean createIndex=urlPath.indexOf("createOrder")>-1; Boolean buildIndex=urlPath.indexOf("buildDraftOrder")>-1; Boolean urlIndex=(urlPath.indexOf("createSosOrder")+urlPath.indexOf("buildDraftOrder"))>-2; String requestId= request.getAttribute("requestId")==null?null:request.getAttribute("requestId").toString(); if(typeIndex&&urlIndex) { CacheContentResponseWrapper cacheResponse =(CacheContentResponseWrapper)((WebStatFilter.StatHttpServletResponseWrapper) response).getResponse(); String data=new String(cacheResponse.getResponseData(),"UTF-8"); BuildAudit audit=BeanTool.getBean(BuildAudit.class); //创建订单 if (createIndex) { audit.buildData(data,"1","2",null,requestId); } //创建草稿 if (buildIndex) { audit.buildData(data,"2","2",null,requestId); } //草稿单转正 if (urlPath.indexOf("buildDraftSosOrder") > -1) { } } }catch (Exception e){ logger.error(e); } } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { logger.info("afterCompletion"); try{ }catch (Exception ex){ logger.error(ex); } }
两个缓存器
package com.lacesar.handler.wrapper; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * @description 包装spring 的 HttpServletRequestWrapper */ public class CacheContentRequestWrapper extends HttpServletRequestWrapper { private static Log log = LogFactory.getLog(CacheContentRequestWrapper.class); /** * 存储body数据的容器 */ private final byte[] body; private String requestId; public CacheContentRequestWrapper(HttpServletRequest request) throws IOException { super(request); // 将body数据存储起来 String bodyStr = getBodyString(request); body = bodyStr.getBytes("UTF-8"); } /** * 获取请求Body * * @param request request * @return String */ public String getBodyString(final ServletRequest request) { try { return inputStream2String(request.getInputStream()); } catch (IOException e) { log.error("", e); throw new RuntimeException(e); } } /** * 获取请求Body * * @return String */ public String getBodyString() { if (body != null) { final InputStream inputStream = new ByteArrayInputStream(body); return inputStream2String(inputStream); } else { return null; } } /** * 将inputStream里的数据读取出来并转换成字符串 * * @param inputStream inputStream * @return String */ private String inputStream2String(InputStream inputStream) { StringBuilder sb = new StringBuilder(); BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); String line; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { log.error("", e); throw new RuntimeException(e); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { log.error("", e); } } } return sb.toString(); } public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream inputStream = new ByteArrayInputStream(body); return new ServletInputStream() { public int read() throws IOException { return inputStream.read(); } public boolean isFinished() { return false; } public boolean isReady() { return false; } public void setReadListener(ReadListener readListener) { } }; } public String getRequestId() { return requestId; } public void setRequestId(String requestId) { this.requestId = requestId; } }
package com.lacesar.handler.wrapper; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; public class CacheContentResponseWrapper extends HttpServletResponseWrapper { private ByteArrayOutputStream buffer = null; private ServletOutputStream out = null; private PrintWriter writer = null; public CacheContentResponseWrapper(HttpServletResponse resp) throws IOException { super(resp); buffer = new ByteArrayOutputStream(); out = new WapperedOutputStream(buffer); writer = new PrintWriter(new OutputStreamWriter(buffer, "UTF-8")); } /** * 重新写入response 数据 * * @param wrapperResponse * @param response */ public void reWriteResponse(CacheContentResponseWrapper wrapperResponse, HttpServletResponse response) { String result = null; try { result = new String(wrapperResponse.getResponseData(), "UTF-8"); //解决可能在运行的过程中页面只输出一部分 response.setContentLength(-1); response.setCharacterEncoding("UTF-8"); PrintWriter out = null; out = response.getWriter(); if (out != null) { out.write(result); out.flush(); out.close(); } } catch (Exception e) { e.printStackTrace(); } } public ServletOutputStream getOutputStream() throws IOException { return out; } public PrintWriter getWriter() throws UnsupportedEncodingException { return writer; } public void flushBuffer() throws IOException { if (out != null) { out.flush(); } if (writer != null) { writer.flush(); } } public void reset() { buffer.reset(); } public byte[] getResponseData() throws IOException { flushBuffer(); return buffer.toByteArray(); } private class WapperedOutputStream extends ServletOutputStream { private ByteArrayOutputStream bos = null; public WapperedOutputStream(ByteArrayOutputStream stream) throws IOException { bos = stream; } @Override public void write(int b) throws IOException { bos.write(b); } @Override public void write(byte[] b) throws IOException { bos.write(b, 0, b.length); } public boolean isReady() { return false; } public void setWriteListener(WriteListener writeListener) { } } }