-
Notifications
You must be signed in to change notification settings - Fork 2
/
mux.go
133 lines (110 loc) · 3.08 KB
/
mux.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
package fdk
import (
"context"
"fmt"
"net/http"
"os"
"strconv"
)
const (
healthzRoute = "/healthz"
healthzMethod = http.MethodGet
)
// Mux defines a handler that will dispatch to a matching route/method combination. Much
// like the std lib http.ServeMux, but with slightly more opinionated route setting. We
// only support the DELETE, GET, POST, and PUT.
type Mux struct {
routes map[string]bool
meth2Routes map[string]map[string]bool
handlers map[routeKey]Handler
}
// NewMux creates a new Mux that is ready for assignment.
func NewMux() *Mux {
m := &Mux{
routes: make(map[string]bool),
meth2Routes: make(map[string]map[string]bool),
handlers: make(map[routeKey]Handler),
}
m.Get(healthzRoute, HandlerFn(func(ctx context.Context, r Request) Response {
return Response{
Code: http.StatusOK,
Body: JSON(map[string]string{
"status": "ok",
"fn_id": r.FnID,
"fn_build_version": os.Getenv("CS_FN_BUILD_VERSION"),
"fn_version": strconv.Itoa(r.FnVersion),
}),
}
}))
return m
}
// Handle enacts the handler to process the request/response lifecycle. The mux fulfills the
// Handler interface and can dispatch to any number of sub routes.
func (m *Mux) Handle(ctx context.Context, r Request) Response {
route := r.URL
if route == "" {
route = "/"
}
rk := routeKey{route: route, method: r.Method}
if !m.routes[rk.route] {
return Response{Errors: []APIError{{Code: http.StatusNotFound, Message: "route not found"}}}
}
if !m.meth2Routes[rk.method][rk.route] {
return Response{Errors: []APIError{{Code: http.StatusMethodNotAllowed, Message: "method not allowed"}}}
}
h := m.handlers[rk] // checks above guarantee this exists here
return h.Handle(ctx, r)
}
// Delete creates a DELETE route.
func (m *Mux) Delete(route string, h Handler) {
m.registerRoute(http.MethodDelete, route, h)
}
// Get creates a GET route.
func (m *Mux) Get(route string, h Handler) {
m.registerRoute(http.MethodGet, route, h)
}
// Post creates a POST route.
func (m *Mux) Post(route string, h Handler) {
m.registerRoute(http.MethodPost, route, h)
}
// Put creates a PUT route.
func (m *Mux) Put(route string, h Handler) {
m.registerRoute(http.MethodPut, route, h)
}
func (m *Mux) registerRoute(method, route string, h Handler) {
if route == "" {
panic("route must be provided")
}
if h == nil {
panic("handler must not be nil")
}
isHealthZ := route == healthzRoute && method == healthzMethod
rk := routeKey{route: route, method: method}
if _, ok := m.handlers[rk]; ok && !isHealthZ {
panic(fmt.Sprintf("multiple handlers added for: %q ", method+" "+route))
}
{
// nil checks, make the zero value useful
if m.routes == nil {
m.routes = map[string]bool{}
}
if m.meth2Routes == nil {
m.meth2Routes = map[string]map[string]bool{}
}
if m.handlers == nil {
m.handlers = map[routeKey]Handler{}
}
}
m.routes[route] = true
m2r := m.meth2Routes[method]
if m2r == nil {
m2r = map[string]bool{}
}
m2r[route] = true
m.meth2Routes[method] = m2r
m.handlers[rk] = h
}
type routeKey struct {
method string
route string
}