-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathserver.go
527 lines (466 loc) · 16.4 KB
/
server.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
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
/*
## go-rest A small and evil REST framework for Go
### Reflection, Go structs, and JSON marshalling FTW!
* go get github.com/ungerik/go-rest
* import "github.com/ungerik/go-rest"
* Documentation: http://go.pkgdoc.org/github.com/ungerik/go-rest
* License: Public Domain
Download, build and run example:
go get github.com/ungerik/go-rest
go install github.com/ungerik/go-rest/example && example
Small?
Yes, the framework consists of only three functions:
HandleGET, HandlePOST, RunServer.
Evil?
Well, this package can be considered bad design because
HandleGET and HandlePOST use dynamic typing to hide 36 combinations
of handler function types to make the interface _easy_ to use.
36 static functions would have been more lines of code but
dramatic _simpler_ in their individual implementations.
So simple in fact, that there wouldn't be a point in
abstracting them away in an extra framework.
See this great talk about easy vs. simple:
http://www.infoq.com/presentations/Simple-Made-Easy
Rob Pike may also dislike this approach:
https://groups.google.com/d/msg/golang-nuts/z4T_n4MHbXM/jT9PoYc6I1IJ
So yes, this package can be called evil because it is an
anti-pattern to all that is good and right about Go.
Why use it then? By maximizing dynamic code
it is easy to use and reduces code.
Yes, that introduces some internal complexity,
but this complexity is still very low in absolute terms
and thus easy to control and debug.
The complexity of the dynamic code also does not spill over
into the package users' code, because the arguments and
results of the handler functions must be static typed
and can't be interface{}.
Now let's have some fun:
HandleGET uses a handler function that returns a struct or string
to create the GET response. Structs will be marshalled as JSON,
strings will be used as body with auto-detected content type.
Format of GET handler:
func([url.Values]) ([struct|*struct|string][, error]) {}
Example:
type MyStruct struct {
A in
B string
}
rest.HandleGET("/data.json", func() *MyStruct {
return &MyStruct{A: 1, B: "Hello World"}
})
rest.HandleGET("/index.html", func() string {
return "<!doctype html><p>Hello World"
})
The GET handler function can optionally accept an url.Values argument
and return an error as second result value that will be displayed as
500 internal server error if not nil.
Example:
rest.HandleGET("/data.json", func(params url.Values) (string, error) {
v := params.Get("value")
if v == "" {
return nil, errors.New("Expecting GET parameter 'value'")
}
return "value = " + v, nil
})
HandlePOST maps POST form data or a JSON document to a struct that is passed
to the handler function. An error result from handler will be displayed
as 500 internal server error message. An optional first string result
will be displayed as a 200 response body with auto-detected content type.
Suported content types for POST requests are:
* application/x-www-form-urlencoded
* multipart/form-data
* text/plain
* application/json
* application/xml
Format of POST handler:
func([*struct|url.Values]) ([struct|*struct|string],[error]) {}
Example:
rest.HandlePOST("/change-data", func(data *MyStruct) (err error) {
// save data
return err
})
Both HandleGET and HandlePOST also accept one optional object argument.
In that case handler is interpreted as a method of the type of object
and called accordingly.
Example:
rest.HandleGET("/method-call", (*myType).MethodName, myTypeObject)
*/
package rest
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"os"
"reflect"
"strconv"
"strings"
)
var (
// IndentJSON is the string with which JSON output will be indented.
IndentJSON string
// Log is a function pointer compatible to fmt.Println or log.Println.
// The default value is log.Println.
Log = log.Println
// DontCheckRequestMethod disables checking for the correct
// request method for a handler, which would result in a
// 405 error if not correct.
// Handy for testing POST handler via hand crafted GET requests.
DontCheckRequestMethod bool
)
/*
HandleGET registers a HTTP GET handler for path.
handler is a function with an optional url.Values argument.
If the first result value of handler is a struct or struct pointer,
then the struct will be marshalled as JSON response.
If the first result value fo handler is a string,
then it will be used as response body with an auto-detected content type.
An optional second result value of type error will
create a 500 internal server error response if not nil.
All non error responses will use status code 200.
A single optional argument can be passed as object.
In that case handler is interpreted as a method and
object is the address of an object with such a method.
Format of GET handler:
func([url.Values]) ([struct|*struct|string][, error]) {}
*/
func HandleGET(path string, handler interface{}, object ...interface{}) {
handlerFunc, in, out := getHandlerFunc(handler, object)
httpHandler := &httpHandler{
method: "GET",
handlerFunc: handlerFunc,
}
// Check handler arguments and install getter
switch len(in) {
case 0:
httpHandler.getArgs = func(request *http.Request) []reflect.Value {
return nil
}
case 1:
if in[0] != reflect.TypeOf(url.Values(nil)) {
panic(fmt.Errorf("HandleGET(): handler argument must be url.Values, got %s", in[0]))
}
httpHandler.getArgs = func(request *http.Request) []reflect.Value {
return []reflect.Value{reflect.ValueOf(request.URL.Query())}
}
default:
panic(fmt.Errorf("HandleGET(): handler accepts zero or one arguments, got %d", len(in)))
}
httpHandler.writeResult = writeResultFunc(out)
http.Handle(path, httpHandler)
}
/*
HandlePOST registers a HTTP POST handler for path.
handler is a function that takes a struct pointer or url.Values
as argument.
If the request content type is text/plain, then only a struct pointer
is allowed as handler argument and the request body will be interpreted
as JSON and unmarshalled to a new struct instance.
If the request content type multipart/form-data, then only a struct pointer
is allowed as handler argument and a file named JSON
will be unmarshalled to a new struct instance.
If the request content type is empty or application/x-www-form-urlencoded
and the handler argument is of type url.Values, then the form
values will be passed directly as url.Values.
If the handler argument is a struct pointer and the form contains
a single value named "JSON", then the value will be interpreted as
JSON and unmarshalled to a new struct instance.
If there are multiple form values, then they will be set at
struct fields with exact matching names.
If the first result value of handler is a struct or struct pointer,
then the struct will be marshalled as JSON response.
If the first result value fo handler is a string,
then it will be used as response body with an auto-detected content type.
An optional second result value of type error will
create a 500 internal server error response if not nil.
All non error responses will use status code 200.
A single optional argument can be passed as object.
In that case handler is interpreted as a method and
object is the address of an object with such a method.
Format of POST handler:
func([*struct|url.Values]) ([struct|*struct|string][, error]) {}
*/
func HandlePOST(path string, handler interface{}, object ...interface{}) {
handlerFunc, in, out := getHandlerFunc(handler, object)
httpHandler := &httpHandler{
method: "POST",
handlerFunc: handlerFunc,
}
// Check handler arguments and install getter
switch len(in) {
case 1:
a := in[0]
if a != urlValuesType && (a.Kind() != reflect.Ptr || a.Elem().Kind() != reflect.Struct) && a.Kind() != reflect.String {
panic(fmt.Errorf("HandlePOST(): first handler argument must be a struct pointer, string, or url.Values. Got %s", a))
}
httpHandler.getArgs = func(request *http.Request) []reflect.Value {
ct := request.Header.Get("Content-Type")
switch ct {
case "", "application/x-www-form-urlencoded":
request.ParseForm()
if a == urlValuesType {
return []reflect.Value{reflect.ValueOf(request.Form)}
}
s := reflect.New(a.Elem())
if len(request.Form) == 1 && request.Form.Get("JSON") != "" {
err := json.Unmarshal([]byte(request.Form.Get("JSON")), s.Interface())
if err != nil {
panic(err)
}
} else {
v := s.Elem()
for key, value := range request.Form {
if f := v.FieldByName(key); f.IsValid() && f.CanSet() {
switch f.Kind() {
case reflect.String:
f.SetString(value[0])
case reflect.Bool:
if val, err := strconv.ParseBool(value[0]); err == nil {
f.SetBool(val)
}
case reflect.Float32, reflect.Float64:
if val, err := strconv.ParseFloat(value[0], 64); err == nil {
f.SetFloat(val)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if val, err := strconv.ParseInt(value[0], 0, 64); err == nil {
f.SetInt(val)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if val, err := strconv.ParseUint(value[0], 0, 64); err == nil {
f.SetUint(val)
}
}
}
}
}
return []reflect.Value{s}
case "text/plain":
if a.Kind() != reflect.String {
panic(fmt.Errorf("HandlePOST(): first handler argument must be a string when request Content-Type is text/plain, got %s", a))
}
defer request.Body.Close()
body, err := ioutil.ReadAll(request.Body)
if err != nil {
panic(err)
}
return []reflect.Value{reflect.ValueOf(string(body))}
case "application/xml":
if a.Kind() != reflect.Ptr || a.Elem().Kind() != reflect.Struct {
panic(fmt.Errorf("HandlePOST(): first handler argument must be a struct pointer when request Content-Type is application/xml, got %s", a))
}
s := reflect.New(a.Elem())
defer request.Body.Close()
body, err := ioutil.ReadAll(request.Body)
if err != nil {
panic(err)
}
err = xml.Unmarshal(body, s.Interface())
if err != nil {
panic(err)
}
return []reflect.Value{s}
case "application/json":
if a.Kind() != reflect.Ptr || a.Elem().Kind() != reflect.Struct {
panic(fmt.Errorf("HandlePOST(): first handler argument must be a struct pointer when request Content-Type is application/json, got %s", a))
}
s := reflect.New(a.Elem())
defer request.Body.Close()
body, err := ioutil.ReadAll(request.Body)
if err != nil {
panic(err)
}
err = json.Unmarshal(body, s.Interface())
if err != nil {
panic(err)
}
return []reflect.Value{s}
case "multipart/form-data":
if a.Kind() != reflect.Ptr || a.Elem().Kind() != reflect.Struct {
panic(fmt.Errorf("HandlePOST(): first handler argument must be a struct pointer when request Content-Type is multipart/form-data, got %s", a))
}
file, _, err := request.FormFile("JSON")
if err != nil {
panic(err)
}
s := reflect.New(a.Elem())
defer file.Close()
body, err := ioutil.ReadAll(request.Body)
if err != nil {
panic(err)
}
err = json.Unmarshal(body, s.Interface())
if err != nil {
panic(err)
}
return []reflect.Value{s}
}
panic("Unsupported POST Content-Type: " + ct)
}
default:
panic(fmt.Errorf("HandlePOST(): handler accepts only one or thwo arguments, got %d", len(in)))
}
httpHandler.writeResult = writeResultFunc(out)
http.Handle(path, httpHandler)
}
/*
RunServer starts an HTTP server with a given address
with the registered GET and POST handlers.
If stop is non nil then a send on the channel
will gracefully stop the server.
*/
func RunServer(addr string, stop chan struct{}) {
server := &http.Server{Addr: addr}
listener, err := net.Listen("tcp", addr)
if err != nil {
panic(err)
}
if stop != nil {
go func() {
<-stop
err := listener.Close()
if err != nil {
os.Stderr.WriteString(err.Error())
}
return
}()
}
Log("Server listening at", addr)
err = server.Serve(listener)
// I know, that's a ugly and depending on undocumented behavior.
// But when the implementation changes, we'll see it immediately as panic.
// To the keepers of the Go standard libraries:
// It would be useful to return a documented error type
// when the network connection is closed.
if !strings.Contains(err.Error(), "use of closed network connection") {
panic(err)
}
Log("Server stopped")
}
///////////////////////////////////////////////////////////////////////////////
// Internal stuff:
func getHandlerFunc(handler interface{}, object []interface{}) (f reflectionFunc, in, out []reflect.Type) {
handlerValue := reflect.ValueOf(handler)
if handlerValue.Kind() != reflect.Func {
panic(fmt.Errorf("handler must be a function, got %T", handler))
}
handlerType := handlerValue.Type()
out = make([]reflect.Type, handlerType.NumOut())
for i := 0; i < handlerType.NumOut(); i++ {
out[i] = handlerType.Out(i)
}
switch len(object) {
case 0:
f = func(args []reflect.Value) []reflect.Value {
return handlerValue.Call(args)
}
in = make([]reflect.Type, handlerType.NumIn())
for i := 0; i < handlerType.NumIn(); i++ {
in[i] = handlerType.In(i)
}
return f, in, out
case 1:
objectValue := reflect.ValueOf(object[0])
if objectValue.Kind() != reflect.Ptr {
panic(fmt.Errorf("object must be a pointer, got %T", objectValue.Interface()))
}
f = func(args []reflect.Value) []reflect.Value {
args = append([]reflect.Value{objectValue}, args...)
return handlerValue.Call(args)
}
in = make([]reflect.Type, handlerType.NumIn()-1)
for i := 1; i < handlerType.NumIn(); i++ {
in[i] = handlerType.In(i)
}
return f, in, out
}
panic(fmt.Errorf("HandleGET(): only zero or one object allowed, got %d", len(object)))
}
var (
urlValuesType = reflect.TypeOf((*url.Values)(nil)).Elem()
errorType = reflect.TypeOf((*error)(nil)).Elem()
)
type reflectionFunc func([]reflect.Value) []reflect.Value
type httpHandler struct {
method string
getArgs func(*http.Request) []reflect.Value
handlerFunc reflectionFunc
writeResult func([]reflect.Value, http.ResponseWriter)
}
func (handler *httpHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
Log(request.Method, request.URL)
if !DontCheckRequestMethod && request.Method != handler.method {
http.Error(writer, "405: Method Not Allowed", http.StatusMethodNotAllowed)
return
}
result := handler.handlerFunc(handler.getArgs(request))
handler.writeResult(result, writer)
}
func writeError(writer http.ResponseWriter, err error) {
Log("ERROR:", err)
http.Error(writer, err.Error(), http.StatusInternalServerError)
}
func writeResultFunc(out []reflect.Type) func([]reflect.Value, http.ResponseWriter) {
var returnError func(result []reflect.Value, writer http.ResponseWriter) bool
switch len(out) {
case 2:
if out[1] == errorType {
returnError = func(result []reflect.Value, writer http.ResponseWriter) (isError bool) {
if isError = !result[1].IsNil(); isError {
writeError(writer, result[1].Interface().(error))
}
return isError
}
} else {
panic(fmt.Errorf("HandleGET(): second result value of handle must be of type error, got %s", out[1]))
}
fallthrough
case 1:
r := out[0]
if r.Kind() == reflect.Struct || (r.Kind() == reflect.Ptr && r.Elem().Kind() == reflect.Struct) {
return func(result []reflect.Value, writer http.ResponseWriter) {
if returnError != nil && returnError(result, writer) {
return
}
j, err := json.Marshal(result[0].Interface())
if err != nil {
writeError(writer, err)
return
}
if IndentJSON != "" {
var buf bytes.Buffer
err = json.Indent(&buf, j, "", IndentJSON)
if err != nil {
writeError(writer, err)
return
}
j = buf.Bytes()
}
writer.Header().Set("Content-Type", "application/json")
writer.Write(j)
}
} else if r.Kind() == reflect.String {
return func(result []reflect.Value, writer http.ResponseWriter) {
if returnError != nil && returnError(result, writer) {
return
}
bytes := []byte(result[0].String())
ct := http.DetectContentType(bytes)
writer.Header().Set("Content-Type", ct)
writer.Write(bytes)
}
} else {
panic(fmt.Errorf("first result value of handler must be of type string or struct(pointer), got %s", r))
}
case 0:
return func(result []reflect.Value, writer http.ResponseWriter) {
// do nothing, status code 200 will be returned
}
}
panic(fmt.Errorf("zero to two return values allowed, got %d", len(out)))
}