This is the 15th day of my participation in Gwen Challenge

background

If you need to implement a business interceptor you need to intercept all incoming information from a request. In general, the body content in HttpServletRequst is read only once, but in some cases it may be read more than once. Because the body content exists as a stream, the first read is completed and the second read is not possible. A typical scenario is Filter After validating the contents of the body, the business method cannot continue reading the stream, resulting in parsing errors.

Solution 1

  • useContentCachingRequestWrapper

We create a filter:

@Component
public class CachingRequestBodyFilter extends GenericFilterBean {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
      throws IOException, ServletException {
        HttpServletRequest currentRequest = (HttpServletRequest) servletRequest;
        ContentCachingRequestWrapper wrappedRequest = newContentCachingRequestWrapper(currentRequest); chain.doFilter(wrappedRequest, servletResponse); }}Copy the code

In the Web filter doFilter method, create ContentCachingRequestWrapper to request to do packing. Then we can call the ContentCachingRequestWrapper method in the Controller for the request body. The following is an example:

@RestController
public class HelloController {
    @PostMapping("/hello")
    public String hello(@RequestBody String id, HttpServletRequest request) {
        ContentCachingRequestWrapper requestWrapper = (ContentCachingRequestWrapper) request;
        String requestBody = new String(requestWrapper.getContentAsByteArray());
        return "Body."+ requestBody; }}Copy the code

Solution 2

Let’s see if we can decorate the request with a decorator so that it wraps the read and can be read multiple times. The decorator to meet httpsevletrequest interface specification, in the framework of the original spring boot offers a simple wrapper ContentCachingRequestWrapper, look from the source the wrapper is not practical, Without a ServletInputStream that encapsulates HTTP, logic built with @requestParam,@RequestBody, etc. using the underlying stream is still useless and can only be used by force. We consult ContentCachingRequestWrapper encapsulates a more reliability, less invasive decorator:

public class RepeatReadHttpRequest extends HttpServletRequestWrapper {
    private static final Logger LOGGER = LoggerFactory.getLogger(RepeatReadHttpRequest.class);
    private final ByteArrayOutputStream cachedContent;
    private Map<String, String[]> cachedForm;

    @Nullable
    private ServletInputStream inputStream;

    public RepeatReadHttpRequest(HttpServletRequest request) {
        super(request);
        this.cachedContent = new ByteArrayOutputStream();
        this.cachedForm = new HashMap<>();
        cacheData();
    }

    @Override
    public ServletInputStream getInputStream(a) throws IOException {
        this.inputStream = new RepeatReadInputStream(cachedContent.toByteArray());
        return this.inputStream;
    }

    @Override
    public String getCharacterEncoding(a) {
        String enc = super.getCharacterEncoding();
        return(enc ! =null ? enc : WebUtils.DEFAULT_CHARACTER_ENCODING);
    }

    @Override
    public BufferedReader getReader(a) throws IOException {
         return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
    }

    @Override
    public String getParameter(String name) {
        String value = null;
        if (isFormPost()) {
            String[] values = cachedForm.get(name);
            if (null! = values && values.length >0) {
                value = values[0]; }}if (StringUtils.isEmpty(value)) {
            value = super.getParameter(name);
        }

        return value;
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        if(isFormPost() && ! CollectionUtils.sizeIsEmpty(cachedForm)) {return cachedForm;
        }

        return super.getParameterMap();
    }

    @Override
    public Enumeration<String> getParameterNames(a) {
        if(isFormPost() && ! CollectionUtils.sizeIsEmpty(cachedForm)) {return Collections.enumeration(cachedForm.keySet());
        }

        return super.getParameterNames();
    }

    @Override
    public String[] getParameterValues(String name) {
        if(isFormPost() && ! CollectionUtils.sizeIsEmpty(cachedForm)) {return cachedForm.get(name);
        }

        return super.getParameterValues(name);
    }

    private void cacheData(a) {
        try {
            if (isFormPost()) {
                this.cachedForm = super.getParameterMap();
            } else {
                ServletInputStream inputStream = super.getInputStream();
                IOUtils.copy(inputStream, this.cachedContent); }}catch (IOException e) {
            LOGGER.warn("[RepeatReadHttpRequest:cacheData], error: {}", e.getMessage()); }}private boolean isFormPost(a) {
        String contentType = getContentType();
        return(contentType ! =null &&
                (contentType.contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE) ||
                        contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) &&
                HttpMethod.POST.matches(getMethod()));
    }

    private static class RepeatReadInputStream extends ServletInputStream {
        private final ByteArrayInputStream inputStream;

        public RepeatReadInputStream(byte[] bytes) {
            this.inputStream = new ByteArrayInputStream(bytes);
        }

        @Override
        public int read(a) throws IOException {
            return this.inputStream.read();
        }

        @Override
        public int readLine(byte[] b, int off, int len) throws IOException {
            return this.inputStream.read(b, off, len);
        }

        @Override
        public boolean isFinished(a) {
            return this.inputStream.available() == 0;
        }

        @Override
        public boolean isReady(a) {
            return true;
        }

        @Override
        public void setReadListener(ReadListener listener) {}}}Copy the code

Replace the original request with Filter when using:

/ * * *@authorLambert * interceptor httprequest replace repeatable access to InputStream */
@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean requestReplaceFilterRegistration(a) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new RequestReplaceFilter());
        registrationBean.addUrlPatterns("/ *");
        registrationBean.setName("RequestReplaceFilter");
        registrationBean.setOrder(1);
        return registrationBean;
    }

    public static class RequestReplaceFilter implements Filter {
        @Override
        public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {}@Override
        public void destroy(a) {}@Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            filterChain.doFilter(newRepeatReadHttpRequest((HttpServletRequest) servletRequest), servletResponse); }}}Copy the code

Subsequent requests can use the wrapper to read the body information multiple times.