0

Is there a way of reading the body of your request in a filter without breaking your controllers behind ?

That is:

  • possibility to get your parameters with @RequestParam or the method getParameter(String name)
  • possibility to get your request's InputStream in your controller even if you already needed it in a filter.
Pleymor
  • 2,611
  • 1
  • 32
  • 44

1 Answers1

0

I inspired from this post: Get the POST request body from HttpServletRequest:

It copies then caches the body, then extracts and stores the map of parameters in an attribute formParameters.

public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {

    private final FormHttpMessageConverter formConverter = new AllEncompassingFormHttpMessageConverter();
    MultiValueMap<String, String> formParameters;
    private ByteArrayOutputStream cachedBytes;

    public MultiReadHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        HttpInputMessage inputMessage = new ServletServerHttpRequest(this) {
            @Override
            public InputStream getBody() throws IOException {
                return getInputStream();
            }
        };
        formParameters = formConverter.read(null, inputMessage);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (cachedBytes == null)
            cacheInputStream();

        return new CachedServletInputStream();
    }

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

    @Override
    public String getParameter(String name) {
        String queryStringValue = super.getParameter(name);
        String formValue = this.formParameters.getFirst(name);
        return (queryStringValue != null) ?  queryStringValue : formValue;
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        Map<String, String[]> result = new LinkedHashMap<>();
        Enumeration<String> names = this.getParameterNames();
        while (names.hasMoreElements()) {
            String name = names.nextElement();
            result.put(name, this.getParameterValues(name));
        }
        return result;
    }

    @Override
    public Enumeration<String> getParameterNames() {
        Set<String> names = new LinkedHashSet<>();
        names.addAll(Collections.list(super.getParameterNames()));
        names.addAll(this.formParameters.keySet());
        return Collections.enumeration(names);
    }

    @Override
    public String[] getParameterValues(String name) {
        String[] queryStringValues = super.getParameterValues(name);
        List<String> formValues = this.formParameters.get(name);
        if (formValues == null) {
            return queryStringValues;
        }
        else if (queryStringValues == null) {
            return formValues.toArray(new String[formValues.size()]);
        }
        else {
            List<String> result = new ArrayList<>();
            result.addAll(Arrays.asList(queryStringValues));
            result.addAll(formValues);
            return result.toArray(new String[result.size()]);
        }
    }

    private void cacheInputStream() throws IOException {
/* Cache the inputstream in order to read it multiple times. For
 * convenience, I use apache.commons IOUtils
 */
        cachedBytes = new ByteArrayOutputStream();
        IOUtils.copy(super.getInputStream(), cachedBytes);
    }

    /* An inputstream which reads the cached request body */
    public class CachedServletInputStream extends ServletInputStream {
        private ByteArrayInputStream input;

        public CachedServletInputStream() {
  /* create a new input stream from the cached request body */
            input = new ByteArrayInputStream(cachedBytes.toByteArray());
        }

        @Override
        public int read() throws IOException {
            return input.read();
        }
    }
}
Community
  • 1
  • 1
Pleymor
  • 2,611
  • 1
  • 32
  • 44
  • That buffers the whole request data in memory. You may not want to do that for things like file uploads. – Thilo Feb 25 '16 at 10:42
  • you mean in formParameters ? – Pleymor Feb 25 '16 at 15:15
  • I mean the `cachedBytes` can potentially be huge. – Thilo Feb 25 '16 at 23:29
  • Yes, I wondered if it was that huge, because initially it's already somewhere, accessible through "request.getInputStream()". So don't we finally only move this object with the `IOUtils.copy(super.getInputStream(), cachedBytes);` given that the original is consumed ? – Pleymor Mar 02 '16 at 14:58
  • request.getInputStream originally reads data from the request as it is being received. This can be used to stream big data, for example file uploads, without reading them into memory all at once. Just like you can read gigabytes data off the disk using a FileInputStream even though it may not fit into memory. – Thilo Mar 03 '16 at 00:06