Issue
Currently, when returning a subclass of InputStreamResource from a controller, ResourceHttpMessageWriter from the Spring Web module doesn't consider subclasses of InputStreamResource when deciding whether or not to call the contentLength method on the given Resource, it only considers the own InputStreamResource class.
The result is: subclasses of InputStreamResource must provide an override for the given method or risk having the whole input stream read by the default implementation of Resource#contentLength, which could be very undesirable and lead to errors such as:
java.lang.IllegalStateException: InputStream has already been read - do not use InputStreamResource if a stream needs to be read multiple times
This issue doesn't happen if you return a ResponseEntity<Resource> from your controller and make sure to set the Content-Length header because there's no need to check for the length of the resource. But that's not always the case, there are cases where the final size of the input stream is undetermined, and for these cases, Spring already does handle these cases and properly sets and uses chunked as the transfer-encoding header, but only when returning an actual InputStreamResource. This is the point I'm going to propose a change.
Affects: All versions up to Spring Framework 6.1.10 / Spring Boot 3.3.1 (and possibility newer versions too).
Proposed Solution
To fix this, I propose a simple, yet significant change in the Spring Web module, changing the check being made in this line:
|
if (InputStreamResource.class != resource.getClass()) { |
to this
InputStreamResource.class.isAssignableFrom(resource.getClass())
Here's a simple test code I ran to double-check that the current check doesn't support subclasses of InputStreamResource.
public class JavaClass {
public static void evaluate() {
InputStreamResource normalInputStream = new InputStreamResource(InputStream.nullInputStream());
InputStreamResource customInputStream = new MyCustomInputStreamResource();
boolean normalInputStreamWithCurrentCheck = InputStreamResource.class == normalInputStream.getClass();
boolean customInputStreamWithCurrentCheck = InputStreamResource.class == customInputStream.getClass();
boolean normalInputStreamWithProposedCheck = InputStreamResource.class.isAssignableFrom(normalInputStream.getClass());
boolean customInputStreamWithProposedCheck = InputStreamResource.class.isAssignableFrom(customInputStream.getClass());
System.out.println("Normal input stream with current check: " + normalInputStreamWithCurrentCheck); // prints true
System.out.println("Custom input stream with current check: " + customInputStreamWithCurrentCheck); // prints false
System.out.println("Normal input stream with proposed check: " + normalInputStreamWithProposedCheck); // prints true
System.out.println("Custom input stream with proposed check: " + customInputStreamWithProposedCheck); // prints true
}
public static class MyCustomInputStreamResource extends InputStreamResource {
public MyCustomInputStreamResource() {
super(InputStream.nullInputStream());
}
}
}
Issue
Currently, when returning a subclass of
InputStreamResourcefrom a controller,ResourceHttpMessageWriterfrom the Spring Web module doesn't consider subclasses ofInputStreamResourcewhen deciding whether or not to call thecontentLengthmethod on the givenResource, it only considers the ownInputStreamResourceclass.The result is: subclasses of
InputStreamResourcemust provide an override for the given method or risk having the whole input stream read by the default implementation ofResource#contentLength, which could be very undesirable and lead to errors such as:This issue doesn't happen if you return a
ResponseEntity<Resource>from your controller and make sure to set theContent-Lengthheader because there's no need to check for the length of the resource. But that's not always the case, there are cases where the final size of the input stream is undetermined, and for these cases, Spring already does handle these cases and properly sets and useschunkedas thetransfer-encodingheader, but only when returning an actualInputStreamResource. This is the point I'm going to propose a change.Affects: All versions up to Spring Framework 6.1.10 / Spring Boot 3.3.1 (and possibility newer versions too).
Proposed Solution
To fix this, I propose a simple, yet significant change in the Spring Web module, changing the check being made in this line:
spring-framework/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java
Line 191 in 9b58e1f
to this
Here's a simple test code I ran to double-check that the current check doesn't support subclasses of
InputStreamResource.