Skip to content

Instantly share code, notes, and snippets.

@calo81
Created March 18, 2012 12:48
Show Gist options
  • Select an option

  • Save calo81/2071634 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown

Thanks ! Really useful.

@fgarsombke
Copy link
Copy Markdown

Thank you!

@tsarnow
Copy link
Copy Markdown

tsarnow commented Jan 10, 2014

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

@beaujackson
Copy link
Copy Markdown

Fantastic example, thanks!

@pcholakov
Copy link
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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

@sarthakbrahmbhatt
Copy link
Copy Markdown

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
Copy Markdown

40lsgy1 commented Jan 22, 2016

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

@gdegani
Copy link
Copy Markdown

gdegani commented Jan 22, 2016

Very useful! Thanks

@jpukg
Copy link
Copy Markdown

jpukg commented Jul 27, 2016

Very useful.

@buzz1000
Copy link
Copy Markdown

Awesome! Thanks for documenting this.

@spyhunter99
Copy link
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

This is awesome !!

@deepd
Copy link
Copy Markdown

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
Copy Markdown

Thanks ! Really useful !!! ๐Ÿ˜„

@javaHelper
Copy link
Copy Markdown

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
Copy Markdown

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
Copy Markdown

DuYr commented Aug 20, 2022

very good!!!

@pacisauctor
Copy link
Copy Markdown

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