-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathstatic.go
134 lines (115 loc) · 3.2 KB
/
static.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
package gojistatic
import (
"log"
"net/http"
"path"
"strings"
"time"
)
// StaticOptions is a struct for specifiying configuration options for the goji-static middleware.
type StaticOptions struct {
// Prefix is the optional prefix used to serve the static directory content
Prefix string
// Indexfile is teh name of the index file that should be served if it exists
IndexFile string
// SkipLogging will disable [Static] log messages when a static file is served
SkipLogging bool
// Disable browser caching for files
DisableCaching bool
// Expires defines which user-defined function to use for producing a HTTP Expires Header
// https://developers.google.com/speed/docs/insights/LeverageBrowserCaching
Expires func() string
}
// prepareOptions prepares the configuration options and sets some default options.
func prepareOptions(options []StaticOptions) StaticOptions {
var opt StaticOptions
if len(options) > 0 {
opt = options[0]
}
if len(opt.IndexFile) == 0 {
opt.IndexFile = "index.html"
}
if len(opt.Prefix) != 0 {
// ensure we have a leading "/"
if opt.Prefix[0] != '/' {
opt.Prefix = "/" + opt.Prefix
}
// remove all trailing "/"
opt.Prefix = strings.TrimRight(opt.Prefix, "/")
}
return opt
}
func Static(directory string, options ...StaticOptions) func(http.Handler) http.Handler {
dir := http.Dir(directory)
opt := prepareOptions(options)
return func(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, req *http.Request) {
if req.Method != "GET" && req.Method != "HEAD" {
h.ServeHTTP(w, req)
return
}
// Get the file name from the path
file := req.URL.Path
// if we have a prefix, filter requests by stripping the prefix
if opt.Prefix != "" {
if !strings.HasPrefix(file, opt.Prefix) {
h.ServeHTTP(w, req)
return
}
file = file[len(opt.Prefix):]
if file != "" && file[0] != '/' {
h.ServeHTTP(w, req)
return
}
}
// Open the file and get the stats
f, err := dir.Open(file)
if err != nil {
h.ServeHTTP(w, req)
return
}
defer f.Close()
fs, err := f.Stat()
if err != nil {
h.ServeHTTP(w, req)
return
}
// if the requested resource is a directory, try to serve the index file
if fs.IsDir() {
// redirect if trailling "/"" is missing
if !strings.HasSuffix(req.URL.Path, "/") {
http.Redirect(w, req, req.URL.Path+"/", http.StatusFound)
return
}
file = path.Join(file, opt.IndexFile)
f, err = dir.Open(file)
if err != nil {
h.ServeHTTP(w, req)
return
}
defer f.Close()
fs, err = f.Stat()
if err != nil || fs.IsDir() {
h.ServeHTTP(w, req)
return
}
}
if !opt.SkipLogging {
log.Println("[Static] Serving " + file)
}
if opt.DisableCaching {
now := time.Now().UTC()
expires := now.Add(-time.Second)
w.Header().Set("Date", now.Format(http.TimeFormat))
w.Header().Set("Expires", expires.Format(http.TimeFormat))
w.Header().Set("Cache-Control", "no-cache")
}
// Add an Expires header to the static content
if opt.Expires != nil {
w.Header().Set("Expires", opt.Expires())
}
http.ServeContent(w, req, file, fs.ModTime(), f)
}
return http.HandlerFunc(fn)
}
}