Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache buster value doesn't change on change of plugin JavaScripts #8713

Closed
Joey3000 opened this issue Sep 4, 2015 · 10 comments
Closed

Cache buster value doesn't change on change of plugin JavaScripts #8713

Joey3000 opened this issue Sep 4, 2015 · 10 comments
Labels
Bug For errors / faults / flaws / inconsistencies etc. c: Platform For Matomo platform changes that aren't impacting any of our APIs but improve the core itself. wontfix If you can reproduce this issue, please reopen the issue or create a new one describing it.

Comments

@Joey3000
Copy link
Contributor

Joey3000 commented Sep 4, 2015

I might be missing something I need to trigger in my plugin, but it seems that plugins/Proxy or core/ProxyHttp.php don not detect removal of minified assets.

Test procedure:

  1. Via FTP, remove /tmp/assets/asset_manager_non_core_js.js
  2. Load the login page (/index.php)

Expected results:

  1. File removed
  2. The removed file gets re-generated and served to the browser

Actual results:
2. The removed file dos not get re-generated. Piwik responds with status code 200 (OK), but a zero length body. Browser uses a locally cached copy of the above asset. Following request is can be seen in Firefox developer console:

Request URL: [removed]/index.php?module=Proxy&action=getNonCoreJs&cb=15e17d580a09498a50eaae89a10dccf0
Request Method: GET
Status Code: HTTP/1.1 200 OK
Request Headers 00:52:08.000
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:39.0) Gecko/20100101 Firefox/39.0
Referer: [removed]/index.php?module=CoreHome&action=&period=range&date=last30
Host: [removed]
DNT: 1
Connection: keep-alive
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Accept: */*
Sent Cookie
PHPSESSID: 15d511c02bc03f6e499bb8a2f87796ce
Response Headers Δ0ms
X-Powered-By: PHP/5.5.22
Vary: Accept-Encoding
Server: Apache
Last-Modified: Fri, 04 Sep 2015 21:29:00 GMT
Expires: Sun, 13 Dec 2015 22:46:07 GMT
Date: Fri, 04 Sep 2015 22:45:54 GMT
Content-Type: application/javascript; charset=UTF-8
Content-Length: 17026
Content-Disposition: inline; filename=asset_manager_non_core_js.js
Cache-Control: public, must-revalidate
No Response Body Δ0ms

It is only with a forced reload (Shift + reload on Firefox; whereby clearing browser cache and reloading normally would work as well, I guess) that the removed file gets re-generated in /tmp/assets and served as follows:

Request URL: [removed]/index.php?module=Proxy&action=getNonCoreJs&cb=15e17d580a09498a50eaae89a10dccf0
Request Method: GET
Status Code: HTTP/1.1 200 OK
Request Headers 01:00:25.000
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:39.0) Gecko/20100101 Firefox/39.0
Referer: [removed]/index.php?module=CoreHome&action=&period=range&date=last30
Pragma: no-cache
Host: [removed]
DNT: 1
Connection: keep-alive
Cache-Control: no-cache
Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Accept: */*
Sent Cookie
PHPSESSID: 15d511c02bc03f6e499bb8a2f87796ce
Response Headers Δ8873ms
X-Powered-By: PHP/5.5.22
Vary: Accept-Encoding
Server: Apache
Last-Modified: Fri, 04 Sep 2015 23:00:34 GMT
Keep-Alive: timeout=4, max=300
Expires: Sun, 13 Dec 2015 23:00:34 GMT
Date: Fri, 04 Sep 2015 23:00:26 GMT
Content-Type: application/javascript; charset=UTF-8
Content-Length: 17026
Content-Disposition: inline; filename=asset_manager_non_core_js.js
Connection: Keep-Alive
Cache-Control: public, must-revalidate

Please note the Pragma: no-cache and Cache-Control: no-cache in the browser request on the forced reload above.

The real world impact is the following: I'm working on a plugin which encrypts user passwords when transmitted from browser to the Piwik server on login, which is useful when no HTTPS is possible. RSA public-key cryptography is used, whereby the public key is placed into a JS file to be served to the browser. I do that, call

AssetManager::getInstance()->removeMergedAssets([my_plugin_name]);

to remove /tmp/assets/asset_manager_non_core_js.js, which works fine. The problem is that when a user tries to login after generation of new keys in plugin settings (or after plugin installation, upon which the keys are generated automatically), the decryption (and thus also login) fails, because the browser still uses the old asset containing the old key (or none after new installation).

I do output respective Exception message, prompting a clearing of browser cache and re-loading, which wouldn't be too bad. But what happens on the following successful login is that the user is greeted with another fear inducing WARNINGs and "report this on the Piwik forum" (one warning per failed login attempt) due to the previous Exception on the login. And that warning is not even necessarily related to the currently logged in user - it is shown on a successful login of any user, after failed attempts by any user.

I tried removing all cache:

Filesystem::deleteAllCacheOnUpdate();

but that had no effect apart from removing more cached files. Tried triggering re-generation of the asset file:

AssetManager::getInstance()->getMergedNonCoreJavaScript();

Which did re-generate /tmp/assets/asset_manager_non_core_js.js, but it still didn't get served.

Shouldn't plugins/Proxy or core/ProxyHttp.php detect absence of a minified asset file, trigger its generation and serve that newly generated file? Or do I miss some trigger I need to call to make that happen?

There doesn't seem to be a way to disable minification per-plugin either. Which could work as a temporary solution, but would not be ideal performance-wise, because my generated JS file changes very rarely - only when the super user decides to generate new keys.

Piwik 2.14.3, PHP 5.5.22, Apache on Debian

@Joey3000
Copy link
Contributor Author

Joey3000 commented Sep 6, 2015

Here is the offending plugin, by the way: https://github.com/Joey3000/piwik-LoginEncrypted

@tsteur
Copy link
Member

tsteur commented Sep 7, 2015

Which did re-generate /tmp/assets/asset_manager_non_core_js.js, but it still didn't get served

Before looking any further into it, have you checked the "Network" tab in your browser re caching headers / etag? Maybe it was still cached in your browser.

@Joey3000
Copy link
Contributor Author

Joey3000 commented Sep 7, 2015

Thanks for looking into this. I've played around a bit more with this, and here are the results.

You are right, if one just navigates to the login page (e.g. signing out of Piwik after generation of new keys) the "Transferred" column in the Network tab says "cached" for the CSS, core and non-core asset files, meaning, I guess that they don't actually get requested.

One can, actually, get stuck using the cached files if one then (after an unsuccessful login) gets presented an exception page due to failed decryption, and then navigates to the login page again using the "Go to Piwik" link on the exception page.

But: A simple login page reload by the user (i.e. just a click on the reload arrow; not a forced reload) is enough to make the browser request and download the updated assets.

Calling

AssetManager::getInstance()->getMergedNonCoreJavaScript();

to re-generate the assets right after having removed them by

AssetManager::getInstance()->removeMergedAssets([my_plugin_name]);

makes no difference - if missing, the assets will be re-generated when the browser requests them anyway.

I guess that the reason for the browser using the local cache is that the cache buster value on the HTML doesn't change, not even when one calls the above re-generation function. It looks like an MD5 hash and is equal for the CSS, the core and non-core JS minified assets, as well as other files. (A hash of all together at some point in the past?)

It's always, e.g. [removed]/index.php?module=Proxy&action=getNonCoreJs&cb=d4b95f8d81cc43b9b783a06d1997c8c2

Is there a way to trigger re-generation of the cache buster value?

I can image the plugin doing, e.g.:

  1. Remove non-core JS asset: AssetManager::getInstance()->removeMergedAssets([my_plugin_name]);
  2. Re-generate the asset: AssetManager::getInstance()->getMergedNonCoreJavaScript();
  3. Re-generate the cache buster value (or: this could be done automatically if asset is detected missing by the above step and therefore re-generated [see note below])

So that an up-to-date cache buster value exists whenever the login HTML page gets navigated to without a page reload request by the user.

[Note] I don't know though, if a possible value change in between page resource requests would have a impact in general Piwik usage, not related to this plugin - e.g., due to asset absence detection after (de-)activation of a plugin. For example:

  • The HTML page gets requested, containing an old cache buster value (it's there plenty of times, not just in the minified assets requests)
  • The CSS asset gets requested using the old value -> OK
  • The non-core JS asset gets requested using the old value, is absent and a new cache buster value gets generated -> OK
  • The core JS asset and other page resources get requested (still) using the old value -> would this be OK?
  • Any race conditions with resource requests around the time of the new value generation?

==> In case issues may occur on automatic cache buster value generation on asset absence detection, it could be done by the plugin separately on the step 3 above.

@Joey3000
Copy link
Contributor Author

Joey3000 commented Sep 7, 2015

Oops, I was wrong: the CSS asset uses a different cache buster value. But all JS page resources use a common one. And it seems to differ in some places in development mode (probably for instant change take-over, without caching):

  • In /core/View.php::render():
    if (Development::isEnabled()) {
        $cacheBuster = rand(0, 10000);
    } else {
        $cacheBuster = UIAssetCacheBuster::getInstance()->piwikVersionBasedCacheBuster();
    }
  • In /core/View.php::applyFilter_cacheBuster():
    $cacheBuster = UIAssetCacheBuster::getInstance();
    $tagJs       = 'cb=' . $cacheBuster->piwikVersionBasedCacheBuster();
    $tagCss      = 'cb=' . $cacheBuster->md5BasedCacheBuster($content);
  • Whereby /core/AssetManager/UIAssetCacheBuster.php::piwikVersionBasedCacheBuster():
    $pluginsInfo = '';
    foreach ($pluginNames as $pluginName) {
        $plugin       = Manager::getInstance()->getLoadedPlugin($pluginName);
        $pluginsInfo .= $plugin->getPluginName() . $plugin->getVersion() . ',';
    }
    $cacheBuster = md5($pluginsInfo . PHP_VERSION . Version::VERSION . trim($currentGitHash));
  • And /core/AssetManager/UIAssetCacheBuster.php::md5BasedCacheBuster():
    return md5($content);

So, the CSS minified asset cache buster value seems to be an MD5 checksum of the file content (see /core/AssetManager/UIAsset/OnDiskUIAsset.php::getContent()). While the JS one seems to be an MD5 checksum of a constant string containing names and versions of plugins, PHP and Piwik versions and a "current Git Hash" - so it's tied to the current environment versioning. No wonder it doesn't change. :)

Is it possible to change the core and non-core minified JS asset files to content-based MD5 checksums, like with the CSS one? (Or is there a reason for the constant value?)

@tsteur
Copy link
Member

tsteur commented Sep 9, 2015

I do not really remember the background of this but I think it was for performance. So far the JS files were not supposed to be changed unless a plugin was eg activated / deactivated etc and it was good enough.

I wonder if a workaround for now could be to manually load this JavaScript file from JS? Similar to RequireJS does etc. It would not be ideal for sure especially since the file would be requested and loaded only after all other JS is loaded.

What we could do, is to use the same cache buster for core JS file as we do currently, and for non-core JS files use the same as CSS. This should be still kinda fast as users usually do not use many custom plugins and not many have actually JS files defined. We'd have to test re performance though and I think it currently does not really have a priority unless it's super easy to implement. A PR is always welcome :)

Do you think you could maybe fix it with the workaround for now?

@tsteur tsteur added Bug For errors / faults / flaws / inconsistencies etc. c: Platform For Matomo platform changes that aren't impacting any of our APIs but improve the core itself. labels Sep 9, 2015
@tsteur
Copy link
Member

tsteur commented Sep 9, 2015

Nice plugin BTW 👍

@Joey3000
Copy link
Contributor Author

Joey3000 commented Sep 9, 2015

What we could do, is to use the same cache buster for core JS file as we do currently, and for non-core JS files use the same as CSS. This should be still kinda fast as users usually do not use many custom plugins and not many have actually JS files defined. We'd have to test re performance though and I think it currently does not really have a priority unless it's super easy to implement.

That sounds good to me as a long-term solution. No worries, it's not a "show stopper", because the issue doesn't result a silent fail, but I output clear instructions to the user as to what to do (clear browser cache, reload the page): https://github.com/Joey3000/piwik-LoginEncrypted/blob/1.0.1/lang/en.json#L3. And it doesn't happen often - only after new encryption keys generation (on plugin installation or when a super user manually re-generates them, which is not needed often and is actually not needed ever at all - I'm going to add this point to the FAQ). Although then every user will see the issue, no just the one who triggered the key re-generation.

Do you think you could maybe fix it with the workaround for now?

I'll have a look into it.

Nice plugin BTW

Thanks! I have to return the compliment. Piwik has been very well prepared for plugins - I see that on every turn (architecture, "how-to"s, examples in the code, etc.) - you guys are doing a great job with that. I myself am a noob with web development, although I have some background in C from a while ago, which helps.

Joey3000 added a commit to Joey3000/piwik-LoginEncrypted that referenced this issue Sep 10, 2015
@Joey3000 Joey3000 changed the title Removal of minified assets not detected by caching proxy Cache buster value doesn't change on change of plugin JavaScripts Sep 10, 2015
@Joey3000
Copy link
Contributor Author

Thanks for the hint with loading the key JavaScript file from JS! It works flawlessly. (I used the following: https://api.jquery.com/jQuery.getScript/). The file is fetched using an AJAX GET request and jQuery even appends an always changing cache busting parameter by default.

So, with that setup, Piwik does the minified JS cache buster update on plugin activation/deactivation (when the static plugin code gets added/removed to/from minified assets), which works (because the currently active plugins, with their names and versions, influence the cache buster value).
While the public key file (which changes on key change by a super user) is fetched by the static code with its own always different cache buster value and is therefore always up-to-date.

P.S.: I took the liberty of modifying the issue title, so that it better reflects what is currently known.
P.P.S: That the key file is loaded after everything else doesn't seem to matter much in my case, because the key in it is used for the first time only once the user clicks the "login" button (or the "submit" button on a password change request). So that there should be enough time for the file to be loaded.

@tsteur
Copy link
Member

tsteur commented Sep 11, 2015

Nice to hear 👍 and thx for changing the title 👍

@mattab mattab added this to the Mid term milestone Sep 20, 2015
@mattab mattab modified the milestones: Long term, Mid term Dec 5, 2016
@mattab
Copy link
Member

mattab commented Jun 19, 2017

As far as I understand, this shouldn't occur in a standard flow when a final user installs the plugin. When developing a plugin one should use the development mode (./console development:enable where Piwik makes sure to load the original source javascript files (not the merged one). So closing as wontfix. If i'm wrong feel free to comment/re-open

@mattab mattab closed this as completed Jun 19, 2017
@mattab mattab added the wontfix If you can reproduce this issue, please reopen the issue or create a new one describing it. label Jun 19, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug For errors / faults / flaws / inconsistencies etc. c: Platform For Matomo platform changes that aren't impacting any of our APIs but improve the core itself. wontfix If you can reproduce this issue, please reopen the issue or create a new one describing it.
Projects
None yet
Development

No branches or pull requests

3 participants