OSDN Git Service

Hulk did something
[bytom/vapor.git] / net / http / httpjson / handler.go
1 package httpjson
2
3 import (
4         "context"
5         "encoding/json"
6         "errors"
7         "net/http"
8         "reflect"
9 )
10
11 // ErrorWriter is responsible for writing the provided error value
12 // to the response.
13 type ErrorWriter func(context.Context, http.ResponseWriter, error)
14
15 // DefaultResponse will be sent as the response body
16 // when the handler function signature
17 // has no return value.
18 var DefaultResponse = json.RawMessage(`{"message":"ok"}`)
19
20 // handler is an http.Handler that calls a function for each request.
21 // It uses the signature of the function to decide how to interpret
22 type handler struct {
23         fv      reflect.Value
24         inType  reflect.Type
25         hasCtx  bool
26         errFunc ErrorWriter
27 }
28
29 // Handler returns an HTTP handler for function f.
30 // See the package doc for details on allowed signatures for f.
31 // If f returns a non-nil error, the handler will call errFunc.
32 func Handler(f interface{}, errFunc ErrorWriter) (http.Handler, error) {
33         fv := reflect.ValueOf(f)
34         hasCtx, inType, err := funcInputType(fv)
35         if err != nil {
36                 return nil, err
37         }
38
39         h := &handler{fv, inType, hasCtx, errFunc}
40         return h, nil
41 }
42
43 func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
44         var a []reflect.Value
45         if h.hasCtx {
46                 ctx := req.Context()
47                 ctx = context.WithValue(ctx, reqKey, req)
48                 ctx = context.WithValue(ctx, respKey, w)
49                 a = append(a, reflect.ValueOf(ctx))
50         }
51         if h.inType != nil {
52                 inPtr := reflect.New(h.inType)
53                 err := Read(req.Body, inPtr.Interface())
54                 if err != nil {
55                         h.errFunc(req.Context(), w, err)
56                         return
57                 }
58                 a = append(a, inPtr.Elem())
59         }
60         rv := h.fv.Call(a)
61
62         var (
63                 res interface{}
64                 err error
65         )
66         switch n := len(rv); {
67         case n == 0:
68                 res = &DefaultResponse
69         case n == 1 && !h.fv.Type().Out(0).Implements(errorType):
70                 res = rv[0].Interface()
71         case n == 1 && h.fv.Type().Out(0).Implements(errorType):
72                 // out param is of type error; its value can still be nil
73                 res = &DefaultResponse
74                 err, _ = rv[0].Interface().(error)
75         case n == 2:
76                 res = rv[0].Interface()
77                 err, _ = rv[1].Interface().(error)
78         }
79         if err != nil {
80                 h.errFunc(req.Context(), w, err)
81                 return
82         }
83
84         Write(req.Context(), w, 200, res)
85 }
86
87 var (
88         errorType   = reflect.TypeOf((*error)(nil)).Elem()
89         contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
90 )
91
92 func funcInputType(fv reflect.Value) (hasCtx bool, t reflect.Type, err error) {
93         ft := fv.Type()
94         if ft.Kind() != reflect.Func || ft.IsVariadic() {
95                 return false, nil, errors.New("need nonvariadic func in " + ft.String())
96         }
97
98         off := 0 // or 1 with context
99         hasCtx = ft.NumIn() >= 1 && ft.In(0).Implements(contextType)
100         if hasCtx {
101                 off = 1
102         }
103
104         if ft.NumIn() > off+1 {
105                 return false, nil, errors.New("too many params in " + ft.String())
106         }
107
108         if ft.NumIn() == off+1 {
109                 t = ft.In(ft.NumIn() - 1)
110         }
111
112         if n := ft.NumOut(); n == 2 && !ft.Out(1).Implements(errorType) {
113                 return false, nil, errors.New("second return value must be error in " + ft.String())
114         } else if n > 2 {
115                 return false, nil, errors.New("need at most two return values in " + ft.String())
116         }
117
118         return hasCtx, t, nil
119 }