aboutsummaryrefslogtreecommitdiff
path: root/httputil/buster.go
diff options
context:
space:
mode:
Diffstat (limited to 'httputil/buster.go')
-rw-r--r--httputil/buster.go95
1 files changed, 95 insertions, 0 deletions
diff --git a/httputil/buster.go b/httputil/buster.go
new file mode 100644
index 0000000..beab151
--- /dev/null
+++ b/httputil/buster.go
@@ -0,0 +1,95 @@
1// Copyright 2013 The Go Authors. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file or at
5// https://developers.google.com/open-source/licenses/bsd.
6
7package httputil
8
9import (
10 "io"
11 "io/ioutil"
12 "net/http"
13 "net/url"
14 "strings"
15 "sync"
16)
17
18type busterWriter struct {
19 headerMap http.Header
20 status int
21 io.Writer
22}
23
24func (bw *busterWriter) Header() http.Header {
25 return bw.headerMap
26}
27
28func (bw *busterWriter) WriteHeader(status int) {
29 bw.status = status
30}
31
32// CacheBusters maintains a cache of cache busting tokens for static resources served by Handler.
33type CacheBusters struct {
34 Handler http.Handler
35
36 mu sync.Mutex
37 tokens map[string]string
38}
39
40func sanitizeTokenRune(r rune) rune {
41 if r <= ' ' || r >= 127 {
42 return -1
43 }
44 // Convert percent encoding reserved characters to '-'.
45 if strings.ContainsRune("!#$&'()*+,/:;=?@[]", r) {
46 return '-'
47 }
48 return r
49}
50
51// Get returns the cache busting token for path. If the token is not already
52// cached, Get issues a HEAD request on handler and uses the response ETag and
53// Last-Modified headers to compute a token.
54func (cb *CacheBusters) Get(path string) string {
55 cb.mu.Lock()
56 if cb.tokens == nil {
57 cb.tokens = make(map[string]string)
58 }
59 token, ok := cb.tokens[path]
60 cb.mu.Unlock()
61 if ok {
62 return token
63 }
64
65 w := busterWriter{
66 Writer: ioutil.Discard,
67 headerMap: make(http.Header),
68 }
69 r := &http.Request{URL: &url.URL{Path: path}, Method: "HEAD"}
70 cb.Handler.ServeHTTP(&w, r)
71
72 if w.status == 200 {
73 token = w.headerMap.Get("Etag")
74 if token == "" {
75 token = w.headerMap.Get("Last-Modified")
76 }
77 token = strings.Trim(token, `" `)
78 token = strings.Map(sanitizeTokenRune, token)
79 }
80
81 cb.mu.Lock()
82 cb.tokens[path] = token
83 cb.mu.Unlock()
84
85 return token
86}
87
88// AppendQueryParam appends the token as a query parameter to path.
89func (cb *CacheBusters) AppendQueryParam(path string, name string) string {
90 token := cb.Get(path)
91 if token == "" {
92 return path
93 }
94 return path + "?" + name + "=" + token
95}