-
Notifications
You must be signed in to change notification settings - Fork 24
/
mux.go
132 lines (117 loc) · 4.04 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
package mux
import (
"context"
"fmt"
"net/http"
"strings"
)
// Mux is a tire base HTTP request router which can be used to
// dispatch requests to different handler functions.
type Mux struct {
trie *Trie
defaultHandler http.HandlerFunc
}
// New returns a Mux instance.
func New(opts ...Options) *Mux {
return &Mux{trie: NewTrie(opts...)}
}
// Get registers a new GET route for a path with matching handler in the Mux.
func (m *Mux) Get(pattern string, handler http.HandlerFunc) {
m.Handle(http.MethodGet, pattern, handler)
}
// Head registers a new HEAD route for a path with matching handler in the Mux.
func (m *Mux) Head(pattern string, handler http.HandlerFunc) {
m.Handle(http.MethodHead, pattern, handler)
}
// Post registers a new POST route for a path with matching handler in the Mux.
func (m *Mux) Post(pattern string, handler http.HandlerFunc) {
m.Handle(http.MethodPost, pattern, handler)
}
// Put registers a new PUT route for a path with matching handler in the Mux.
func (m *Mux) Put(pattern string, handler http.HandlerFunc) {
m.Handle(http.MethodPut, pattern, handler)
}
// Patch registers a new PATCH route for a path with matching handler in the Mux.
func (m *Mux) Patch(pattern string, handler http.HandlerFunc) {
m.Handle(http.MethodPatch, pattern, handler)
}
// Delete registers a new DELETE route for a path with matching handler in the Mux.
func (m *Mux) Delete(pattern string, handler http.HandlerFunc) {
m.Handle(http.MethodDelete, pattern, handler)
}
// Options registers a new OPTIONS route for a path with matching handler in the Mux.
func (m *Mux) Options(pattern string, handler http.HandlerFunc) {
m.Handle(http.MethodOptions, pattern, handler)
}
// DefaultHandler registers a new handler in the Mux
// that will run if there is no other handler matching.
func (m *Mux) DefaultHandler(handler http.HandlerFunc) {
m.defaultHandler = handler
}
// Handle registers a new handler with method and path in the Mux.
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
// functions can be used.
func (m *Mux) Handle(method, pattern string, handler http.HandlerFunc) {
if method == "" {
panic(fmt.Errorf("invalid method"))
}
m.trie.Parse(pattern).Handle(strings.ToUpper(method), handler)
}
// Handler is an adapter which allows the usage of an http.Handler as a
// request handle.
func (m *Mux) Handler(method, path string, handler http.Handler) {
m.Handle(method, path, func(w http.ResponseWriter, req *http.Request) {
handler.ServeHTTP(w, req)
})
}
// ServeHTTP implemented http.Handler interface
func (m *Mux) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var handler http.HandlerFunc
path := req.URL.Path
method := req.Method
match, err := m.trie.Match(path)
if err != nil {
http.Error(w, fmt.Sprintf(`"Access %s: %s"`, path, err), http.StatusNotImplemented)
return
}
if match.Node == nil {
// Redirect for slash url
// Router /a/b Access PATH /a/b/ Redirect to /a/b
// Router /a/b/ Access PATH /a/b Redirect to /a/b/
if match.Path != "" {
req.URL.Path = match.Path
code := http.StatusMovedPermanently
if method != "GET" {
code = http.StatusTemporaryRedirect
}
http.Redirect(w, req, req.URL.String(), code)
return
}
if m.defaultHandler == nil {
http.Error(w, fmt.Sprintf(`"%s" not implemented`, path), http.StatusNotFound)
return
}
handler = m.defaultHandler
} else {
var ok bool
if handler, ok = match.Node.GetHandler(method).(http.HandlerFunc); !ok {
// OPTIONS preflight
if method == http.MethodOptions {
w.Header().Set("Access-Control-Allow-Methods", strings.Join(match.Node.GetAllow(), ", "))
w.WriteHeader(http.StatusNoContent)
return
}
if m.defaultHandler == nil {
w.Header().Set("Access-Control-Allow-Methods", strings.Join(match.Node.GetAllow(), ", "))
http.Error(w, fmt.Sprintf(`"%s" not allowed in "%s"`, method, path), 405)
return
}
handler = m.defaultHandler
}
}
if match.Params != nil {
ctx := context.WithValue(req.Context(), routeParamsID, match.Params)
req = req.WithContext(ctx)
}
handler(w, req)
}