Skip to content

Commit 40c2111

Browse files
committed
Bugfix: cache control response value is now mag-age
(as per RFC9111)
1 parent 1eb349a commit 40c2111

File tree

7 files changed

+54
-55
lines changed

7 files changed

+54
-55
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,17 @@ Please see the [GoDoc](https://godoc.org/github.com/rickb777/servefiles) for mor
2121
User agents can cache responses. This http server enables easy support for two such mechanisms:
2222

2323
* Conditional requests (using `etags`) allow the response to be sent only when it has changed
24-
* MaxAge response headers allow the user agent to cache entities until some expiry time.
24+
* Cache-Control `max-age` response headers allow the user agent to cache entities until some expiry time.
2525

26-
Note that conditional requests (RFC7232) and MaxAge caching (RFC7234) can work together as required. Conditional requests still require network round trips, whereas caching removes all network round-trips until the entities reach their expiry time.
26+
Note that conditional requests [RFC9110](https://www.rfc-editor.org/rfc/rfc9110#name-conditional-requests) and `max-age` caching ([RFC9111](https://www.rfc-editor.org/rfc/rfc9111#section-5.2.2.1)) can work together as required. Conditional requests still require network round trips, whereas caching removes all network round-trips until the entities reach their expiry time.
27+
28+
## Echo Adapter
29+
30+
Sub-package echo_adapter provides integration hooks into the [Echo web framework](https://echo.labstack.com/). This makes it easy for Echo code to use this asset handler also: see the example in the sub-package for more info.
2731

2832
## Gin Adapter
2933

30-
Sub-package gin_adapter provides integration hooks into the [Gin web framework](github.com/gin-gonic/gin). This makes it easy for Gin code to use this asset handler also: see the example in the sub-package for more info.
34+
Sub-package gin_adapter provides integration hooks into the [Gin web framework](https://github.com/gin-gonic/gin). This makes it easy for Gin code to use this asset handler also: see the example in the sub-package for more info.
3135

3236
## v3
3337

assets.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ func (a *Assets) chooseResource(header http.Header, req *http.Request) (string,
226226

227227
if a.MaxAge > 0 {
228228
header.Set("Expires", a.expires())
229-
header.Set("Cache-Control", fmt.Sprintf("public, maxAge=%d", a.MaxAge/time.Second))
229+
header.Set("Cache-Control", fmt.Sprintf("public, max-age=%d", a.MaxAge/time.Second))
230230
}
231231

232232
acceptEncoding := commaSeparatedList(req.Header.Get("Accept-Encoding"))

assets_test.go

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func TestChooseResourceSimpleDirNoGzip(t *testing.T) {
6060
maxAge time.Duration
6161
url, path, cacheControl string
6262
}{
63-
{0, 1, "/", "assets/index.html", "public, maxAge=1"},
63+
{0, 1, "/", "assets/index.html", "public, max-age=1"},
6464
}
6565

6666
for i, test := range cases {
@@ -89,9 +89,9 @@ func TestChooseResourceSimpleNoGzip(t *testing.T) {
8989
maxAge time.Duration
9090
url, path, cacheControl string
9191
}{
92-
{0, 1, "/img/sort_asc.png", "assets/img/sort_asc.png", "public, maxAge=1"},
93-
{0, 3671, "/img/sort_asc.png", "assets/img/sort_asc.png", "public, maxAge=3671"},
94-
{3, 3671, "/x/y/z/img/sort_asc.png", "assets/img/sort_asc.png", "public, maxAge=3671"},
92+
{0, 1, "/img/sort_asc.png", "assets/img/sort_asc.png", "public, max-age=1"},
93+
{0, 3671, "/img/sort_asc.png", "assets/img/sort_asc.png", "public, max-age=3671"},
94+
{3, 3671, "/x/y/z/img/sort_asc.png", "assets/img/sort_asc.png", "public, max-age=3671"},
9595
}
9696

9797
for i, test := range cases {
@@ -137,7 +137,7 @@ func TestChooseResourceSimpleNonExistent(t *testing.T) {
137137
//t.Logf("header %v", w.Header())
138138
isGte(t, len(w.Header()), 4, i)
139139
isEqual(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8", i)
140-
isEqual(t, w.Header().Get("Cache-Control"), "public, maxAge=1", i)
140+
isEqual(t, w.Header().Get("Cache-Control"), "public, max-age=1", i)
141141
isGte(t, len(w.Header().Get("Expires")), 25, i)
142142
}
143143
}
@@ -148,10 +148,10 @@ func TestServeHTTP200WithGzipAndGzipWithAcceptHeader(t *testing.T) {
148148
maxAge time.Duration
149149
url, mime, encoding, path, cacheControl string
150150
}{
151-
{0, 1, "/css/style1.css", cssMimeType, "xx, gzip, zzz", "assets/css/style1.css.gz", "public, maxAge=1"},
152-
{2, 1, "/a/b/css/style1.css", cssMimeType, "xx, gzip, zzz", "assets/css/style1.css.gz", "public, maxAge=1"},
153-
{0, 1, "/js/script1.js", javascriptMimeType, "xx, gzip, zzz", "assets/js/script1.js.gz", "public, maxAge=1"},
154-
{2, 1, "/a/b/js/script1.js", javascriptMimeType, "xx, gzip, zzz", "assets/js/script1.js.gz", "public, maxAge=1"},
151+
{0, 1, "/css/style1.css", cssMimeType, "xx, gzip, zzz", "assets/css/style1.css.gz", "public, max-age=1"},
152+
{2, 1, "/a/b/css/style1.css", cssMimeType, "xx, gzip, zzz", "assets/css/style1.css.gz", "public, max-age=1"},
153+
{0, 1, "/js/script1.js", javascriptMimeType, "xx, gzip, zzz", "assets/js/script1.js.gz", "public, max-age=1"},
154+
{2, 1, "/a/b/js/script1.js", javascriptMimeType, "xx, gzip, zzz", "assets/js/script1.js.gz", "public, max-age=1"},
155155
}
156156

157157
for _, test := range cases {
@@ -185,10 +185,10 @@ func TestServeHTTP200WithBrAndBrWithAcceptHeader(t *testing.T) {
185185
maxAge time.Duration
186186
url, mime, encoding, path, cacheControl string
187187
}{
188-
{0, 1, "/css/style1.css", cssMimeType, "br, gzip, zzz", "assets/css/style1.css.br", "public, maxAge=1"},
189-
{2, 1, "/a/b/css/style1.css", cssMimeType, "br, gzip, zzz", "assets/css/style1.css.br", "public, maxAge=1"},
190-
{0, 1, "/js/script1.js", javascriptMimeType, "br, gzip, zzz", "assets/js/script1.js.br", "public, maxAge=1"},
191-
{2, 1, "/a/b/js/script1.js", javascriptMimeType, "br, gzip, zzz", "assets/js/script1.js.br", "public, maxAge=1"},
188+
{0, 1, "/css/style1.css", cssMimeType, "br, gzip, zzz", "assets/css/style1.css.br", "public, max-age=1"},
189+
{2, 1, "/a/b/css/style1.css", cssMimeType, "br, gzip, zzz", "assets/css/style1.css.br", "public, max-age=1"},
190+
{0, 1, "/js/script1.js", javascriptMimeType, "br, gzip, zzz", "assets/js/script1.js.br", "public, max-age=1"},
191+
{2, 1, "/a/b/js/script1.js", javascriptMimeType, "br, gzip, zzz", "assets/js/script1.js.br", "public, max-age=1"},
192192
}
193193

194194
for _, test := range cases {
@@ -222,10 +222,10 @@ func TestServeHTTP200WithGzipButNoAcceptHeader(t *testing.T) {
222222
maxAge time.Duration
223223
url, mime, encoding, path, cacheControl string
224224
}{
225-
{0, 1, "/css/style1.css", cssMimeType, "xx, yy, zzz", "assets/css/style1.css", "public, maxAge=1"},
226-
{2, 2, "/a/b/css/style1.css", cssMimeType, "xx, yy, zzz", "assets/css/style1.css", "public, maxAge=2"},
227-
{0, 3, "/js/script1.js", javascriptMimeType, "xx, yy, zzz", "assets/js/script1.js", "public, maxAge=3"},
228-
{2, 4, "/a/b/js/script1.js", javascriptMimeType, "xx, yy, zzz", "assets/js/script1.js", "public, maxAge=4"},
225+
{0, 1, "/css/style1.css", cssMimeType, "xx, yy, zzz", "assets/css/style1.css", "public, max-age=1"},
226+
{2, 2, "/a/b/css/style1.css", cssMimeType, "xx, yy, zzz", "assets/css/style1.css", "public, max-age=2"},
227+
{0, 3, "/js/script1.js", javascriptMimeType, "xx, yy, zzz", "assets/js/script1.js", "public, max-age=3"},
228+
{2, 4, "/a/b/js/script1.js", javascriptMimeType, "xx, yy, zzz", "assets/js/script1.js", "public, max-age=4"},
229229
}
230230

231231
for _, test := range cases {
@@ -258,18 +258,18 @@ func TestServeHTTP200WithGzipAcceptHeaderButNoGzippedFile(t *testing.T) {
258258
maxAge time.Duration
259259
url, mime, encoding, path, cacheControl string
260260
}{
261-
{0, 1, "/css/style2.css", cssMimeType, "xx, gzip, zzz", "assets/css/style2.css", "public, maxAge=1"},
262-
{0, 1, "/css/style2.css", cssMimeType, "br, gzip, zzz", "assets/css/style2.css", "public, maxAge=1"},
263-
{2, 2, "/a/b/css/style2.css", cssMimeType, "xx, gzip, zzz", "assets/css/style2.css", "public, maxAge=2"},
264-
{2, 2, "/a/b/css/style2.css", cssMimeType, "br, gzip, zzz", "assets/css/style2.css", "public, maxAge=2"},
265-
{0, 3, "/js/script2.js", javascriptMimeType, "xx, gzip, zzz", "assets/js/script2.js", "public, maxAge=3"},
266-
{0, 3, "/js/script2.js", javascriptMimeType, "br, gzip, zzz", "assets/js/script2.js", "public, maxAge=3"},
267-
{2, 4, "/a/b/js/script2.js", javascriptMimeType, "xx, gzip, zzz", "assets/js/script2.js", "public, maxAge=4"},
268-
{2, 4, "/a/b/js/script2.js", javascriptMimeType, "br, gzip, zzz", "assets/js/script2.js", "public, maxAge=4"},
269-
{0, 5, "/img/sort_asc.png", "image/png", "xx, gzip, zzz", "assets/img/sort_asc.png", "public, maxAge=5"},
270-
{0, 5, "/img/sort_asc.png", "image/png", "br, gzip, zzz", "assets/img/sort_asc.png", "public, maxAge=5"},
271-
{2, 6, "/a/b/img/sort_asc.png", "image/png", "xx, gzip, zzz", "assets/img/sort_asc.png", "public, maxAge=6"},
272-
{2, 6, "/a/b/img/sort_asc.png", "image/png", "br, gzip, zzz", "assets/img/sort_asc.png", "public, maxAge=6"},
261+
{0, 1, "/css/style2.css", cssMimeType, "xx, gzip, zzz", "assets/css/style2.css", "public, max-age=1"},
262+
{0, 1, "/css/style2.css", cssMimeType, "br, gzip, zzz", "assets/css/style2.css", "public, max-age=1"},
263+
{2, 2, "/a/b/css/style2.css", cssMimeType, "xx, gzip, zzz", "assets/css/style2.css", "public, max-age=2"},
264+
{2, 2, "/a/b/css/style2.css", cssMimeType, "br, gzip, zzz", "assets/css/style2.css", "public, max-age=2"},
265+
{0, 3, "/js/script2.js", javascriptMimeType, "xx, gzip, zzz", "assets/js/script2.js", "public, max-age=3"},
266+
{0, 3, "/js/script2.js", javascriptMimeType, "br, gzip, zzz", "assets/js/script2.js", "public, max-age=3"},
267+
{2, 4, "/a/b/js/script2.js", javascriptMimeType, "xx, gzip, zzz", "assets/js/script2.js", "public, max-age=4"},
268+
{2, 4, "/a/b/js/script2.js", javascriptMimeType, "br, gzip, zzz", "assets/js/script2.js", "public, max-age=4"},
269+
{0, 5, "/img/sort_asc.png", "image/png", "xx, gzip, zzz", "assets/img/sort_asc.png", "public, max-age=5"},
270+
{0, 5, "/img/sort_asc.png", "image/png", "br, gzip, zzz", "assets/img/sort_asc.png", "public, max-age=5"},
271+
{2, 6, "/a/b/img/sort_asc.png", "image/png", "xx, gzip, zzz", "assets/img/sort_asc.png", "public, max-age=6"},
272+
{2, 6, "/a/b/img/sort_asc.png", "image/png", "br, gzip, zzz", "assets/img/sort_asc.png", "public, max-age=6"},
273273
}
274274

275275
for _, test := range cases {
@@ -401,7 +401,7 @@ func TestServeHTTP304(t *testing.T) {
401401
{"/js/script2.js", "assets/js/script2.js", "xx", &h404{}},
402402
}
403403

404-
// net/http serveFiles handles conditional requests according to RFC723x specs.
404+
// net/http serveFiles handles conditional requests according to RFC9110 specs.
405405
// So we only need to check that a conditional request is correctly wired in.
406406

407407
for i, test := range cases {

doc.go

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,11 @@ Package servefiles provides a static asset handler for serving files such as ima
2525
javascript code. This is an enhancement to the standard net/http ServeFiles, which is used internally.
2626
Care is taken to set headers such that the assets will be efficiently cached by browsers and proxies.
2727
28-
assets := servefiles.NewAssetHandler("./assets/").WithMaxAge(time.Hour)
28+
assets := servefiles.NewAssetHandler("./assets/").WithMaxAge(time.Hour)
2929
3030
Assets is an http.Handler and can be used alongside your other handlers.
3131
32-
33-
Gzipped Content
32+
# Gzipped Content
3433
3534
The Assets handler serves gzipped content when the browser indicates it can accept it. But it does not
3635
gzip anything on-the-fly. Nor does it create any gzipped files for you.
@@ -47,8 +46,7 @@ You should not attempt to gzip already-compressed files, such as PNG, JPEG, SVGZ
4746
Very small files (e.g. less than 1kb) gain little from compression because they may be small enough to fit
4847
within a single TCP packet, so don't bother with them. (They might even grow in size when gzipped.)
4948
50-
51-
Conditional Request Support
49+
# Conditional Request Support
5250
5351
The Assets handler sets 'Etag' headers for the responses of the assets it finds. Modern browsers need this: they
5452
are then able to send conditional requests that very often shrink responses to a simple 304 Not Modified. This
@@ -59,10 +57,9 @@ or weak tags are used for plain or gzipped files respectively (the reason is tha
5957
compressed with different levels of compression, a weak Etag indicates there is not a strict match for the
6058
file's content).
6159
62-
For further information see RFC7232 https://tools.ietf.org/html/rfc7232.
63-
60+
For further information see RFC9110 https://tools.ietf.org/html/rfc9110.
6461
65-
Cache Control
62+
# Cache Control
6663
6764
To go even further, the 'far-future' technique can and should often be used. Set a long expiry time, e.g.
6865
ten years via `time.Hour * 24 * 365 * 10`.
@@ -72,34 +69,32 @@ conditional requests are made. There is clearly a big benefit in page load times
7269
No in-memory caching is performed server-side. This is needed less due to far-future caching being
7370
supported, but might be added in future.
7471
75-
For further information see RFC7234 https://tools.ietf.org/html/rfc7234.
72+
For further information see RFC9111 https://tools.ietf.org/html/rfc9111.
7673
77-
78-
Path Stripping
74+
# Path Stripping
7975
8076
The Assets handler can optionally strip some path segments from the URL before selecting the asset to be served.
8177
8278
This means, for example, that the URL
8379
84-
http://example.com/e3b1cf/css/style1.css
80+
http://example.com/e3b1cf/css/style1.css
8581
8682
can map to the asset files
8783
88-
./assets/css/style1.css
89-
./assets/css/style1.css.gz
84+
./assets/css/style1.css
85+
./assets/css/style1.css.gz
9086
9187
without the /e3b1cf/ segment. The benefit of this is that you can use a unique number or hash in that segment (chosen
9288
for example each time your server starts). Each time that number changes, browsers will see the asset files as
9389
being new, and they will later drop old versions from their cache regardless of their ten-year lifespan.
9490
9591
So you get the far-future lifespan combined with being able to push out changed assets as often as you need to.
9692
97-
98-
Example Usage
93+
# Example Usage
9994
10095
To serve files with a ten-year expiry, this creates a suitably-configured handler:
10196
102-
assets := servefiles.NewAssetHandler("./assets/").StripOff(1).WithMaxAge(10 * 365 * 24 * time.Hour)
97+
assets := servefiles.NewAssetHandler("./assets/").StripOff(1).WithMaxAge(10 * 365 * 24 * time.Hour)
10398
10499
The first parameter names the local directory that holds the asset files. It can be absolute or relative to
105100
the directory in which the server process is started.

echo_adapter/handler_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func ExampleHandlerFunc() {
5555

5656
// how long we allow user agents to cache assets
5757
// (this is in addition to conditional requests, see
58-
// RFC7234 https://tools.ietf.org/html/rfc7234#section-5.2.2.8)
58+
// RFC9111 https://www.rfc-editor.org/rfc/rfc9111#section-5.2.2.1)
5959
maxAge := time.Hour
6060

6161
// define the URL pattern that will be routed to the asset handler

example_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func ExampleNewAssetHandler() {
4040

4141
// how long we allow user agents to cache assets
4242
// (this is in addition to conditional requests, see
43-
// RFC7234 https://tools.ietf.org/html/rfc7234#section-5.2.2.8)
43+
// RFC9111 https://www.rfc-editor.org/rfc/rfc9111#section-5.2.2.1)
4444
maxAge := time.Hour
4545

4646
h := servefiles.NewAssetHandler(localPath).WithMaxAge(maxAge)
@@ -56,7 +56,7 @@ func ExampleNewAssetHandlerFS() {
5656

5757
// how long we allow user agents to cache assets
5858
// (this is in addition to conditional requests, see
59-
// RFC7234 https://tools.ietf.org/html/rfc7234#section-5.2.2.8)
59+
// RFC9111 https://www.rfc-editor.org/rfc/rfc9111#section-5.2.2.1)
6060
maxAge := time.Hour
6161

6262
h := servefiles.NewAssetHandlerFS(fs).WithMaxAge(maxAge)

gin_adapter/handler_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func ExampleHandlerFunc() {
5555

5656
// how long we allow user agents to cache assets
5757
// (this is in addition to conditional requests, see
58-
// RFC7234 https://tools.ietf.org/html/rfc7234#section-5.2.2.8)
58+
// RFC9111 https://www.rfc-editor.org/rfc/rfc9111#section-5.2.2.1)
5959
maxAge := time.Hour
6060

6161
// define the URL pattern that will be routed to the asset handler

0 commit comments

Comments
 (0)