forked from matryer/mix
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mix.go
126 lines (103 loc) · 2.5 KB
/
mix.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
package mix
import (
"bytes"
"io"
"os"
"path"
"path/filepath"
"time"
"net/http"
)
type h struct {
files []string
err error
}
// ServeFiles serves all specified files.
// Content-Type (if not set) will be inferred from the extension in the
// request.
// Uses http.ServeContent to serve the content.
func ServeFiles(w http.ResponseWriter, r *http.Request, files ...string) {
var recentMod time.Time
var buf bytes.Buffer
for _, f := range files {
stat, err := os.Stat(f)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// keep track of latest modtime
if stat.ModTime().After(recentMod) {
recentMod = stat.ModTime()
}
file, err := os.Open(f)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, err = io.Copy(&buf, file)
file.Close()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// write linefeed
buf.WriteRune('\n')
}
http.ServeContent(w, r, path.Base(r.URL.Path), recentMod, sizable(buf))
}
func (h *h) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// return error if something went wrong
if h.err != nil {
http.Error(w, h.err.Error(), http.StatusInternalServerError)
return
}
ServeFiles(w, r, h.files...)
}
// Glob takes a range of patterns and produces a unique list
// of matching files.
// Files are added in pattern and alphabetical order.
// Like filepath.Glob, but you can pass in many patterns.
func Glob(patterns ...string) ([]string, error) {
seen := make(map[string]struct{})
var files []string
for _, g := range patterns {
matches, err := filepath.Glob(g)
if err != nil {
return nil, err
}
for _, match := range matches {
match = filepath.Clean(match)
if _, alreadySeen := seen[match]; !alreadySeen {
files = append(files, match)
seen[match] = struct{}{}
}
}
}
return files, nil
}
// Handler makes a new mix handler with the specified files or
// patterns.
func Handler(patterns ...string) http.Handler {
files, err := Glob(patterns...)
h := &h{
files: files,
err: err,
}
return h
}
type sizableBuffer struct {
buf bytes.Buffer
}
var _ io.ReadSeeker = (*sizableBuffer)(nil)
func (s *sizableBuffer) Seek(offset int64, whence int) (int64, error) {
if whence == os.SEEK_END {
return int64(s.buf.Len()), nil
}
return 0, nil
}
func (s *sizableBuffer) Read(p []byte) (int, error) {
return s.buf.Read(p)
}
func sizable(buf bytes.Buffer) *sizableBuffer {
return &sizableBuffer{buf: buf}
}