Project

General

Profile

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