Springboot 设置过滤器以及重复读取request里的body

需求:request的content-type为applciation/json,进入controller之前需要把body中的参数取出来做一次处理,然后和hearder中的另一个参数做对比。

思路:加一个过滤器,在过滤器中取出参数做处理,然后比较

注意:body里的数据用流来读取,只能读取一次

HttpServletRequest的输入流只能读取一次的原因

我们先来看看为什么HttpServletRequest的输入流只能读一次,当我们调用getInputStream()方法获取输入流时得到的是一个InputStream对象,而实际类型是ServletInputStream,它继承于InputStream。

InputStream的read()方法内部有一个postion,标志当前流被读取到的位置,每读取一次,该标志就会移动一次,如果读到最后,read()会返回-1,表示已经读取完了。如果想要重新读取则需要调用reset()方法,position就会移动到上次调用mark的位置,mark默认是0,所以就能从头再读了。调用reset()方法的前提是已经重写了reset()方法,当然能否reset也是有条件的,它取决于markSupported()方法是否返回true。

InputStream默认不实现reset(),并且markSupported()默认也是返回false,这一点查看其源码便知:
解决HttpServletRequest的输入流只能读取一次的问题

我们再来看看ServletInputStream,可以看到该类没有重写mark()reset()以及markSupported()方法:
解决HttpServletRequest的输入流只能读取一次的问题

综上,InputStream默认不实现reset的相关方法,而ServletInputStream也没有重写reset的相关方法,这样就无法重复读取流,这就是我们从request对象中获取的输入流就只能读取一次的原因。

重复读取body中数据的方法

这个自定义的requestWrapper继承了HttpServletRequestWrapper ,HttpServletRequestWrapper 是一个ServletRequest的包装类同时也是ServletRequest的实现类。在这个自定义的requestWrapper里,用一个String做缓存,在构造方法里先把request的body中的数据缓存起来,然后重写了getInputStream,返回这个缓存的body,而不是从流中读取。这样,在需要多次读取body的地方,只需要在过滤器中把原来的request换成这个自定义的request,然后把这个自定义的带缓存功能的request传到后续的过滤器链中。

public class BodyReaderRequestWrapper extends HttpServletRequestWrapper {     private final String body;      /**      *      * @param request      */     public BodyReaderRequestWrapper(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 = 0;                 while((readCount = isr.read(charBuffer)) != -1){                     sb.append(charBuffer,0,readCount);                 }             }else{                 sb.append("");             }         }catch (IOException e){             throw e;         }finally {             if(isr != null) {                 isr.close();             }         }          sb.toString();         body = sb.toString();     }      @Override     public BufferedReader getReader() throws IOException {         return new BufferedReader(new InputStreamReader(this.getInputStream()));     }      @Override     public ServletInputStream getInputStream() throws IOException {         final ByteArrayInputStream byteArrayIns = new ByteArrayInputStream(body.getBytes());         ServletInputStream servletIns = 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 byteArrayIns.read();             }         };         return  servletIns;     } }

springboot的过滤器:

2个注解:

@WebFilter(过滤器上)

@ServletComponentScan (加在启动类上,支持servlet components扫描(为了webfilter))

@Order(999) // 序号越低,优先级越高 // 加上WebFilter即可成为过滤器 @WebFilter(filterName="myFilter", urlPatterns="/api/workorder/service/selfAppeal") public class ExternalFilter implements Filter  {      private final static Logger logger = LoggerFactory.getLogger(ExternalFilter.class);          @Override     public void init(FilterConfig filterConfig) throws ServletException {         logger.info("filter init");     }       @Override     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)             throws IOException, ServletException {         ResponseObject object = new ResponseObject();         HttpServletRequest req = (HttpServletRequest)servletRequest;         HttpServletResponse res = (HttpServletResponse)servletResponse;         // 一个request的包装类,初始化时缓存了body,重写了getInputStream返回缓存的body,实现重复读取body         BodyReaderRequestWrapper requestWrapper  = new BodyReaderRequestWrapper(req);         String requestURI = requestWrapper.getRequestURI();         System.out.println("--------------------->过滤器:请求地址" + requestURI);         String md5 = requestWrapper.getHeader("md5")  ;                 if (md5 == null || !md5.toLowerCase().equals(MD5.md5(ReqGetBody.getBody(requestWrapper)).toLowerCase())) {             object.setStatus(501);             object.setStatusText("数据md5校验失败");             render(object, res);             return;         }         // 这里传递下去的就是自定义的request了,所以后续的Controller才能重复读取到body里的参数         filterChain.doFilter(requestWrapper, res);     }       @Override     public void destroy() {       }      /**      * @Title: render      * @Description: 发送Response      * @param object     * @param response void     * @author MasterYi     * @date 2020年1月15日上午10:48:45     */      private void render(ResponseObject object, HttpServletResponse response) {         response.setContentType("application/json;charset=UTF-8");         try {             response.setStatus(200);             response.getWriter().write(JSONObject.toJSON(object).toString());         } catch (IOException e) {             logger.error("ExternalFilter写入response异常");         }     } }

上面的getBody的代码(从body中取值的具体操作)

public class ReqGetBody {     static public String getBody(HttpServletRequest request) {         try {             ServletInputStream in = request.getInputStream();             String body;             body = StreamUtils.copyToString(in, Charset.forName("UTF-8"));                          return body;         } catch (IOException e) {             e.printStackTrace();             return "";         }     } }