Solution for Repeated Reading of Request Body Content over SpringBoot v 2.2

  • 2021-12-04 10:18:31
  • OfStack

Directory SpringBoot v2.2 Repeatedly read Request Body contents 1. Requirements 2. Solution 3. Problems encountered 4. Problem troubleshooting solution SpringBoot reads Request parameter pit back end takes parameter correlation about stream

Repeat reading Request Body contents above SpringBoot v2.2

1. Requirements

There are two scenarios in the project that will use reading content from Body of Request.

Print request log The Api interface is provided. Before the api method is executed, the parameters are read from the Request Body for verification. After the verification is passed, the api method is executed

2. Solutions

2.1 Custom RequestWrapper


public class MyRequestWrapper extends HttpServletRequestWrapper {
 private final String body;
 public MyRequestWrapper(HttpServletRequest request) throws IOException {
  super(request);
  this.body = RequestReadUtils.read(request);
 }
 public String getBody() {
  return body;
 }
 @Override
 public ServletInputStream getInputStream() throws IOException {
  final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes());  
  return new ServletInputStream() {
   ... Slightly 
  };
 }
 @Override
 public BufferedReader getReader() throws IOException {
  return new BufferedReader(new InputStreamReader(this.getInputStream()));
 }
}

RequestReadUtils (copied online)


private static final int BUFFER_SIZE = 1024 * 8;
  
    public static String read(HttpServletRequest request) throws IOException {
        BufferedReader bufferedReader = request.getReader();
        for (Enumeration<String> iterator = request.getHeaderNames(); iterator.hasMoreElements();) {
         String type = iterator.nextElement();
   System.out.println(type+" = "+request.getHeader(type));
  }
        System.out.println();
        StringWriter writer = new StringWriter();
        write(bufferedReader,writer);
        return writer.getBuffer().toString();
    }
 
    public static long write(Reader reader,Writer writer) throws IOException {
        return write(reader, writer, BUFFER_SIZE);
    }
 
    public static long write(Reader reader, Writer writer, int bufferSize) throws IOException
    {
        int read;
        long total = 0;
        char[] buf = new char[bufferSize];
        while( ( read = reader.read(buf) ) != -1 ) {
            writer.write(buf, 0, read);
            total += read;
        }
        return total;
    }

2.2 Defining Filter


@WebFilter
public class TestFilter implements Filter{
 @Override
 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain){
  HttpServletRequest request = (HttpServletRequest) servletRequest;
  HttpServletResponse response = (HttpServletResponse) servletResponse;
  
  MyRequestWrapper wrapper = WebUtils.getNativeRequest(request, MyRequestWrapper.class);
  chain.doFilter(wrapper == null ? new MyRequestWrapper(request) :wrapper,servletRequest);
 }
}

STEP 3 Encounter problems

SpringBoot v 2.1. x version used

Form Commit No Problem No problem getting RequestBody

Use SpringBoot v 2.2. 0 and above (including v 2.3. x)

Form Submit could not get parameters No problem getting RequestBody

4. Problem troubleshooting

After investigation, the difference between v2.2. x and v2.1. x lies in the code difference under 1:


BufferedReader bufferedReader = request.getReader();
-----------------
char[] buf = new char[bufferSize];
while( ( read = reader.read(buf) ) != -1 ) {
    writer.write(buf, 0, read);
    total += read;
}

When the form is submitted

v2.1. x Unable to read to content, read result is-1 v2.2. x, v2.3. x can read content

When the form is submitted (x-www-form-urlencoded), getInputStream operation of wrapper will not be triggered after inputStream is read once, so Controller cannot get parameters.

Solutions

Transformation of MyRequestWrapper


public class MyRequestWrapper extends HttpServletRequestWrapper {
 private final String body;
 public MyRequestWrapper(HttpServletRequest request) throws IOException {
  super(request);
  this.body = getBodyString(request);
 }
 public String getBody() {
  return body;
 }
 
 public String getBodyString(final HttpServletRequest request) throws IOException {
     String contentType = request.getContentType();
     String bodyString = "";
     StringBuilder sb = new StringBuilder();
     if (StringUtils.isNotBlank(contentType) && (contentType.contains("multipart/form-data") || contentType.contains("x-www-form-urlencoded"))) {
         Map<String, String[]> parameterMap = request.getParameterMap();
         for (Map.Entry<String, String[]> next : parameterMap.entrySet()) {
             String[] values = next.getValue();
             String value = null;
             if (values != null) {
                 if (values.length == 1) {
                     value = values[0];
                 } else {
                     value = Arrays.toString(values);
                 }
             }
             sb.append(next.getKey()).append("=").append(value).append("&");
         }
         if (sb.length() > 0) {
             bodyString = sb.toString().substring(0, sb.toString().length() - 1);
         }
         return bodyString;
     } else {
         return IOUtils.toString(request.getInputStream());
     }
 }
 @Override
 public ServletInputStream getInputStream() throws IOException {
  final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes());
  
  return new ServletInputStream() {
   @Override
   public boolean isFinished() {
    return false;
   }
   @Override
   public boolean isReady() {
    return false;
   }
   @Override
   public int read() {
    return bais.read();
   }
   @Override
   public void setReadListener(ReadListener readListener) {
   }
  };
 }
 @Override
 public BufferedReader getReader() throws IOException {
  return new BufferedReader(new InputStreamReader(this.getInputStream()));
 }
}

Pit for SpringBoot to read Request parameters

Backend takes parameter correlation

When configured by default,

Errors will be reported when getInputStream () and getReader () are used

Use getInputStream () twice, and the second pass will be empty

When there are annotations such as @ RequestBody, springMVC has read the stream once, and either getInputStream () alone or getReader () is empty by default.

Resolution: Write filter to inherit HttpServletRequestWrapper, cache InputStream, override getInputStream () and getReader () methods, and use ByteArrayInputStream is = new ByteArrayInputStream (body. getBytes ()); Read InputStream.

Note: In springboot, the filter only needs @ Component to take effect, and the path and priority can be configured in FilterRegistrationBean.

For interceptors, the addInterceptor method must be called in InterceptorRegistry. (Path can be added in a chain)

About flow

It can only be read once, similar to a tube.

It only undertakes transmission responsibilities, and has nothing to do with processing and storage.

For byte streams, repeated reads are easy to implement, but the pointer is not reset, which should be to keep 1 with the InputStream interface definition.


Related articles: