Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

img HTML tag not honoring the HTTP Refresh header (but loading image URL in the browser does)

I'm serving a placeholder image with an HTTP Refresh header, like this:

Connection:keep-alive
Content-Type:image/gif
Date:Thu, 01 Aug 2013 14:16:25 GMT
Refresh:10; url=/media/thumbs/document/18.png
Server:nginx/1.4.1
Transfer-Encoding:chunked

If I load the image placeholder URL in the full window, the content is refreshed after 10 seconds, but if I put the same URL in the image tag src property, the image is never refreshed (tested in Chrome and FF).

Why am I doing this? I have an application where the thumbnail of a document takes a few seconds to be generated by the server (it is a complex SVG rendering). When the user goes to the document list right after creating a new document, the thumbnail may be not there yet. I was trying to use a placeholder image with a refresh header to load the real thumbnail after a few seconds.

I can't recall how, but back in the good old days of the Netscape browser, something like the refresh header was how we hacked animation sometimes (it was even before Flash - am I prehistoric or what?). We called it "server push" (I guess the the buzzword was recycled with the Comet model).

I'm using Angular.js at the client side (server side is Django+uWSGI+Nginx), so it should be easy enough to hack something using javascript, yet I'm curious: is there any trick (without javascript) to make the image tag load another source after a few seconds?

(I will tag this Django and Nginx as well because may be someone knows a server-side solution)

like image 984
Paulo Scardine Avatar asked Aug 01 '13 14:08

Paulo Scardine


2 Answers

If you don't need to support IE, and your svg generation doesn't take so long that it would cause a connection to timeout, you can achieve a similar effect using a Content-Type of multipart/x-mixed-replace. Something like this:

Content-Type: multipart/x-mixed-replace; boundary=myboundary

For your actual content, you start by writing out the placeholder images like this:

--myboundary
Content-Type: image/jpeg
Content-Length: <size of the image in bytes>

<binary image data + CR/LF>

Then immediately flush the output, but keep the connection alive (i.e. this will use a chunked Transfer-Encoding).

Now you can start generating your svg, and when it is finished, write it out like this:

--myboundary
Content-Type: image/svg+xml
Content-Length: <size of the image in bytes>

<svg image data + CR/LF>
--myboundary--

This is basically how streaming M-JPEG works, only in this case the frames aren't all jpeg images.

like image 200
James Holderness Avatar answered Nov 15 '22 22:11

James Holderness


Explanation

When you open an image directly in the browser, it's wrapped up as an HTML document.

Go to google's latest doodle jpg then open up your HTML console (http://www.google.com/logos/doodles/2013/erwin_schrdingers_126th_birthday-2002007-hp.jpg).

In Chrome you'll get:

<html><body style="margin: 0px;">
<img style="-webkit-user-select: none" src="http://www.google.com/logos/doodles/2013/erwin_schrdingers_126th_birthday-2002007-hp.jpg">
</body></html>

This "wrapping" isn't done for IMG tag src's that are inside of a document.

Solution

If you are intent on using just HTTP headers you're stuck with the multipart/x-mixed-replace described by @JamesHolderness, but note leaving HTTP connections hanging open like this can hurt your server performance and potentially open you up to a DDoS attack.

I was going to suggest using CSS animation / transition to lazy load an image. Unfortunately only Chrome support background-image (CSS property) animation. Another option would of been display: none since the image doesn't load until the element is displayed, but no browser supports animating or transitioning on the display (CSS property).

Here's the Chrome only, CSS only solution http://jsfiddle.net/r2Tag/

HTML

<div id="thumbnail"></div>

CSS

#thumbnail {
    position: relative;
    width: 475px;
    height: 184px;
    background-image:url('http://lh3.ggpht.com/Z9Bl8P_zqvnB_FPBw5PqZlHelALdwWoBV5EZSEVI85kS698xDzghSmLzREcaS1Uh31L5PIRdAiuMUcBSNlBGCsc-9YshQaxnMA4uzU2c-Q');
    animation: loadthumbnail 0s linear 10s;
    -webkit-animation: loadthumbnail 0s linear 10s;
    animation-fill-mode: forwards;
    -webkit-animation-fill-mode: forwards;
}
@keyframes loadthumbnail { to {
    width: 491px;
    height: 190px;
    background-image:url('https://www.google.com/logos/doodles/2013/erwin_schrdingers_126th_birthday-2002007-hp.jpg');
} }
@-webkit-keyframes loadthumbnail { to {
    width: 491px;
    height: 190px;
    background-image:url('https://www.google.com/logos/doodles/2013/erwin_schrdingers_126th_birthday-2002007-hp.jpg');
} }

EDIT If you use a data URI SVG as the background-image you animate to you can circumvent Chrome's parser pre-loading the image.

Example SVG

<svg x="0px" y="0px" width="491px" height="190px" viewbox="0 0 491 190" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink"><image externalResourcesRequired="true" xmlns="http://www.w3.org/2000/svg" x="0%" y="0%" height="100%" width="100%" xlink:href="https://www.google.com/logos/doodles/2013/erwin_schrdingers_126th_birthday-2002007-hp.jpg" xmlns:xlink="http://www.w3.org/1999/xlink"/></svg>

background-image: url('data:image/svg+xml;base64,PHN2ZyB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjQ5MXB4IiBoZWlnaHQ9IjE5MHB4IiB2aWV3Ym94PSIwIDAgNDkxIDE5MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48aW1hZ2UgZXh0ZXJuYWxSZXNvdXJjZXNSZXF1aXJlZD0idHJ1ZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4PSIwJSIgeT0iMCUiIGhlaWdodD0iMTAwJSIgd2lkdGg9IjEwMCUiIHhsaW5rOmhyZWY9Imh0dHBzOi8vd3d3Lmdvb2dsZS5jb20vbG9nb3MvZG9vZGxlcy8yMDEzL2Vyd2luX3NjaHJkaW5nZXJzXzEyNnRoX2JpcnRoZGF5LTIwMDIwMDctaHAuanBnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIvPjwvc3ZnPg==');
like image 36
Louis Ricci Avatar answered Nov 15 '22 20:11

Louis Ricci