-
Notifications
You must be signed in to change notification settings - Fork 0
/
http.go
324 lines (287 loc) · 8.67 KB
/
http.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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
// Package http_util provides useful routines for writing web apps.
package http_util
import (
"bytes"
"fmt"
"html/template"
"io"
"log"
"mime/multipart"
"net/http"
"net/url"
"os"
"strconv"
"time"
)
var (
kLog *log.Logger
kAppStart time.Time
)
// Redirect sends a 302 redirect
func Redirect(w http.ResponseWriter, r *http.Request, redirectUrl string) {
http.Redirect(w, r, redirectUrl, 302)
}
// HasParam returns true if values contains a particular parameter.
func HasParam(values url.Values, param string) bool {
_, ok := values[param]
return ok
}
// WithParams returns a URL with new parameters. If the parameters
// already exist in the original URL, they are replaced.
// u is the original URL;
// nameValues is parameter name, parameter value, parameter name, parameter
// value, etc. nameValues must have even length.
func WithParams(u *url.URL, nameValues ...string) *url.URL {
length := len(nameValues)
if length%2 != 0 {
panic("nameValues must have even length.")
}
result := *u
values := result.Query()
for i := 0; i < length; i += 2 {
values.Set(nameValues[i], nameValues[i+1])
}
result.RawQuery = values.Encode()
return &result
}
// NewUrl returns a new URL with a given path and parameters.
// nameValues is parameter name, parameter value, parameter name, parameter
// value, etc. nameValues must have even length.
func NewUrl(path string, nameValues ...string) *url.URL {
length := len(nameValues)
if length%2 != 0 {
panic("nameValues must have even length.")
}
values := make(url.Values)
for i := 0; i < length; i += 2 {
values.Add(nameValues[i], nameValues[i+1])
}
return &url.URL{
Path: path,
RawQuery: values.Encode()}
}
// AppendParams returns a URL with new parameters appended. No existing
// parameter is replaced. u is the original URL; nameValues is
// parameter name, parameter value, parameter name, parameter
// value, etc. nameValues must have even length.
func AppendParams(u *url.URL, nameValues ...string) *url.URL {
length := len(nameValues)
if length%2 != 0 {
panic("nameValues must have even length.")
}
result := *u
values := result.Query()
for i := 0; i < length; i += 2 {
values.Add(nameValues[i], nameValues[i+1])
}
result.RawQuery = values.Encode()
return &result
}
// PageBreadCrumb is used for displaying the page breadcrumb
type PageBreadCrumb struct {
// the currennt URL
URL *url.URL
// The page number URL parameter name
PageNoParam string
// The zero based page number
PageNo int
// Whether or not we are at last page.
End bool
}
// DisplayPageNo returns the 1-based page number.
func (p *PageBreadCrumb) DisplayPageNo() int {
return p.PageNo + 1
}
// NextPageLink returns the URL for the next page.
func (p *PageBreadCrumb) NextPageLink() *url.URL {
return WithParams(p.URL, p.PageNoParam, strconv.Itoa(p.PageNo+1))
}
// PrevPageLink returns the URL for the previous page.
func (p *PageBreadCrumb) PrevPageLink() *url.URL {
return WithParams(p.URL, p.PageNoParam, strconv.Itoa(p.PageNo-1))
}
// WriteTemplate writes a template. v is the values for the template.
func WriteTemplate(w io.Writer, t *template.Template, v interface{}) {
if err := t.Execute(w, v); err != nil {
fmt.Fprintln(w, "Error in template.")
kLog.Printf("Error in template: %v\n", err)
}
}
// Repoort error reports an error. message is what user sees.
func ReportError(w http.ResponseWriter, message string, err error) {
http.Error(w, message, http.StatusInternalServerError)
kLog.Printf("%s: %v\n", message, err)
}
// Selection represents a single selection from a drop down
type Selection struct {
// Value is the value of the selection
Value string
// Name is what is displayed for the selection
Name string
}
// SelectModel converts a parameter value to a selection.
type SelectModel interface {
// ToSelection converts a parameter value to a selection. ToSelection may
// return nil if value does not map to a valid selection.
ToSelection(s string) *Selection
}
// Choice represents a choice in a combo box.
type Choice struct {
// What the user sees in the choice dialog
Name string
// The parameter value attached to this choice
Value interface{}
}
// ComboBox represents an immutable combo box of items.
// ComboBox implements SelectModel.
type ComboBox []Choice
func (c ComboBox) ToSelection(s string) *Selection {
if idx, ok := c.toIdx(s); ok {
return &Selection{Name: c[idx].Name, Value: s}
}
return nil
}
// ToValue returns the value associated with the selected choice or nil
// if none selected. s is the value from the form.
func (c ComboBox) ToValue(s string) interface{} {
if idx, ok := c.toIdx(s); ok {
return c[idx].Value
}
return nil
}
// Items returns all the items in this combo box.
func (c ComboBox) Items() []Selection {
result := make([]Selection, len(c))
for i := range c {
result[i] = Selection{Name: c[i].Name, Value: strconv.Itoa(i + 1)}
}
return result
}
func (c ComboBox) toIdx(s string) (int, bool) {
oneIdx, _ := strconv.Atoi(s)
idx := oneIdx - 1
if idx < 0 || idx >= len(c) {
return 0, false
}
return idx, true
}
// Selections implements SelectModel
type Selections []Selection
func (s Selections) ToSelection(str string) *Selection {
for _, sel := range s {
if str == sel.Value {
return &sel
}
}
return nil
}
// Values is a wrapper around url.Values providing additional methods.
type Values struct {
url.Values
}
// GetSelection gets the current selection. name is the request parameter
// name. GetSelection may return nil if there is no valid selection.
func (v Values) GetSelection(
model SelectModel, name string) *Selection {
return model.ToSelection(v.Get(name))
}
// Equals returns true if the request parameter 'paramName' is equal to
// 'value'
func (v Values) Equals(paramName, value string) bool {
return v.Get(paramName) == value
}
// Mux is the interface that wraps the Handle method.
type Mux interface {
Handle(pattern string, handler http.Handler)
}
// AddStatic adds static content to mux.
// path is the path to the file; content is the file content.
func AddStatic(mux Mux, path, content string) {
AddStaticBinary(mux, path, []byte(content))
}
// AddStaticBinary adds static content to mux.
// path is the path to the file; content is the file content.
func AddStaticBinary(mux Mux, path string, content []byte) {
mux.Handle(
path,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.ServeContent(w, r, path, kAppStart, bytes.NewReader(content))
}))
}
// AddStaticFromFile adds static content to mux. path is the
// path to the file; localPath is the actual path of the file on the local
// filesystem.
func AddStaticFromFile(mux Mux, path, localPath string) error {
file, err := os.Open(localPath)
if err != nil {
return err
}
defer file.Close()
buffer := bytes.Buffer{}
buffer.ReadFrom(file)
AddStaticBinary(mux, path, buffer.Bytes())
return nil
}
// Error sends the status code along with its corresponding message
func Error(w http.ResponseWriter, status int) {
http.Error(w, fmt.Sprintf("%d %s", status, http.StatusText(status)), status)
}
// MultipartFile represents a file in a multipart form.
type MultipartFile struct {
FileName string
Contents []byte
}
// MultipartForm represents a multipart form
type MultipartForm struct {
filesByKey map[string]MultipartFile
}
// NewMultipartForm creates a new MultipartForm. reader comes from calling
// MultipartReader() on the http.Request valaue. maxSizes limits the sizes
// of files. The keys are the http request keys; the values are the maximum
// number of bytes to read. No value for a request key means no size limit
// for that key.
func NewMultipartForm(
reader *multipart.Reader, maxSizes map[string]int) (*MultipartForm, error) {
filesByKey := make(map[string]MultipartFile)
for part, err := reader.NextPart(); err != io.EOF; part, err = reader.NextPart() {
if err != nil {
return nil, err
}
formName := part.FormName()
var buffer bytes.Buffer
var reader io.Reader
maxSize, ok := maxSizes[formName]
if !ok {
reader = part
} else {
reader = &io.LimitedReader{R: part, N: int64(maxSize)}
}
_, err = buffer.ReadFrom(reader)
if err != nil {
return nil, err
}
filesByKey[formName] = MultipartFile{
FileName: part.FileName(),
Contents: buffer.Bytes(),
}
err = part.Close()
if err != nil {
return nil, err
}
}
return &MultipartForm{filesByKey: filesByKey}, nil
}
// Get gets the value for the request key.
func (m *MultipartForm) Get(key string) string {
file := m.filesByKey[key]
return string(file.Contents)
}
// GetFile gets the file for the request key.
func (m *MultipartForm) GetFile(key string) (file MultipartFile, ok bool) {
file, ok = m.filesByKey[key]
return
}
func init() {
kLog = log.New(os.Stderr, "", log.LstdFlags|log.Lmicroseconds)
kAppStart = time.Now()
}