-
Notifications
You must be signed in to change notification settings - Fork 1
/
buster.go
129 lines (110 loc) · 2.89 KB
/
buster.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
package buster
import (
"math/rand"
"net/http"
"os"
"path"
"strings"
"time"
"github.com/gin-gonic/gin"
)
var (
noCacheHeader = "no-store"
cacheHeader = "max-age=31536000"
)
func NewFileServer(rootDir, prefix string) *FileServer {
return &FileServer{
RootDir: rootDir,
Prefix: prefix,
Buster: Buster,
BuildBusterFunc: DefaultBuildBuster,
StripBusterFunc: DefaultStripBuster,
BuildURLFunc: DefaultBuildURL,
}
}
type FileServer struct {
RootDir string // File system directory that will be served
Prefix string // URL prefix
Buster string // Cache buster that will be added to the Prefix
Host string // include for absolute urls (example http://example.com)
DisableCache bool // don't cache
prefix string
BuildBusterFunc func(s *FileServer) string // func used to build the path prefix
StripBusterFunc func(path string, s *FileServer) (string, bool) // func used to strip the prefix
BuildURLFunc func(path string, s *FileServer) string // func use to create URLs with the cache buster
}
// BuildURL
func (fs *FileServer) BuildURL(url string) string {
return fs.BuildURLFunc(url, fs)
}
// Buster is used as the cache buster slug
var Buster = random(10)
func DefaultBuildBuster(s *FileServer) string {
return s.Prefix + s.Buster + "/"
}
func DefaultStripBuster(p string, s *FileServer) (string, bool) {
l := len(s.prefix)
if len(p) < l {
return "", false
}
return path.Join(s.RootDir, p[l:]), true
}
func DefaultBuildURL(path string, s *FileServer) string {
keys := strings.Split(path, "/")
if len(keys) == 0 || len(keys) == 1 {
return s.Buster
}
if len(keys) <= 2 {
return path + "/" + s.Buster
}
keys = append(keys, "")
copy(keys[3:], keys[2:])
keys[2] = s.Buster
if len(s.Host) > 0 {
return s.Host + strings.Join(keys, "/")
}
return strings.Join(keys, "/")
}
// GinFunc returns a gin middleware func
func (s *FileServer) GinFunc() gin.HandlerFunc {
s.prefix = s.BuildBusterFunc(s)
return func(ctx *gin.Context) {
if !strings.HasPrefix(ctx.Request.URL.Path, s.Prefix) {
ctx.Next()
return
}
filePath, ok := s.StripBusterFunc(ctx.Request.URL.Path, s)
if !ok {
ctx.Next()
return
}
stats, err := os.Stat(filePath)
if err != nil {
ctx.Next()
return
}
if stats.IsDir() {
ctx.Next()
return
}
if s.DisableCache {
ctx.Writer.Header().Set("Cache-Control", noCacheHeader)
} else {
ctx.Writer.Header().Set("Cache-Control", cacheHeader)
}
http.ServeFile(ctx.Writer, ctx.Request, filePath)
ctx.Abort()
}
}
// vars used to generate a random string
var (
letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
)
func random(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rnd.Intn(len(letterRunes))]
}
return string(b)
}