-
Notifications
You must be signed in to change notification settings - Fork 3
/
helpers.go
198 lines (163 loc) · 4.33 KB
/
helpers.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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package fire
import (
"fmt"
"io"
"net/http"
"runtime/debug"
"strconv"
"strings"
)
// Map is a general purpose type to represent a map.
type Map map[string]interface{}
type safeError struct {
err error
}
// E is a short-hand function to construct a safe error.
func E(format string, a ...interface{}) error {
return Safe(fmt.Errorf(format, a...))
}
// Safe wraps an error and marks it as safe. Wrapped errors are safe to be
// presented to the client if appropriate.
func Safe(err error) error {
return &safeError{
err: err,
}
}
func (err *safeError) Error() string {
return err.err.Error()
}
// IsSafe can be used to check if an error has been wrapped using Safe.
func IsSafe(err error) bool {
_, ok := err.(*safeError)
return ok
}
// Compose is a short-hand for chaining the specified middleware and handler
// together.
func Compose(chain ...interface{}) http.Handler {
// check length
if len(chain) < 2 {
panic("fire: expected chain to have at least two items")
}
// get handler
h, ok := chain[len(chain)-1].(http.Handler)
if !ok {
panic(`fire: expected last chain item to be a "http.Handler"`)
}
// chain all middleware
for i := len(chain) - 2; i >= 0; i-- {
// get middleware
m, ok := chain[i].(func(http.Handler) http.Handler)
if !ok {
panic(`fire: expected intermediary chain item to be a "func(http.handler) http.Handler"`)
}
// chain
h = m(h)
}
return h
}
// DataSize parses human readable data sizes (e.g. 4K, 20M or 5G) and returns
// the amount of bytes they represent.
func DataSize(str string) uint64 {
const msg = "fire: data size must be like 4K, 20M or 5G"
// check length
if len(str) < 2 {
panic(msg)
}
// get symbol
sym := string(str[len(str)-1])
// parse number
num, err := strconv.ParseUint(str[:len(str)-1], 10, 64)
if err != nil {
panic(msg)
}
// calculate size
switch sym {
case "K":
return num * 1000
case "M":
return num * 1000 * 1000
case "G":
return num * 1000 * 1000 * 1000
}
panic(msg)
}
// Contains returns true if a list of strings contains another string.
func Contains(list []string, str string) bool {
for _, item := range list {
if item == str {
return true
}
}
return false
}
// Includes returns true if a list of strings includes another list of strings.
func Includes(all, subset []string) bool {
for _, item := range subset {
if !Contains(all, item) {
return false
}
}
return true
}
// Intersect will return the intersection of both lists.
func Intersect(listA, listB []string) []string {
// prepare new list
list := make([]string, 0, len(listA))
// add items that are part of both lists
for _, item := range listA {
if Contains(listB, item) {
list = append(list, item)
}
}
return list
}
type bodyLimiter struct {
io.ReadCloser
Original io.ReadCloser
}
// LimitBody will limit reading from the body of the supplied request to the
// specified amount of bytes. Earlier calls to LimitBody will be overwritten
// which essentially allows callers to increase the limit from a default limit.
func LimitBody(w http.ResponseWriter, r *http.Request, n int64) {
// get original body from existing limiter
if bl, ok := r.Body.(*bodyLimiter); ok {
r.Body = bl.Original
}
// set new limiter
r.Body = &bodyLimiter{
Original: r.Body,
ReadCloser: http.MaxBytesReader(w, r.Body, n),
}
}
// AssetServer constructs an asset server handler that serves an asset
// directory on a specified path and serves the index file for not found paths
// which is needed to run single page applications like Ember.
func AssetServer(prefix, directory string) http.Handler {
// ensure prefix
prefix = "/" + strings.Trim(prefix, "/")
// create dir server
dir := http.Dir(directory)
// create file server
fs := http.FileServer(dir)
h := func(w http.ResponseWriter, r *http.Request) {
// pre-check if file does exist
f, err := dir.Open(r.URL.Path)
if err != nil {
r.URL.Path = "/"
} else if f != nil {
_ = f.Close()
}
// serve file
fs.ServeHTTP(w, r)
}
return http.StripPrefix(prefix, http.HandlerFunc(h))
}
// ErrorReporter returns a very basic reporter that writes errors and stack
// traces to the specified writer.
func ErrorReporter(out io.Writer) func(error) {
return func(err error) {
_, _ = fmt.Fprintf(out, "===> Begin Error: %s\n", err.Error())
_, _ = out.Write(debug.Stack())
_, _ = fmt.Fprintln(out, "<=== End Error")
}
}