Liipimaginebundle: Images not resolved by cache

Created on 13 Jan 2017  路  5Comments  路  Source: liip/LiipImagineBundle

I've been trying to debug this issue quite some time now, digging through old issue and documentation.

When i start with a clean /media/cache folder (proper permissions, not symlinked) after deploy, i receive lots of 404 errors when the page renders in the browser.

[2017-01-13 16:12:07] request.ERROR: Uncaught PHP Exception Symfony\Component\HttpKernel\Exception\NotFoundHttpException: "No route found for "GET /media/cache/avatarfilter/uploads/bst980/avatar/282c3874413d9efb83bb37a773d82245.jpeg" (from "https://website.com/incrediblepath")" at /home/www/webadmindev.vlh.de/releases/20170113151031/var/cache/prod/classes.php line 3915 {"exception":"[object] (Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException(code: 0): No route found for \"GET /media/cache/avatarfilter/uploads/bst980/avatar/282c3874413d9efb83bb37a773d82245.jpeg\" (from \"https://website.com/incrediblepath\") at /home/www/webadmindev.vlh.de/releases/20170113151031/var/cache/prod/classes.php:3915, Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException(code: 0): at /home/www/webadmindev.vlh.de/releases/20170113151031/var/cache/prod/appProdProjectContainerUrlMatcher.php:893)"}

As i get those entries in my prod.log file, i don't expect the apache config to break anything.. it's beeing processed by my application.
I'm using PHP 7.0.14, Symfony3.2.2 and LiipImagineBundle 1.6.0.

When i refresh the browsers cache, the images are properly generated. The overall configuration seems to work fine.. at least i think so...

My configuration:

liip_imagine:
    resolvers:
        default:
            web_path:
                web_root: "%kernel.root_dir%/../web"
                cache_prefix: "/media/cache"
    loaders:
        default:
            filesystem:
                data_root: "%kernel.root_dir%/../web"
    filter_sets:
        cache: ~
        thumb:
            quality: 50
            filters:
                resize: { size:  [64, 64] }

I'd be thankful for any hint, works like a charme on local machine, even with prod environment.. just the server keeps bugging me *sight

Most helpful comment

I think this is the same problem I have been having, which I think I have just now got to the bottom of.

When there is a copy of the image with the filter already applied stored in the cache, the imagine_filter twig filter generates a URL like this:

/media/cache/avatarfilter/uploads/bst980/avatar/282c3874413d9efb83bb37a773d82245.jpeg

As this URL directly points at the cached image file, the web server should just return that file, just like any other image.

When there doesn't yet exist a cached version of an image, the imagine_filter twig filter generates a URL like this:

/media/cache/**resolve/**avatarfilter/uploads/bst980/avatar/282c3874413d9efb83bb37a773d82245.jpeg (note the additional subdirectory)

If we look at the routing.xml in the bundle, there are only two routes:

<route id="liip_imagine_filter_runtime" path="/media/cache/**resolve/**{filter}/rc/{hash}/{path}" methods="GET">
    ...
</route>

<route id="liip_imagine_filter" path="/media/cache/**resolve/**{filter}/{path}" methods="GET">
    ...
</route>

The idea is that, when no cached version exists, the generated URL includes this resolve directory in its path, so the image request gets routed to the bundle, which goes ahead and generates a cached version. Once it has generated that file, the bundle sends a 301 Moved Permanently redirect response to the browser, which indicates to the browser that the image can now be found at /media/cache/avatarfilter/uploads/bst980/avatar/282c3874413d9efb83bb37a773d82245.jpeg.

The problem can be reproduced (on my Chrome install - Version 57.0.2987.98 (64-bit), at least) by doing the following:

  • Load page with filtered image for the first time (LiipImagineBundle generates the thumbnail and caches it)
  • Reload page (this time, the web server responds to the request for an image file directly, and PHP/LiipImagineBundle isn't involved at all)
  • Delete the cached version of the image
  • Reload the page.

    • This time, the filter produces the URL with the resolve/ directory in it: /media/cache/**resolve/**avatarfilter/uploads/bst980/avatar/282c3874413d9efb83bb37a773d82245.jpeg

    • However, the browser cache, having already discovered that this URL is simply a 301 Moved Permanently does not make another request to the server.

    • Instead, it uses what it learned before, and tries to fetch the image from its new permanent location: /media/cache/avatarfilter/uploads/bst980/avatar/282c3874413d9efb83bb37a773d82245.jpeg

    • Obviously, this has been removed, and not yet regenerated (because the browser didn't repeat the request that resulted in a 301 response). Hence, you receive a 404 Not Found response from the web server.

Possible solution?

It seems to me that sending a 302 Moved Temporarily response from ImagineController's filterAction instead would solve this problem, because browsers shouldn't cache 302s, but most will cache 301s. In some cases it could result in additional requests to the resolve/ URL (from cached HTML pages), but this would still be handled gracefully by the bundle without any further changes, and would not be a persistent problem once caches were updated.

I'm happy to create a PR for the above with some of the details from above if it's considered a good solution.

All 5 comments

I think this is the same problem I have been having, which I think I have just now got to the bottom of.

When there is a copy of the image with the filter already applied stored in the cache, the imagine_filter twig filter generates a URL like this:

/media/cache/avatarfilter/uploads/bst980/avatar/282c3874413d9efb83bb37a773d82245.jpeg

As this URL directly points at the cached image file, the web server should just return that file, just like any other image.

When there doesn't yet exist a cached version of an image, the imagine_filter twig filter generates a URL like this:

/media/cache/**resolve/**avatarfilter/uploads/bst980/avatar/282c3874413d9efb83bb37a773d82245.jpeg (note the additional subdirectory)

If we look at the routing.xml in the bundle, there are only two routes:

<route id="liip_imagine_filter_runtime" path="/media/cache/**resolve/**{filter}/rc/{hash}/{path}" methods="GET">
    ...
</route>

<route id="liip_imagine_filter" path="/media/cache/**resolve/**{filter}/{path}" methods="GET">
    ...
</route>

The idea is that, when no cached version exists, the generated URL includes this resolve directory in its path, so the image request gets routed to the bundle, which goes ahead and generates a cached version. Once it has generated that file, the bundle sends a 301 Moved Permanently redirect response to the browser, which indicates to the browser that the image can now be found at /media/cache/avatarfilter/uploads/bst980/avatar/282c3874413d9efb83bb37a773d82245.jpeg.

The problem can be reproduced (on my Chrome install - Version 57.0.2987.98 (64-bit), at least) by doing the following:

  • Load page with filtered image for the first time (LiipImagineBundle generates the thumbnail and caches it)
  • Reload page (this time, the web server responds to the request for an image file directly, and PHP/LiipImagineBundle isn't involved at all)
  • Delete the cached version of the image
  • Reload the page.

    • This time, the filter produces the URL with the resolve/ directory in it: /media/cache/**resolve/**avatarfilter/uploads/bst980/avatar/282c3874413d9efb83bb37a773d82245.jpeg

    • However, the browser cache, having already discovered that this URL is simply a 301 Moved Permanently does not make another request to the server.

    • Instead, it uses what it learned before, and tries to fetch the image from its new permanent location: /media/cache/avatarfilter/uploads/bst980/avatar/282c3874413d9efb83bb37a773d82245.jpeg

    • Obviously, this has been removed, and not yet regenerated (because the browser didn't repeat the request that resulted in a 301 response). Hence, you receive a 404 Not Found response from the web server.

Possible solution?

It seems to me that sending a 302 Moved Temporarily response from ImagineController's filterAction instead would solve this problem, because browsers shouldn't cache 302s, but most will cache 301s. In some cases it could result in additional requests to the resolve/ URL (from cached HTML pages), but this would still be handled gracefully by the bundle without any further changes, and would not be a persistent problem once caches were updated.

I'm happy to create a PR for the above with some of the details from above if it's considered a good solution.

I had the same problem as @ghostal and solved adding a query parameter to the resolve URL. Something like:

public function getPath()
{
    return $this->path ? $this->path . '?v=' . time() : null;
}

In this way, if the thumbnails have been removed, the browser will try to get the image instead of directly redirect, as the URL is not identical.

Hope this helps

I'm having the sam problem, tx for the explanation. I've never understood that issue.
Maybe someone can explain further how to solve this problem.
@msalsas : where did u place the function, can you clearify it to me ?
@ghostal : Changing ImagineController is not permanent, no?

Tx all!

Symfony supports a plethora of cache-busting techniques natively, namely through the asset() Twig function and the corresponding version, version_format, and json_manifest_path configuration options.

right, didnt see the wood because of all the trees ;)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tobias-r picture tobias-r  路  4Comments

fueguino84 picture fueguino84  路  3Comments

robfrawley picture robfrawley  路  4Comments

Aerendir picture Aerendir  路  3Comments

ghost picture ghost  路  5Comments