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/