Caching renderBinary

252 views
Skip to first unread message

indrek

unread,
Apr 20, 2011, 3:40:50 PM4/20/11
to play-framework
I use the Blob field to store uploaded images. Now when I
renderBinary() the images in PROD mode, the images are not cached (no
ETag applied), meaning all the images are reloaded by the browser on
each load. When I use the @CacheFor before the controller's method, I
start getting IOExceptions after first load:

Oops: IOException
An unexpected error occured caused by exception IOException: Read
error

play.exceptions.UnexpectedException: Unexpected Error
at play.mvc.results.RenderBinary.apply(RenderBinary.java:162)
at play.mvc.ActionInvoker.invoke(ActionInvoker.java:307)
at Invocation.HTTP Request(Play!)
Caused by: java.io.IOException: Read error
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:177)
at play.mvc.results.RenderBinary.apply(RenderBinary.java:154)
... 2 more

Erwan Loisant

unread,
Apr 21, 2011, 4:10:44 AM4/21/11
to play-fr...@googlegroups.com
Hi,

There are several things:
* The @CacheFor annotation is for server side caching. Correctly used
it can improve performances. If you want to set the cache header, you
should use response.cacheFor(duration). A max-age header will be
added.
* The Etag should always be there I think
* The exception is probably a bug in the @CacheFor annotation. If you
can reproduce it consistently could you open a report?

> --
> You received this message because you are subscribed to the Google Groups "play-framework" group.
> To post to this group, send email to play-fr...@googlegroups.com.
> To unsubscribe from this group, send email to play-framewor...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/play-framework?hl=en.
>
>

--
Erwan Loisant

indrek

unread,
Apr 21, 2011, 7:59:40 AM4/21/11
to play-framework
The client side caching (ETag) is not in my headers...

Response Headerspretty print

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Server: Play! Framework;1.1.1;prod
Content-Type: text/plain; charset=utf-8
Content-Disposition: inline
Set-Cookie: PLAY_FLASH=;Path=/
PLAY_ERRORS=;Path=/
PLAY_SESSION=fcecce70514ebd9a40ec0ef56144fb0daedeffa5-%00___ID
%3A178e6c98-e7c1-4a95-8ba9-0ba938d9b510%00;Path=/
Cache-Control: no-cache
Date: Thu, 21 Apr 2011 11:54:50 GMT


Request Headerspretty print

GET /offers/getImage?imageId=14 HTTP/1.1
Host: hubb.ee
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:
1.9.2.12) Gecko/20101026 Firefox/3.6.12
Accept: image/png,image/*;q=0.8,*/*;q=0.5
Accept-Language: en-gb,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Referer: http://hubb.ee/
Cookie: __utma=252080400.632771543.1303323406.1303323406.1303384723.2;
__utmz=252080400.1303323406.1.1.utmcsr=(direct)|utmccn=(direct)|
utmcmd=(none); PLAY_FLASH=; PLAY_ERRORS=;
PLAY_SESSION=fcecce70514ebd9a40ec0ef56144fb0daedeffa5-%00___ID
%3A178e6c98-e7c1-4a95-8ba9-0ba938d9b510%00; __utmc=252080400


Code:
public static void getImage(Long imageId){
Image image = Image.findById(imageId);
if (image != null){
renderBinary(image.picture.get());
}else{
notFound();
}

}

@Entity
public class Image extends Model {
@Required
public Blob picture;

// @Required
// public String extension;

@OneToOne(mappedBy = "image")
public Offer offer;

Alexander Reelsen

unread,
Apr 21, 2011, 9:26:46 AM4/21/11
to play-fr...@googlegroups.com
Hi

Erwan Loisant wrote:
> There are several things:
> * The @CacheFor annotation is for server side caching. Correctly used
> it can improve performances. If you want to set the cache header, you
> should use response.cacheFor(duration). A max-age header will be
> added.
> * The Etag should always be there I think

In case of binary content a last-modified header should be sufficient as
well (without etag), but would have to set in the program logic as well.
Also highly depends of course of the uniqueness of your URL...


--Alexander

indrek

unread,
Apr 23, 2011, 1:41:38 PM4/23/11
to play-framework
I didn't find anywhere in Play docs/source that ETag is automatically
applied when calling renderBinary. I modified my controller method to
supply the ETag and now the images are nicely cached on browser-side:

public static void getImage(Long imageId){
// Check if browser already has this image
if (request.headers.containsKey("if-none-match") &&
("img_" +imageId).equals(request.headers.get("if-none-
match").value())){
notModified();
}

Image image = Image.findById(imageId);
if (image != null){
String etag = "img_" + image.id;

response.setHeader("Etag", etag);
renderBinary(image.picture.get(), etag, image.picture.type(),
true);
}else{
notFound();

Ivan San José García

unread,
Apr 25, 2011, 8:36:13 AM4/25/11
to play-fr...@googlegroups.com
You can see it in framework/src/play/server/PlayHandler.java:

public void copyResponse(ChannelHandlerContext ctx, Request request,
Response response, HttpRequest nettyRequest) throws Exception {
Logger.trace("copyResponse: begin");
...
final boolean keepAlive = isKeepAlive(nettyRequest);
if (file != null && file.isFile()) {
try {
nettyResponse = addEtag(nettyRequest, nettyResponse, file);
..

And the addEtag() method is in the same JAVA file.

So, if you did a renderBinary() of a variable that is a File type,
ETag should be in HTTP response, unless you have disabled explicitly
the ETag header.

You can disable ETag header putting http.useETag=false in your application.conf.

Hope this helps.

2011/4/23 indrek <ind...@gmail.com>:

indrek

unread,
Apr 26, 2011, 1:52:11 PM4/26/11
to play-framework
Yea thanks, I was sending an inputstream instead of file to
renderBinary. But when I was trying to send my own cache-control
header with renderBinary(InputStream), it got overriden. Any way to
disable that behavior?

On Apr 25, 3:36 pm, Ivan San José García <ivan...@gmail.com> wrote:
> You can see it in framework/src/play/server/PlayHandler.java:
>
> public void copyResponse(ChannelHandlerContext ctx, Request request,
> Response response, HttpRequest nettyRequest) throws Exception {
>         Logger.trace("copyResponse: begin");
> ...
>         final boolean keepAlive = isKeepAlive(nettyRequest);
>         if (file != null && file.isFile()) {
>             try {
>                 nettyResponse = addEtag(nettyRequest, nettyResponse, file);
> ..
>
> And the addEtag() method is in the same JAVA file.
>
> So, if you did arenderBinary() of a variable that is a File type,
> ETag should be in HTTP response, unless you have disabled explicitly
> the ETag header.
>
> You can disable ETag header putting http.useETag=false in your application.conf.
>
> Hope this helps.
>
> 2011/4/23 indrek <ind...@gmail.com>:
>
>
>
>
>
>
>
> > I didn't find anywhere in Play docs/source that ETag is automatically
> > applied when callingrenderBinary. I modified my controller method to

Ivan San José García

unread,
Apr 26, 2011, 3:01:40 PM4/26/11
to play-fr...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages