Logging HttpRequest parameters and request body
Asked Answered
L

4

14

I am trying to create a request log for my web app. I am using Spring 3. 0.

I implemented a class extending HandlerInterceptorAdapter and used the preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) to intercept the request.

In the method i want to be able to log the request body (my parameters are objects in XML that are written directly to the request body), and for that i use request.getReader();

The problem is - later on I will get an IllegalStateException when the spring controller tries to read the request.

Is there a way to do what I intend?

Luther answered 12/6, 2011 at 14:3 Comment(4)
There are a couple issues. In a ServletRequest you can only call getReader() or getInputStream() not both. If you call both you get an IllegalStateException. You could try calling getInputStream but you still may get an error if you read the input stream Spring may not be able to see it. (ServletInputStream may support reset but I don't think so). Your best bet is to log the parameters either during the XML deserialization process (you should find out what class does this) or immediately after.Daphne
have you considered configuring your http server to log the request headers or using servlet filters to do the logging?Advertence
@Advertence I dont need only the headers i need the body as well.Luther
either solution will still work. in the servlet filter approach, you just have to read from the input stream and printout everything; this will also include the request body. in the http server approach, you have to refer to your documentation as the configuration differs from vendor to vendor.Advertence
W
6

You can do this with a filter. The request parameters are easy to handle. However dealing with the request body will be much more difficult and will require wrapping the servlet request see: HttpServletRequest.

You will need to look how big the incoming request is and decide whether you want to store the request body as a tmp file or string.

You will need to override ServetRequest.getInputStream() with your file or saved string that used for logging.

If the request body is huge I recommend putting the input stream into a buffered input stream and then reading the start of the body.

public class LogRequest extends HttpServletRequestWrapper {

    public LogRequest(HttpServletRequest request) {
        super(request);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        //read from tmp file or string.
    }

    @Override
    public BufferedReader getReader() throws IOException {
        //read from tmp file or string
    }

}
Weintrob answered 12/6, 2011 at 14:48 Comment(2)
Wrapping the request still does not let me call getReader() twice. I still get illegalStateExceptionLuther
Noam Nevo you are going to call the input methods (reader/inputstream) on the original request and then wrap the original request with the request wrapper with getReader() and friends overridden.Weintrob
E
4

Spring has a ready filter to do that for you - see usage of AbstractRequestLoggingFilter and its subclasses described in this answer.

Be aware that when using this solution, the request body will be logged only after the request processing is complete and the body has been read by your application.

Extrajudicial answered 27/11, 2014 at 12:23 Comment(1)
AbstractRequestLoggingFilter won't allow you to log Request before it has been processed. You can't get payload in AbstractRequestLoggingFilter#beforeRequest. If it's ok for you than you can override AbstractRequestLoggingFilter#afterRequest that will log everything including payload.Faller
F
2

Simple implementation for small requests. Don't use it for multipart request.

package ru.rbs.logger.web;

import org.apache.commons.io.IOUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

class CachedRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] cachedBody;

    CachedRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        IOUtils.copy(request.getInputStream(), bos);
        cachedBody = bos.toByteArray();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new CachedServletInputStream();
    }

    byte[] toByteArray(){
        return cachedBody;
    }

    private class CachedServletInputStream extends ServletInputStream {
        private InputStream baseInputStream;

        CachedServletInputStream() throws IOException {
            baseInputStream = new ByteArrayInputStream(cachedBody);
        }

        @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 baseInputStream.read();
        }
    }
}
Faller answered 6/2, 2017 at 12:23 Comment(0)
R
0

This can be accomplished fairly easily using TeeInputStream. This writes to two streams at once: the source input stream, and some output stream. Simply override the ServletInputStream in the request wrapper, and read from the TeeInputStream. You can then read from the output stream after calling chain.doFilter():

@Override
public void doFilterNonOption(HttpServletRequest request, HttpServletResponse response,
      FilterChain chain) throws IOException, ServletException {
  ByteArrayOutputStream requestBaos = new ByteArrayOutputStream();
  final PrintStream requestPrintStream = new PrintStream(requestBaos);
  final TeeInputStream inputStream = new TeeInputStream(request.getInputStream(), requestPrintStream);

  HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(request) {
    @Override
    public ServletInputStream getInputStream() {
      return new ServletInputStream() {
        @Override
        public boolean isFinished() {
          return false;
        }

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

        @Override
        public void setReadListener(ReadListener readListener) {
          // NOOP
        }

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

  try {
    chain.doFilter(wrappedRequest, response);
  } finally {
    System.out.println(requestBaos.toString(StandardCharsets.UTF-8));
  }
}
Rowden answered 25/10, 2022 at 19:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.