Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cache a InputStreamResource In RestController?

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?

like image 793
membersound Avatar asked Jun 23 '16 07:06

membersound


Video Answer


2 Answers

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.

like image 175
Ken Bekov Avatar answered Oct 19 '22 08:10

Ken Bekov


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.

like image 2
walen Avatar answered Oct 19 '22 08:10

walen