Cannot Read HttpServletRequest,getInputStream() Multi Times.

  • When I readed HttpServletRequest.getInputStream() in the Filter, then the @RequestBody parsing parmeter face an exception like "java.io.IOException: Stream closed".
  • The exception above is that HttpServletRequest,getInputStream() can only be read once, I read HttpServletRequest.getInputStream() in the filter before RequestParamMethodArgumentResolver read.

The Same Problem In StackOverFlow

  • https://stackoverflow.com/questions/4449096/how-to-read-request-getinputstream-multiple-times
  • https://stackoverflow.com/questions/5345173/unable-to-read-request-getinputstream

Comment From: quaff

It's vulnerable to cache large bytes. https://en.wikipedia.org/wiki/Denial-of-service_attack

Comment From: lixiaolong11000

It's vulnerable to cache large bytes. https://en.wikipedia.org/wiki/Denial-of-service_attack

yes,I think I provide a tool for particular situation. For example, I always face a problem that I read inputStream before RequestBody parsing.

Comment From: rstoyanchev

RepeatableReadInputStreamAndContentCacheRequestWrapper.java appears to be a duplicate of our own ContentCachingRequestWrapper.java. I don't know how this class was created but it appears to have originated from ContentCachingRequestWrapper looking at its Javadoc and much of the structure. In any case, we clearly don't need a second variant of the same class in the framework.

Comment From: lixiaolong11000

RepeatableReadInputStreamAndContentCacheRequestWrapper.java appears to be a duplicate of our own ContentCachingRequestWrapper.java. I don't know how this class was created but it appears to have originated from ContentCachingRequestWrapper looking at its Javadoc and much of the structure. In any case, we clearly don't need a second variant of the same class in the framework.

This ContentCachingRequestWrapper cannot solve my problem such as https://stackoverflow.com/questions/4449096/how-to-read-request-getinputstream-multiple-times https://stackoverflow.com/questions/5345173/unable-to-read-request-getinputstream I must read the HttpservletRequest.InputStream in the filter,When I readed it, ReqestBody cannot read it.

I know the ContentCachingRequestWrapper but It cannot make it work. And what's more, cannot improve it ,because the two sides conflict.

To some extent I think you misunderstood me.

Comment From: rstoyanchev

@lixiaolong11000 it is okay to provide links to StackOverflow for extra context but not as a substitute for a proper issue description, as indicated in our CONTRIBUTING guidelines.

Reviewing long SO threads and trying to extrapolate what you might have meant is very ineffective, and it causes everyone else who comes along and reads this issue to go through the same process.

For example I don't know why you made a copy of ContentCachingRequestWrapper, you made no mention of that, it is not something SO can help with either, so it remains unclear why you cannot make it work. If you don't mind explaining, I will have another look.

Comment From: lixiaolong11000

@lixiaolong11000 it is okay to provide links to StackOverflow for extra context but not as a substitute for a proper issue description, as indicated in our CONTRIBUTING guidelines.

Reviewing long SO threads and trying to extrapolate what you might have meant is very ineffective, and it causes everyone else who comes along and reads this issue to the same.

For example I don't know why you made a copy of ContentCachingRequestWrapper, you made no mention of that, and it's still not clear why you cannot make it work. If you don't mind explaining, I will have another look.

ok,I will study CONTRIBUTING guidelines.

hi,I describe it briefly.

@PostMapping("/helloRequestBody")
public void helloRequestBody(@RequestBody Domain domain, HttpServletRequest httpServletRequest) throws IOException 
{
    IOUtils.toString(httpServletRequest.getInputStream(), Charset.forName("utf-8"));
}

In this case,it will face an exception. because: 1. when @RequestBody work,'RequestParamMethodArgumentResolver' will read 'httpServletRequest.getInputStream()' the stream and close it. 2. when IOUtils.toString(httpServletRequest.getInputStream(), Charset.forName("utf-8")) read the steam too it face an exception,because the stream is read over and close.

In the same way,when I read the httpServletRequest.getInputStream() in the filter, it make the @RequestBody face an Exception. Totaly, the httpServletRequest.getInputStream() can only be read once. I want repeately read. When I see ContentCachingRequestWrapper I find ContentCachingRequestWrapper.getInputStream() cannot read repeately, It only cache part stream. Fix ContentCachingRequestWrapper.getInputStream() conflict it's origin aim.So I make RepeatableReadInputStreamAndContentCacheRequestWrapper.

Diff between ContentCachingRequestWrapper and RepeatableReadInputStreamAndContentCacheRequestWrapper 1. ContentCachingRequestWrapper cache limit bytes,and make the limit bytes a new stream.The stream can only be read once. 2. RepeatableReadInputStreamAndContentCacheRequestWrapper make getInputStream() can be repeatable read.To make repeatable read,the cache bytes must always the same. 3. ContentCachingRequestWrapper.ContentCachingInputStream is new ServletInputStream. it caching when the ServletInputStream be read. Read one cache one.

From other aspects, It's hard to do it. Many people inherit HttpServletRequestWrapper too. Thank you for your prompt reply. Mybe you can give a good idea.

Comment From: rstoyanchev

I've edited your comment to improve the formatting. You might want to check out this Mastering Markdown guide for future reference. Keep in mind that @ is a mention and needs to be escaped to avoid notifications.

Comment From: rstoyanchev

ContentCachingRequestWrapper does cache the content and makes it available for repeated use via getContentAsByteArray(). If you want getInputStream() to also be used multiple times, you could extend ContentCachingRequestWrapper. Something like:

public class RepeatableContentCachingRequestWrapper extends ContentCachingRequestWrapper {


    public RepeatableContentCachingRequestWrapper(HttpServletRequest request) {
        super(request);
        StreamUtils.drain(super.getInputStream());
    }


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


    private static class ByteServletInputStream extends ServletInputStream {

        private final InputStream is;

        private ByteServletInputStream(byte[] content) {
            this.is = new ByteArrayInputStream(content);
        }

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

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

        @Override
        public void setReadListener(ReadListener readListener) {

        }

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

        @Override
        public void close() throws IOException {
            this.is.close();
        }
    }
}

Comment From: bigunyak

@rstoyanchev sorry for bothering you here. Just want to ask a quick question about ContentCachingRequestWrapper as I tried to use getContentAsByteArray() to implement request payload logging in a filter. But it turned out there is a restriction inside ContentCachingRequestWrapper that it only works with HTTP POST requests and when Content-type is application/x-www-form-urlencoded. I wonder if there is a particular reason for that, as I'd want to use it for PUT and PATCH methods as well and "application/json" content-type. In general I see there is a lot of confusion in how to get access to request payload and implement logging. People come up with all sorts of ideas but it doesn't seem there is a good standard way of doing it. Or maybe there is and I just can't find it? Would appreciate your comment.

Comment From: rimwoorim

hi, i have met the same problem. i am using the ContentCachingRequestWrapper in my filter to decorate the request. And then i am going to read the request body in the before handler function of my access-logging interceptor. if i use getContentAsByteArray(), it print nothing; if i use its inputstream, i can get the body content, but controller can't, with a HttpMessageNotReadableException saying 'Required request body is missing'. What makes me amazing is about that i read the code of the AbstractRequestLoggingFilter linking from Spring doc. it looks like having the same process -- before doChainFilter(), it try to read content by using getContentAsByteArray(). sorry i dont have run the code, i will try it later.


i try it just now. it also doesn't work before calling doChainFilter(), the content payload that read from request is empty.

Comment From: bigunyak

@rimwoorim you're right, I tried using AbstractRequestLoggingFilter as well, it's not working because of underlying ContentCachingRequestWrapper. For the same reason CommonsRequestLoggingFilter is not working either. To me this looks like a bug actually as I can't find anything in the documentation of AbstractRequestLoggingFilter and CommonsRequestLoggingFilter that says they wouldn't work for anything but POST method and application/x-www-form-urlencoded content type.

Comment From: rimwoorim

@bigunyak hi. i have read a blog before, it says that the ContentCachingRequestWrapper works after spring-mvc parsing the @RequestBody. and i think it is the fisrt time that the spring-mvc read the stream in the request. see AbstractMessageConverterMethodArgumentResolver, the line number is from 190. so, ContentCachingRequestWrapper dosen't solve the problem of reading the inputStream repeatablely, and just cache the content after spring-mvc parsing the body. never work, never your code touching parsing requestBody. and base of this, at the end, i try to write a class call RepeatReadContentCachingRequestWrapper extend ContentCachingRequestWrapper.

it has 2 task : - reading stream and caching it when constructor is called by super.getInputStream().read(....); - override getInputStream(), getReader() and close() . its source stream is read from super.getContentAsByteArray() and decorating to a ByteArrayInputStream;

i am not sure its is a wise way. and you can try it too.

Comment From: rstoyanchev

But it turned out there is a restriction inside ContentCachingRequestWrapper that it only works with HTTP POST requests and when Content-type is application/x-www-form-urlencoded

This is not true actually. You will see that getReader() and getInputStream() do wrap unconditionally.

Comment From: bigunyak

@rstoyanchev, My comment was regarding getContentAsByteArray() which you suggested to use for repeatable reading of cached content. And the "only HTTP POST requests and application/x-www-form-urlencoded content type" limitation is there for that case, as writeRequestParametersToCachedContent() would never be called otherwise. It is true what you're saying about getReader() and getInputStream(), they're available with no restrictions but if I read the stream in my custom logging filter, then it won't be longer available for the controller and I get HttpMessageNotReadableException. This is I believe the problem the author of this pull request was trying to address. Now, I guess I'll have to look at writing something like RepeatableContentCachingRequestWrapper that you also suggested. I'm just surprised by the fact this problem actually exist, considering how typical logging task is and how many people face it trying to find a workaround for current Spring limitations...

Comment From: rstoyanchev

@bigunyak here is a test for a GET request that gets the cached content via getContentAsByteArray().

It would be best if you could write some such test to explain what you mean. I'm really not getting your point so far.