-
Notifications
You must be signed in to change notification settings - Fork 2
/
handler.go
147 lines (122 loc) · 3.28 KB
/
handler.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
package autohttp
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"reflect"
"github.com/fortytw2/lounge"
)
var (
ErrTooManyInputArgs = errors.New("autohttp: too many input args")
ErrTooManyReturnValues = errors.New("autohttp: too many return values")
)
// 65536
var DefaultMaxBytesToRead int64 = 2 << 15
type Header map[string]string
type HeaderWriter func(key, val string)
type Decoder interface {
ValidateType(x interface{}) error
// Decode returns the reflect values needed to call the fn
// from the *http.Request
Decode(fn interface{}, r *http.Request) ([]reflect.Value, error)
}
type Encoder interface {
ValidateType(x interface{}) error
// Encode cannot write a status code, this is reserved for autohttp to control
// preventing duplicate WriteHeader calls
Encode(values interface{}, hw HeaderWriter) (int, io.Reader, error)
}
type ErrorHandler func(w http.ResponseWriter, err error)
func DefaultErrorHandler(w http.ResponseWriter, err error) {
ewc, ok := err.(ErrorWithCode)
if ok {
w.WriteHeader(ewc.StatusCode)
json.NewEncoder(w).Encode(map[string]string{
"error": err.Error(),
})
return
}
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{
"error": err.Error(),
})
}
// An Handler is an http.Handler generated from any function
type Handler struct {
fn interface{}
log lounge.Log
encoder Encoder
decoder Decoder
errorHandler ErrorHandler
hideFromIntrospectors bool
}
func NewHandler(
log lounge.Log,
decoder Decoder,
encoder Encoder,
errorHandler ErrorHandler,
fn interface{},
) (*Handler, error) {
if decoder == nil || encoder == nil {
return nil, errors.New("a decoder and encoder must be supplied. use httpz.NoOpDecoder")
}
err := decoder.ValidateType(fn)
if err != nil {
return nil, err
}
err = encoder.ValidateType(fn)
if err != nil {
return nil, err
}
// extra autoroute rule
if reflect.ValueOf(fn).Type().NumOut() > 2 {
return nil, errors.New("a function can only have up to 2 return values")
}
return &Handler{
fn: fn,
encoder: encoder,
decoder: decoder,
errorHandler: DefaultErrorHandler,
hideFromIntrospectors: false,
}, nil
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// handle panics
defer func() {
if r := recover(); r != nil {
fmt.Fprintf(w, "panic in route execution: %v", r)
}
}()
callValues, err := h.decoder.Decode(h.fn, r)
if err != nil {
// encode the parsing error cleanly
h.errorHandler(w, err)
return
}
// call the handler function using reflection
returnValues := reflect.ValueOf(h.fn).Call(callValues)
// split out the error value and the return value
var encodableValue interface{} = nil
for _, rv := range returnValues {
if isErrorType(rv.Type()) && !rv.IsNil() && !rv.IsZero() {
err = rv.Interface().(error)
// encode the parsing error cleanly
h.errorHandler(w, err)
return
} else if !isErrorType(rv.Type()) {
encodableValue = rv.Interface()
}
}
responseCode, body, err := h.encoder.Encode(encodableValue, w.Header().Set)
if err != nil {
h.errorHandler(w, err)
} else {
w.WriteHeader(responseCode)
_, err = io.Copy(w, body)
if err != nil {
h.log.Errorf("error copying response body to writer: %s", err)
}
}
}