Created
February 26, 2018 17:25
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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' => [ | |
], | |
]; |
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.
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.
Right @ostark, the supposition is that we have the /cpresources/
in a shared NFS (or whatever) volume
Posted issue here: craftcms/cms#2712
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@khalwat
With a little less code:
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