Skip to content

Instantly share code, notes, and snippets.

@khalwat
Created February 26, 2018 17:25
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save khalwat/a0687a8a8f0bf3ef5db7c371d88d3e04 to your computer and use it in GitHub Desktop.
Save khalwat/a0687a8a8f0bf3ef5db7c371d88d3e04 to your computer and use it in GitHub Desktop.
Keep hashed directories consistent in a load balanced server environment with Craft CMS 3
<?php
/**
* Yii Application Config
*
* Edit this file at your own risk!
*
* The array returned by this file will get merged with
* vendor/craftcms/cms/src/config/app/main.php and [web|console].php, when
* Craft's bootstrap script is defining the configuration for the entire
* application.
*
* You can define custom modules and system components, and even override the
* built-in system components.
*/
/**
* AdminCP resources in Craft CMS 3 are published in the public @webroot/cpresources
* with a directory name that's a hashed from the directory path and the timestamp
* of when the resource was published.
*
* On load balanced environments, the addition of a timestamp can cause 404s as
* the timestamp will vary from server to server. You can work around this problem
* by providing your own `hashCallback` function to generate a hash of the directory
* path without a timestamp.
*
* The `hashCallback` can actually be any PHP callable, but here it's presented
* as an anonymous function. c.f.:
*
* vendor/craftcms/cms/src/config/app.web.php
* vendor/craftcms/cms/src/web/AssetManager.php
*
* @author nystudio107
* @package n/a
* @since 1.0.0
*/
return [
// All environments
'*' => [
],
// Live (production) environment
'live' => [
'components' => [
'assetManager' => function() {
$generalConfig = Craft::$app->getConfig()->getGeneral();
$config = [
'class' => craft\web\AssetManager::class,
'basePath' => $generalConfig->resourceBasePath,
'baseUrl' => $generalConfig->resourceBaseUrl,
'fileMode' => $generalConfig->defaultFileMode,
'dirMode' => $generalConfig->defaultDirMode,
'appendTimestamp' => true,
'hashCallback' => function ($path) {
return hash('md4', $path);
}
];
return Craft::createObject($config);
},
],
],
// Staging (pre-production) environment
'staging' => [
],
// Local (development) environment
'local' => [
],
];
@ostark
Copy link

ostark commented Mar 9, 2018

@khalwat

With a little less code:

 // Yes, these bootstrappers can be Callables and 
 // don't require to implement BootstrapInterface

 'bootstrap' => [function () {
       \yii\base\Event::on(\craft\web\Controller::class, \craft\web\Controller::EVENT_BEFORE_ACTION, function () {
            \Craft::$app->assetManager->hashCallback = function ($path) {
                return hash('md4', $path);
            };
        });
  }],

This way we touch just a single property ($hashCallback ), instead of repeating the whole config for AssetManager.

It must happen after Application::ensureResourcePathExists() is called, that's why I wrapped it in an EVENT_INIT handler.
Controller::EVENT_BEFORE_ACTION handler

@ostark
Copy link

ostark commented Mar 9, 2018

However, both approaches do not fully mitigate the Problem. The paths are consistent, but:

If the first request (e.g. GET /admin) is handled by Webserver1, all /cpresources/* get generated there.
But if the next request goes to a static resource (e.g. GET /cpresources/f3d68a1b74ef1a6133bc59d0e4d8b87c/js/Craft.min.js?v=1520600) on Webserver2, the file does not exist until they are generated by a dynamic request.

@ostark
Copy link

ostark commented Mar 9, 2018

Something like this might work as a command in the build process:

foreach(array_keys($classLoader->getClassMap()) as $class) {
 	if ($class instanceof AssetBundle)) {
 	  \Craft::$app->getView()->registerAssetBundle($class);
 	}
 } 

this way we don't need to set $hashCallback - I'll try if it works.

@khalwat
Copy link
Author

khalwat commented Mar 9, 2018

Right @ostark, the supposition is that we have the /cpresources/ in a shared NFS (or whatever) volume

@timkelty
Copy link

timkelty commented Apr 9, 2018

@ostark you ever get anywhere with this? I'm trying to avoid having a shared volume (performance sucks) in a LB setup.

@timkelty
Copy link

timkelty commented Apr 9, 2018

Posted issue here: craftcms/cms#2712

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment