BrowserCaching » History » Version 15
Jonathan Cormier, 2021-11-05 22:18
1 | 1 | dj jones | h1. BrowserCaching |
---|---|---|---|
2 | |||
3 | 5 | dj jones | {{toc}} |
4 | |||
5 | 2 | dj jones | h2. General Background: as to why caching in browser is a good thing |
6 | 1 | dj jones | |
7 | It is good practise on any website to let the browser cache objects that are static - ie don't contain user content, and are the same for hours, days or weeks. |
||
8 | |||
9 | (This in general means files like: .css, .js and image files) |
||
10 | |||
11 | But some content does change very fast, so must not be cached by the browser. |
||
12 | |||
13 | *If the browser is told 'don't cache this object':* |
||
14 | 4 | dj jones | then it knows not to. This is what Websites (and Redmine) do on the pages that may change: eg an issue page: it will put into the HTTP header that message. |
15 | 1 | dj jones | |
16 | *But in the absence of that - if the browser is not sure about a page component* |
||
17 | 4 | dj jones | that it already has recently downloaded, it will send a 304 request to the server: saying 'can you tell me, is this file still not stale'. And with a 304 the server does not need to send the whole object again: just a short 'yes, that is still fresh' answer. |
18 | 1 | dj jones | |
19 | 4 | dj jones | In Redmine's default set-up: it generates a lot of 304 connects on every page: you can see these in a tool like Firebug. One for every css and .js file etc. Many. |
20 | 1 | dj jones | |
21 | 4 | dj jones | These are bad for the user experience: because the browser has to wait for these responses, before it can carry on and build the page. |
22 | 1 | dj jones | |
23 | *So we need to tell the browser that these objects will not be stale for a long time* |
||
24 | |||
25 | 9 | dj jones | There are easy ways to configure Apache and nginx to do this: by telling them to set the 'Expiry' date in the HTTP Header well into the future: so that the browser knows: 'OK, this objects is not stale, because we are still before the expiry date'. |
26 | 1 | dj jones | |
27 | 4 | dj jones | Thus when a new user visits Redmine, their browser on the first page will GET the .css and .js files etc, but on pages after that: does not need to get them again. |
28 | 1 | dj jones | |
29 | 9 | dj jones | The user will experience faster web page builds! |
30 | 1 | dj jones | |
31 | h2. The Problem with Redmine |
||
32 | 6 | dj jones | |
33 | 9 | dj jones | Unfortunately, RedMine (rather unwisely) uses .js file names, for things that DO contain user content: ie things that should NOT be cached in the browser. |
34 | 1 | dj jones | |
35 | So this means; if in Apache/nginx you add a simple config, to cache anyting that is named *.js: then your Redmine will break! |
||
36 | |||
37 | 9 | dj jones | See the Issue #17770 - where this problem is reported, to see if the RedMine team can change it, to STOP using the .js in bad places. |
38 | 1 | dj jones | |
39 | Tne places it breaks if you use a simple config are: (a) when editing a journal in an issue (b) when uploading a file to an issue |
||
40 | |||
41 | 14 | Jonathan Cormier | h2. The work around for RedMine |
42 | 3 | dj jones | |
43 | 11 | dj jones | The simple case, that will break Redmine as above, because it does not care about which directory the js files are in:eg: |
44 | 1 | dj jones | <pre> |
45 | 11 | dj jones | location ~* \.(ico|css|js|gif|jp?g|png)(\?[0-9]+)?$ { |
46 | 1 | dj jones | expires 365d; |
47 | } |
||
48 | </pre> |
||
49 | |||
50 | 11 | dj jones | So need to use a more complex configuration: that also checks which directory the .js file is in, before setting the cache heading. |
51 | |||
52 | I got this working |
||
53 | 1 | dj jones | <pre> |
54 | 11 | dj jones | # the regex logic: after either /javascripts/ or /stylesheets/ find the suffixes we want, followed by any quantity of numbers 0-9 |
55 | # This works because the files we want to cache always appear after one of those 2 directories: but not the files we want to ignore |
||
56 | 13 | dj jones | # /journals/edit/24174.js and /uploads.js?attachment_id=1&filename=my-file-to-upload.png |
57 | 15 | Jonathan Cormier | location ~* /(?:(?:plugin_assets/|themes/).+)?(javascripts|stylesheets|images|favicon)/.+(css|js|jpg|gif|ico|png)(\?[0-9]+)$ { |
58 | 12 | dj jones | # add_header X-SV-test 304-killer; use this do-nothing HTTP Header, if you need to play with the regexp |
59 | #- for testing without fear of breaking anything! |
||
60 | 11 | dj jones | expires 365d; |
61 | } |
||
62 | </pre> |
||
63 | 1 | dj jones | |
64 | 14 | Jonathan Cormier | Since redmine 3.4.0 (#24617), static files requests have a ?timestamp appended to them, to avoid issues when upgrading redmine versions and cache serving the wrong files. However, custom themes don't append a timestamp to the request so application.css and responsive.css can break spectacularly. See #29625 for a workaround. |
65 | 1 | dj jones | |
66 | 14 | Jonathan Cormier | If you want to be safe, don't cache files which don't have a ?timestamp |
67 | <pre><code class="diff"> |
||
68 | - # the regex logic: after either /javascripts/ or /stylesheets/ find the suffixes we want, followed by any quantity of numbers 0-9 |
||
69 | + # the regex logic: after either /javascripts/ or /stylesheets/ find the suffixes we want, followed by one or more numbers 0-9 |
||
70 | # This works because the files we want to cache always appear after one of those 2 directories: but not the files we want to ignore |
||
71 | # /journals/edit/24174.js and /uploads.js?attachment_id=1&filename=my-file-to-upload.png |
||
72 | 15 | Jonathan Cormier | - location ~* /(?<file>/(?:(?:plugin_assets/|themes/).+)?(?:javascripts|stylesheets|images|favicon)/.+(?:css|js|jpe?g|gif|ico|png|html)(\?[0-9]+)?$) { |
73 | + location ~* /(?<file>/(?:(?:plugin_assets/|themes/).+)?(?:javascripts|stylesheets|images|favicon)/.+(?:css|js|jpe?g|gif|ico|png|html)(\?[0-9]+)$) { |
||
74 | 14 | Jonathan Cormier | </code></pre> |
75 | 4 | dj jones | |
76 | Note that in nginx: 'expires' is the config that sets the 'Expiry' HTTP header in the object. |
||
77 | |||
78 | In the above, 365d means 365 days. |
||
79 | |||
80 | 9 | dj jones | For full nginx 'expires' config details: http://nginx.org/en/docs/http/ngx_http_headers_module.html#expires |
81 | 10 | dj jones | For Apache: see mod_expires: http://httpd.apache.org/docs/2.2/mod/mod_expires.html |