I have a servlet that returns an image as InputStreamResource
. There are approx 50 static images that are to be returned based on some get query
parameters.
For not having to look up each of those images every time it is requested (which is very often), I'd like to cache those images responses.
@RestController
public class MyRestController {
//code is just example; may be any number of parameters
@RequestMapping("/{code}")
@Cachable("code.cache")
public ResponseEntity<InputStreamResource> getCodeLogo(@PathVariable("code") String code) {
FileSystemResource file = new FileSystemResource("d:/images/" + code + ".jpg");
return ResponseEntity.ok()
.contentType("image/jpg")
.lastModified(file.lastModified())
.contentLength(file.contentLength())
.body(new InputStreamResource(file.getInputStream()));
}
}
When using the @Cacheable
annotation (no matter if directly on the RestMapping
method or refactored to an external service), I_'m getting the following exception:
cause: java.lang.IllegalStateException: InputStream has already been read - do not use InputStreamResource if a stream needs to be read multiple times - error: InputStream has already been read - do not use InputStreamResource if a stream needs to be read multiple times
org.springframework.core.io.InputStreamResource.getInputStream(InputStreamResource.java:96)
org.springframework.http.converter.ResourceHttpMessageConverter.writeInternal(ResourceHttpMessageConverter.java:100)
org.springframework.http.converter.ResourceHttpMessageConverter.writeInternal(ResourceHttpMessageConverter.java:47)
org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:195)
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:238)
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:183)
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:126)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:832)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:743)
Question: how can I then cache the ResponseEntity
of type InputStreamResource
at all?
Cache manager will add to cache ResponseEntity
with InputStreamResource
inside of it. First time it will be ok. But when cached ResponseEntity
will try to read InputStreamResouce
second time you'll get exception, because it is unable to read stream more than one time.
Solution: don't cache InputStreamResouce
itself, but cache the content of stream.
@RestController
public class MyRestController {
@RequestMapping("/{code}")
@Cachable("code.cache")
public ResponseEntity<byte[]> getCodeLogo(@PathVariable("code") String code) {
FileSystemResource file = new FileSystemResource("d:/images/" + code + ".jpg");
byte [] content = new byte[(int)file.contentLength()];
IOUtils.read(file.getInputStream(), content);
return ResponseEntity.ok()
.contentType(MediaType.IMAGE_JPEG)
.lastModified(file.lastModified())
.contentLength(file.contentLength())
.body(content);
}
}
I've used IOUtils.read()
from org.apache.commons.io
, to copy bytes from stream to array, but you can do it by any preferred way.
You can't cache Streams. Once they are read, they are gone. The error message is pretty clear about that:
InputStream has already been read -
do not use InputStreamResource if a stream needs to be read multiple times
By your code and comments, it seems to me that you have a big images
folder with JPG logos (which might be added, deleted or modified), and you want to have a daily cache of the one's you're being asked for, so you don't have to constantly reload them from disk.
If that's the case, your best option is to read the File's content to a ByteArray and cache/return that instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With