SpringBoot AOP/Interceptor aspect or the Interceptor gets the @requestBody parameter of the POST request

SpringBoot Interceptor Interceptor for POST request @ RequestBody parameters, through it. The getInputStream () to obtain parameters, I/O error while reading input message; Nested exception is java.io.IOException: Stream closed

Cause of the problem

The reason for this problem is the getInputStream in the default HttpServletRequest object. The getReader function is only allowed to be called once. In a request, in addition to calling getInputStream in the section or interceptor, the SpringBoot framework also calls getInputStream to read the entire request message body during parameter conversion and then reverts back to the request parameters. This violates the principle of calling only once, thus raising an exception.

In order to solve this problem, we can introduce the HttpServletRequestWrapper this object. This class encapsulates the behavior of HttpServletRequest, and we can inherit this class to emulate the behavior of the original HttpServletRequest with a new class. Then use the filter (filter) it will be the original replaced by HttpServletRequestWrapper object.

  • The business interceptor definition is used by the rewritten HttpServletRequest wrapper class

import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;

/ * * *@author young
 * @version 1.0
 * @date 2021/4/13 11:51 上午
 * @descriptionCheck interceptor */
public class SignatureInterceptor implements HandlerInterceptor {

    Call ** before the program executes@param request
     * @param response
     * @param o
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
        // Create a rewritten HttpServletRequest
        RequestReaderHttpServletRequestWrapper wrapper = new RequestReaderHttpServletRequestWrapper(request);
        // Get the body argument
        String bodyParams = wrapper.inputStream2String(wrapper.getInputStream());
        // Business logic
        // ...
        return true;
    }

    /** ** is executed after the program is executed@param request
     * @param response
     * @param o
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object o, ModelAndView modelAndView) throws Exception {}/** * Callback ** after request processing is complete (that is, after rendering the view)@param request
     * @param response
     * @param o
     * @param e
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) throws Exception {}}Copy the code
  • Register the business interceptor and register the reassembled HttpServletRequest RequestBodyFilter
package com.yks.oms.order.grab.interceptor;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/ * * *@author young
 * @version 1.0
 * @date 2021/4/13 11:59 上午
 * @descriptionRegister the SignatureInterceptor interceptor and FilterRegistrationBean HttpServletRequest wrapper classes that retrieve information from the stream multiple times */
@Configuration
public class SignatureConfigurerAdapter extends WebMvcConfigurerAdapter {

    /** * Register a custom SignatureInterceptor **@return* /
    @Bean
    public SignatureInterceptor signatureInterceptor(a) {
        return new SignatureInterceptor();
    }

    /** * specifies the request to intercept **@param registry
     */
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(signatureInterceptor()).addPathPatterns("/api/*");
    }

    /** * Re-registering the requested HttpServletRequest returns **@return* /
    @Bean
    public FilterRegistrationBean setLogServiceFilter(a) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        RequestBodyFilter requestBodyFilter = new RequestBodyFilter();
        registrationBean.setFilter(requestBodyFilter);
        registrationBean.setName("interceptor filter body params");
        registrationBean.addUrlPatterns("/api/*");
        registrationBean.setOrder(1);
        returnregistrationBean; }}Copy the code
  • RequestBodyFilter(Core class)
package com.yks.oms.order.grab.interceptor;

import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/ * * *@author young
 * @version 1.0
 * @date 2021/4/13 11:51 上午
 * @descriptionReassemble the HttpServletRequest return to solve the problem that the controller layer cannot retrieve the body parameter in the POST request after the interceptor has retrieved it from the stream */
public class RequestBodyFilter implements Filter {

    @Override
    public void destroy(a) {}@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if (request instanceof HttpServletRequest) {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            String method = httpServletRequest.getMethod();
            String contentType = httpServletRequest.getContentType() == null ? "" : httpServletRequest.getContentType();
            // If it is a POST request and not a file upload
            if(HttpMethod.POST.name().equals(method) && ! contentType.equals(MediaType.MULTIPART_FORM_DATA_VALUE)) {This new ServletRequest fetches the stream and overwrites the stream's data into the stream
                requestWrapper = newRequestReaderHttpServletRequestWrapper((HttpServletRequest) request); }}if (requestWrapper == null) {
            chain.doFilter(request, response);
        } else{ chain.doFilter(requestWrapper, response); }}@Override
    public void init(FilterConfig arg0) throws ServletException {}}Copy the code
  • RequestReaderHttpServletRequestWrapper (core)
package com.yks.oms.order.grab.interceptor;

import com.yks.oms.order.grab.controller.LazadaController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;

/** * I/O error while reading input message * 

* I/O error while reading input message nested exception is java.io.IOException: Stream closed */

public class RequestReaderHttpServletRequestWrapper extends HttpServletRequestWrapper { private static final Logger LOGGER = LoggerFactory.getLogger(LazadaController.class); private final byte[] body; public RequestReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); body = inputStream2String(request.getInputStream()).getBytes(Charset.forName("UTF-8")); } @Override public BufferedReader getReader(a) throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream(a) throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read(a) throws IOException { return bais.read(); } @Override public boolean isFinished(a) { return false; } @Override public boolean isReady(a) { return false; } @Override public void setReadListener(ReadListener readListener) {}}; }/** * Reads data from inputStream and converts it to string **@param inputStream inputStream * @return String */ public String inputStream2String(InputStream inputStream) { StringBuilder sb = new StringBuilder(); BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))); String line; while((line = reader.readLine()) ! =null) { sb.append(line); }}catch (IOException e) { sb.append("get body params fail"); LOGGER.error(e.getMessage()); } finally { if(reader ! =null) { try { reader.close(); } catch(IOException e) { LOGGER.error(e.getMessage()); }}}returnsb.toString(); }}Copy the code

Retesting can be done by calling the getInputStream method multiple times on the aspect or by the interceptor to get the body argument