-
Notifications
You must be signed in to change notification settings - Fork 2
/
handler.go
150 lines (132 loc) · 4.23 KB
/
handler.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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// Serve files that never change
//
// If you can put a version string (commitish for instance) in the path of your
// static files, then the content served by the corresponding URLs is guaranteed
// to never change. A whole set of optimisations become possible.
//
// * If the request contains `If-Modified-Since`, return `304` without checking anything
//
// * Set the `Expires` to `<forever>` (`<forever>` defaulting to one year)
//
// * Set the `Cache-Control` header to `public; max-age=<forever>; s-maxage=<forever>`
//
// * Set the `Last-Modified` headers to `<origin>` (`<origin>` being 1970)
//
// This handler is implemented as a wrapper around http.FileServer, and when the
// isDevelopment flag is set, http.FileServer is used directly.
//
// Example:
//
// package main
//
// import(
// "github.com/ant0ine/go-static-forever"
// "net/http"
// )
//
// func main() {
// handler := forever.NewStaticHandler(
// http.Dir("/static/"), // FileSytem to serve
// "1234567", // version string, like a commitish for instance
// nil, // "forever duration", defaults to one year
// false, // isDevelopement
// )
//
// http.ListenAndServe(":8080", handler)
// }
//
package forever
import (
"fmt"
"net/http"
"strings"
"time"
)
type staticHandler struct {
fileHandler http.Handler
versionPrefix string
originHttpDate string
foreverHttpDate string
deltaSeconds int
isDevelopment bool
}
// borrowed from net/http/server.go
const timeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
// foreverDuration defaults to one year.
func NewStaticHandler(
root http.FileSystem,
version string,
foreverDuration *time.Duration,
isDevelopment bool) http.Handler {
// set the default
if foreverDuration == nil {
// "servers SHOULD NOT send Expires dates more than one year in the future."
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21
dur := time.Duration(365 * 86400 * time.Second)
foreverDuration = &dur
}
deltaSeconds := int(foreverDuration.Seconds())
forever := time.Now().Add(*foreverDuration)
foreverHttpDate := forever.Format(timeFormat)
originHttpDate := time.Unix(0, 0).Format(timeFormat)
prefix := "/" + version
return &staticHandler{
fileHandler: http.FileServer(root),
versionPrefix: prefix,
originHttpDate: originHttpDate,
foreverHttpDate: foreverHttpDate,
deltaSeconds: deltaSeconds,
isDevelopment: isDevelopment,
}
}
func (self *staticHandler) ServeHTTP(origWriter http.ResponseWriter, origRequest *http.Request) {
if !strings.HasPrefix(origRequest.URL.Path, self.versionPrefix) {
http.NotFound(origWriter, origRequest)
return
}
origRequest.URL.Path = origRequest.URL.Path[len(self.versionPrefix):]
if self.isDevelopment {
self.fileHandler.ServeHTTP(origWriter, origRequest)
return
}
// If the request contains If-Modified-Since, return 304 without checking anything
if origRequest.Header.Get("If-Modified-Since") != "" {
http.Error(origWriter, "Not Modified", http.StatusNotModified)
return
}
// Provide writer wrapper to write the custom headers only when the response code is 200
writer := &responseWriter{
origWriter,
self,
false,
}
self.fileHandler.ServeHTTP(writer, origRequest)
}
// Inherit from an object implementing the http.ResponseWriter interface
type responseWriter struct {
http.ResponseWriter
handler *staticHandler
wroteHeader bool
}
// Overloading of the http.ResponseWriter method.
func (self *responseWriter) WriteHeader(code int) {
if code == 200 {
// Cache forever
self.Header().Set("Expires", self.handler.foreverHttpDate)
self.Header().Set("Last-Modified", self.handler.originHttpDate)
self.Header().Set("Cache-Control", fmt.Sprintf(
"public; max-age=%d; s-maxage=%d",
self.handler.deltaSeconds,
self.handler.deltaSeconds,
))
}
self.ResponseWriter.WriteHeader(code)
self.wroteHeader = true
}
// Overloading of the http.ResponseWriter method.
func (self *responseWriter) Write(b []byte) (int, error) {
if !self.wroteHeader {
self.WriteHeader(http.StatusOK)
}
return self.ResponseWriter.Write(b)
}