Affects: 6.0.4

Hi,

since the upgrade to 6.x I have some MockMvc Tests that fail if the content ist null. This also happens for Spring Boot Health Actuators. The tests fail with an HttpMessageNotReadableException caused by a java.io.IOException: Stream closed

I went deep in to the debugger to find the reason: Pull-Request https://github.com/spring-projects/spring-data-mongodb/pull/4160 replaced StreamUtils.emptyInput() with InputStream.nullInputStream() for private static final ServletInputStream EMPTY_SERVLET_INPUT_STREAM = new DelegatingServletInputStream(InputStream.nullInputStream()); see: https://github.com/spring-projects/spring-framework/commit/0770d86936ae1fdd2931a637938aaef4badc776a#diff-1a707902c32bc4ce99156e838462185a17d27d3a8c686a879894597981563124L104

EMPTY_SERVLET_INPUT_STREAM is used every time the content of a call is null: https://github.com/spring-projects/spring-framework/blob/e1010a179f81b98d79ac5beeb4fe39e72a51f6a4/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java#L519

@Override
public ServletInputStream getInputStream() {
...
    this.inputStream = (this.content != null ?
            new DelegatingServletInputStream(new ByteArrayInputStream(this.content)) :
            EMPTY_SERVLET_INPUT_STREAM);
    return this.inputStream;
}

The Problem is, that the old StreamUtils.emptyInput() was a simple new ByteArrayInputStream(new byte[0]); which could be read and closed multiple times. The new InputStream.nullInputStream() handles more like a normal stream and can't be read again if closed.

I think the problem result from the fact that public ServletInputStream getInputStream() uses the static EMPTY_SERVLET_INPUT_STREAM (every time the same instance) instead of calling new DelegatingServletInputStream(InputStream.nullInputStream());

I don't know the logic behind the caching and context creation but a fix could be in the MockHttpServletRequest getInputStream():

this.inputStream = new DelegatingServletInputStream(this.content != null ?
        new ByteArrayInputStream(this.content) : InputStream.nullInputStream());

There might be some more side effects that I don't know off.

Best regards Daniel

Comment From: bclozel

After looking into the behavior here, it looks like this change fixed an actual issue. This aligns MockHttpServletRequest with the expected behavior of a HttpServletRequest. I've replicated the exact same behavior with Tomcat.

In light of that, I'm closing this issue as this works as expected.

Thanks for your report!

Comment From: ColdFireIce

Hi ok. That might be, but could you explain how I'm supposed align with the expected behavior if I'm testing something like this:

@SpringBootTest
@AutoConfigureMockMvc
class TestControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void testNullContent() throws Exception {
        mockMvc.perform(post("/test"))
                .andDo(print()).andExpect(status().isOk());
    }
    @Test
    void testNullContentAgain() throws Exception {
        mockMvc.perform(post("/test"))
                .andDo(print()).andExpect(status().isOk());
    }
}

The 2nd tests fails with the mentioned Stream closed exception. With this controller:

@RestController
public class TestController {

    @PostMapping(path = "/test")
    public void test(HttpServletRequest request) throws IOException {
        ServletInputStream inputStream = request.getInputStream();
        // something reads the stream and closes it:
        inputStream.readAllBytes();
        inputStream.close();
    }
}
Stream closed
java.io.IOException: Stream closed
    at java.base/java.io.InputStream$1.ensureOpen(InputStream.java:91)
    at java.base/java.io.InputStream$1.read(InputStream.java:103)
    at org.springframework.mock.web.DelegatingServletInputStream.read(DelegatingServletInputStream.java:63)
    at java.base/java.io.InputStream.read(InputStream.java:284)
    at java.base/java.io.InputStream.readNBytes(InputStream.java:409)
    at java.base/java.io.InputStream.readAllBytes(InputStream.java:346)
    at com.example.demo.TestController.test(TestController.java:19)
...

Maybe you could tell me how I'm supposed to do this differently if every null content is replaced with the identical - already closed - stream?

Best regards Daniel

Comment From: bclozel

Sorry, I initially tried to replicate the static behavior by using different requests and it worked fine; obviously my unit test was flawed. Thanks to your code snippet I managed to reproduce the issue. I'm reopening this and re-scheduling this for 6.0.5.

Comment From: ColdFireIce

Ah ok, thank you. I just uploaded a working (broken) test snippet. Maybe It's of use: https://github.com/ColdFireIce/spring-null-content-stream-bug/