-
-
Notifications
You must be signed in to change notification settings - Fork 195
/
middleware_tile_cache.go
124 lines (100 loc) · 3.05 KB
/
middleware_tile_cache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package server
import (
"bytes"
"fmt"
"io"
"net/http"
"path"
"strings"
"github.com/go-spatial/tegola/atlas"
"github.com/go-spatial/tegola/cache"
"github.com/go-spatial/tegola/internal/log"
"github.com/go-spatial/tegola/mvt"
)
// TileCacheHandler implements a request cache for tiles on requests when the URLs
// have a /:z/:x/:y scheme suffix (i.e. /osm/1/3/4.pbf)
func TileCacheHandler(a *atlas.Atlas, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error
// check if a cache backend exists
cacher := a.GetCache()
if cacher == nil {
// nope. move on
next.ServeHTTP(w, r)
return
}
// parse our URI into a cache key structure (remove any configured URIPrefix + "maps/" )
key, err := cache.ParseKey(strings.TrimPrefix(r.URL.Path, path.Join(URIPrefix, "maps")))
if err != nil {
log.Errorf("cache middleware: ParseKey err: %v", err)
next.ServeHTTP(w, r)
return
}
// use the URL path as the key
cachedTile, hit, err := cacher.Get(key)
if err != nil {
log.Errorf("cache middleware: error reading from cache: %v", err)
next.ServeHTTP(w, r)
return
}
// cache miss
if !hit {
// buffer which will hold a copy of the response for writing to the cache
var buff bytes.Buffer
// ovewrite our current responseWriter with a tileCacheResponseWriter
w = newTileCacheResponseWriter(w, &buff)
next.ServeHTTP(w, r)
// check if our request context has been canceled
if r.Context().Err() != nil {
return
}
// if nothing has been written to the buffer, don't write to the cache
if buff.Len() == 0 {
return
}
if err := cacher.Set(key, buff.Bytes()); err != nil {
log.Warnf("cache response writer err: %v", err)
}
return
}
// mimetype for mapbox vector tiles
w.Header().Add("Content-Type", mvt.MimeType)
// communicate the cache is being used
w.Header().Add("Tegola-Cache", "HIT")
w.Header().Add("Content-Length", fmt.Sprintf("%d", len(cachedTile)))
w.Write(cachedTile)
return
})
}
func newTileCacheResponseWriter(resp http.ResponseWriter, w io.Writer) http.ResponseWriter {
return &tileCacheResponseWriter{
resp: resp,
multi: io.MultiWriter(w, resp),
}
}
// tileCacheResponsWriter wraps http.ResponseWriter (https://golang.org/pkg/net/http/#ResponseWriter)
// to additionally write the response to a cache when there is a cache MISS
type tileCacheResponseWriter struct {
// status response code
status int
resp http.ResponseWriter
multi io.Writer
}
func (w *tileCacheResponseWriter) Header() http.Header {
// communicate the cache is being used
w.resp.Header().Set("Tegola-Cache", "MISS")
return w.resp.Header()
}
func (w *tileCacheResponseWriter) Write(b []byte) (int, error) {
// only write to the multi writer when http response == StatusOK
if w.status == http.StatusOK {
// write to our multi writer
return w.multi.Write(b)
}
// write to the original response writer
return w.resp.Write(b)
}
func (w *tileCacheResponseWriter) WriteHeader(i int) {
w.status = i
w.resp.WriteHeader(i)
}