Skip to content

Instantly share code, notes, and snippets.

@calo81
Created March 18, 2012 12:48
Show Gist options
  • Star 69 You must be signed in to star a gist
  • Fork 27 You must be signed in to fork a gist
  • Save calo81/2071634 to your computer and use it in GitHub Desktop.
Save calo81/2071634 to your computer and use it in GitHub Desktop.
Filter for reading and logging HttpServletRequest body, and resetting the input stream
package com.paddypower.financials.market.management.rest.logging;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.io.IOUtils;
public class LoggerFilter implements Filter {
private Auditor auditor;
public void destroy() {
// Nothing to do
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
ResettableStreamHttpServletRequest wrappedRequest = new ResettableStreamHttpServletRequest(
(HttpServletRequest) request);
// wrappedRequest.getInputStream().read();
String body = IOUtils.toString(wrappedRequest.getReader());
auditor.audit(wrappedRequest.getRequestURI(),wrappedRequest.getUserPrincipal(), body);
wrappedRequest.resetInputStream();
chain.doFilter(wrappedRequest, response);
}
public void init(FilterConfig arg0) throws ServletException {
// Nothing to do
}
private static class ResettableStreamHttpServletRequest extends
HttpServletRequestWrapper {
private byte[] rawData;
private HttpServletRequest request;
private ResettableServletInputStream servletStream;
public ResettableStreamHttpServletRequest(HttpServletRequest request) {
super(request);
this.request = request;
this.servletStream = new ResettableServletInputStream();
}
public void resetInputStream() {
servletStream.stream = new ByteArrayInputStream(rawData);
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (rawData == null) {
rawData = IOUtils.toByteArray(this.request.getReader());
servletStream.stream = new ByteArrayInputStream(rawData);
}
return servletStream;
}
@Override
public BufferedReader getReader() throws IOException {
if (rawData == null) {
rawData = IOUtils.toByteArray(this.request.getReader());
servletStream.stream = new ByteArrayInputStream(rawData);
}
return new BufferedReader(new InputStreamReader(servletStream));
}
private class ResettableServletInputStream extends ServletInputStream {
private InputStream stream;
@Override
public int read() throws IOException {
return stream.read();
}
}
}
public void setAuditor(Auditor auditor) {
this.auditor = auditor;
}
}
@denys-popov
Copy link

Thanks ! Really useful.

@fgarsombke
Copy link

Thank you!

@tsarnow
Copy link

tsarnow commented Jan 10, 2014

Works perfectly even for POST requests with RAW data!!!

@beaujackson
Copy link

Fantastic example, thanks!

@pcholakov
Copy link

If you're using Spring Web MVC, you can also try org.springframework.web.filter.CommonsRequestLoggingFilter or one of the other AbstractRequestLoggingFilter implementations.

@uruloki85
Copy link

Exactly what I need! 😄
Does anybody know why is not possible to read the body more than once? I know that documentation states this but I would like to know why... Thanks!

@mpas
Copy link

mpas commented Mar 20, 2015

It seems this can not be used in Servlet 3.1 some methods are missing. Is there a solution to this?

@bhonnegowda
Copy link

When you read the content of a request, you access its InputStream object and that InputStream cannot be reset to its initial position to re-read the content of the request. It's simply not possible, you can't read the content twice so apparently by making a request wrapper you essentially get a duplicate request which leaves the original request alone.

@stevenwcarter
Copy link

I've been using this for a while now (thank you!) but recently I discovered that it is messing up character encoding for binary file uploading. I'm trying to pin-point where this is happening, but if anyone else is more InputStream savvy than me I would appreciate the help!

@vjvipulvj
Copy link

In dofilter method I am trying to get Authenticaed user by using SecurityContextHolder, but it is coming null, Although it is working in Spring MVC. What I am trying to use is this
if (SecurityContextHolder.getContext().getAuthentication()!=null) {
principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
System.out.print(SecurityContextHolder.getContext().getAuthentication());
}
But here SecurityContextHolder.getContext().getAuthentication() is coming null, so I am not able to get my principal from here, Can you please help me out

@Rugal
Copy link

Rugal commented Jul 29, 2015

@sabela85 because body data is stored in stream. Stream could only be read once according to Javadoc

@hbprotoss
Copy link

It seems that both getInputStream and getReader are not called under Spring 4, any ideas?

@sarthakbrahmbhatt
Copy link

Please its urgent,i m using spring boot .i use filter as interceptor and i got this messgae

"timestamp": 1450168414601,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "Required request body content is missing: org.springframework.web.method.HandlerMethod$HandlerMethodParameter@21779fb",
"path": "/BootMyBatisDemo/student/create"

}

@40lsgy1
Copy link

40lsgy1 commented Jan 22, 2016

It is really useful, but do I need to close the ByteArrayInputStream created in getReader method?

@gdegani
Copy link

gdegani commented Jan 22, 2016

Very useful! Thanks

@jpukg
Copy link

jpukg commented Jul 27, 2016

Very useful.

@buzz1000
Copy link

Awesome! Thanks for documenting this.

@spyhunter99
Copy link

This has potential issues with OOM if the request is too large, such as a large file upload. There should be limits on the how much content is recorded from the input stream

@nyIdeas
Copy link

nyIdeas commented Jul 17, 2017

Know its been long this thread has been stale. Much appreciate if someone can help. What is the Auditor class used here ? can you please let me know. I am writing a similar filter function to decode a header value.[At the Controller level request header param can only be retreived by getHeader() which by decode the value in some default encoding scheme.]

@Thyreach
Copy link

what about response filter, i want to catch or log all the data of webservice's response before it reach to client

someone have experience on it pleas share it bro... :)

@hkps4jj
Copy link

hkps4jj commented Apr 12, 2018

Thank you so much for this!
And hope my version is helpful to someone

public class ResponseFilter implements Filter{
    public ResponseFilter() {
        super();
    }
    
	private static class ResettableStreamHttpServletResponse extends HttpServletResponseWrapper {
		private HttpServletResponse response;
	
		public ResettableStreamHttpServletResponse(HttpServletResponse response) {
			super(response);
			this.response = response;
		}
	}
    
    
	@Override public void init(FilterConfig config) throws ServletException{}
	@Override public void destroy(){}
	
 
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException  
	{	
		ResettableStreamHttpServletResponse wrappedResponse = new ResettableStreamHttpServletResponse((HttpServletResponse) response);
		
		wrappedResponse.addHeader("Access-Control-Allow-Origin", "*");
		wrappedResponse.addHeader("Access-Control-Allow-Headers", "origin, content-type, accept, authorization");
		wrappedResponse.addHeader("Access-Control-Allow-Credentials", "true");
		wrappedResponse.addHeader("Access-Control-Allow-Methods", "GET, POST");
		
		chain.doFilter(request,  wrappedResponse);
	}
 
}

@miladhub
Copy link

Concerning the encoding problem, this might work, but I haven't tried it:

public class ResettableStreamHttpServletRequest extends HttpServletRequestWrapper {
	private byte[] rawData;
	private HttpServletRequest request;
	private ResettableServletInputStream servletStream;

	ResettableStreamHttpServletRequest(HttpServletRequest request) {
		super(request);
		this.request = request;
		this.servletStream = new ResettableServletInputStream();
	}

	void resetInputStream() {
		servletStream.stream = new ByteArrayInputStream(rawData);
	}

	@Override
	public ServletInputStream getInputStream() throws IOException {
		if (rawData == null) {
			rawData = IOUtils.toByteArray(this.request.getInputStream());
			servletStream.stream = new ByteArrayInputStream(rawData);
		}
		return servletStream;
	}

	@Override
	public BufferedReader getReader() throws IOException {
		if (rawData == null) {
			rawData = IOUtils.toByteArray(this.request.getInputStream());
			servletStream.stream = new ByteArrayInputStream(rawData);
		}
        String encoding = getCharacterEncoding();
		if (encoding != null) {
            return new BufferedReader(new InputStreamReader(servletStream, encoding));
        } else {
            return new BufferedReader(new InputStreamReader(servletStream));
        }
	}

	private class ResettableServletInputStream extends ServletInputStream {
		private InputStream stream;

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

@gargvive18
Copy link

This is awesome !!

@deepd
Copy link

deepd commented Sep 27, 2019

Thanks. The following code with some changes from your code works for me :

private static class ResettableStreamHttpServletRequest extends HttpServletRequestWrapper {

    private byte[] rawData;
    private HttpServletRequest request;
    private ResettableServletInputStream servletStream;

    private ResettableStreamHttpServletRequest(HttpServletRequest request) {
        super(request);
        this.request = request;
        servletStream = new ResettableServletInputStream();
    }

    public void resetInputStream() {
        servletStream.stream = new ByteArrayInputStream(rawData);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (rawData == null) {
            rawData = IOUtils.toByteArray(request.getReader());
            servletStream.stream = new ByteArrayInputStream(rawData);
        }
        return servletStream;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        if (rawData == null) {
            rawData = IOUtils.toByteArray(request.getReader());
            servletStream.stream = new ByteArrayInputStream(rawData);
        }
        return new BufferedReader(new InputStreamReader(servletStream));
    }
}

private static class ResettableServletInputStream extends ServletInputStream {

    private InputStream stream;

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

    @Override
    public boolean isFinished() {
        try {
            int available = stream.available();
            return available == 0;
        } catch (IOException e) {
            return true;
        }
    }

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

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

@baohoanGmail
Copy link

Thanks ! Really useful !!! 😄

@javaHelper
Copy link

I am calling Http POST to the REST endpoint, but making XML request, in my case 3rd party system can only make the SOAP request, now I need to remove soapenv:Envelope from the request and only pass the xml tags. I am using Spring Boot application.

Is there any way to read the request and update the POST data?

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:wer.com:dms:wsdls:organization">
   <soapenv:Header/>
   <soapenv:Body>
      <urn:getProfile>
         <getProfileRequest>
            <patientId>160166</patientId>
         </getProfileRequest>
      </urn:getProfile>
   </soapenv:Body>
</soapenv:Envelope>

@from-Mateusz
Copy link

Thank you.
It was really helpful.
Here is my working solution, that is up-to-date with packages' versions.

private static final class CopyableHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private byte[] rawData;
    private HttpServletRequest request;
    private CopyableServletInputStream servletInputStream;

    private static final class CopyableServletInputStream extends ServletInputStream {

        private InputStream stream;

        public CopyableServletInputStream(InputStream stream) {
            this.stream = stream;
        }

        @Override
        public boolean isFinished() {
            try {
                int remainingBytes = stream.available();
                return 0 == remainingBytes;
            } catch (IOException ex) {
                return false;
            }
        }

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

        @Override
        public void setReadListener(ReadListener listener) { }

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

    private CopyableHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
        this.request = request;
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        copyBodyDataIfNecessary();
        return servletInputStream;
    }

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

    public void copyBodyDataIfNecessary() throws IOException {
        if(null == rawData) {
            rawData = IOUtils.toByteArray(request.getReader(), "utf-8");
            servletInputStream = new CopyableServletInputStream(new ByteArrayInputStream(rawData));
        }
    }

    public void resetStream() throws IOException {
        this.servletInputStream = new CopyableServletInputStream(new ByteArrayInputStream(rawData));
    }
}

@DuYr
Copy link

DuYr commented Aug 20, 2022

very good!!!

@pacisauctor
Copy link

Thank you every one, a little change because not working in my project by a cast wrong

private static final class CopyableHttpServletRequestWrapper extends HttpServletRequestWrapper {

        private byte[] rawData;
        private final HttpServletRequest request;
        private CopyableServletInputStream servletInputStream;

        private static final class CopyableServletInputStream extends ServletInputStream {

            private final InputStream stream;

            public CopyableServletInputStream(InputStream stream) {
                this.stream = stream;
            }

            @Override
            public boolean isFinished() {
                try {
                    int remainingBytes = stream.available();
                    return 0 == remainingBytes;
                } catch (IOException ex) {
                    return false;
                }
            }

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

            @Override
            public void setReadListener(ReadListener listener) {
            }

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

        private CopyableHttpServletRequestWrapper(HttpServletRequest request) {
            super(request);
            this.request = request;
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            copyBodyDataIfNecessary();
            return servletInputStream;
        }

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

        public void copyBodyDataIfNecessary() throws IOException {
            if (null == rawData) {
                rawData = IOUtils.toByteArray(this.request.getInputStream());
                servletInputStream = new CopyableServletInputStream(new ByteArrayInputStream(rawData));
            }
        }

        public void resetStream() throws IOException {
            this.servletInputStream = new CopyableServletInputStream(new ByteArrayInputStream(rawData));
        }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment