forked from guregu/kami
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmiddleware.go
130 lines (117 loc) · 4.18 KB
/
middleware.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
package kami
import (
"fmt"
"net/http"
"strings"
"golang.org/x/net/context"
"github.com/go-kami/tree"
)
// Middleware is a function that takes the current request context and returns a new request context.
// You can use middleware to build your context before your handler handles a request.
// As a special case, middleware that returns nil will halt middleware and handler execution (LogHandler will still run).
type Middleware func(context.Context, http.ResponseWriter, *http.Request) context.Context
// MiddlewareType represents types that kami can convert to Middleware.
// kami will try its best to convert standard, non-context middleware.
// See the Use function for important information about how kami middleware is run.
// The following concrete types are accepted:
// - Middleware
// - func(context.Context, http.ResponseWriter, *http.Request) context.Context
// - func(http.Handler) http.Handler [will run sequentially, not in a chain]
// - func(http.ContextHandler) http.ContextHandler [will run sequentially, not in a chain]
type MiddlewareType interface{}
var middleware = make(map[string][]Middleware) // "normal" non-wildcard middleware
var wildcardMW = new(tree.Node) // wildcard middleware
// Use registers middleware to run for the given path.
// Middleware with be executed hierarchically, starting with the least specific path.
// Middleware will be executed in order of registration.
// You may use wildcards in the path. Wildcard middleware will be run last,
// after all hierarchical middleware has run.
//
// Adding middleware is not threadsafe.
//
// WARNING: kami middleware is run in sequence, but standard middleware is chained;
// middleware that expects its code to run after the next handler, such as
// standard loggers and panic handlers, will not work as expected.
// Use kami.LogHandler and kami.PanicHandler instead.
// Standard middleware that does not call the next handler to stop the request is supported.
func Use(path string, mw MiddlewareType) {
if containsWildcard(path) {
wildcardMW.AddRoute(path, convert(mw))
} else {
fn := convert(mw)
chain := middleware[path]
chain = append(chain, fn)
middleware[path] = chain
}
}
// run runs the middleware chain for a particular request.
// run returns false if it should stop early.
func run(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool) {
for i, c := range r.URL.Path {
if c == '/' || i == len(r.URL.Path)-1 {
wares, ok := middleware[r.URL.Path[:i+1]]
if !ok {
continue
}
for _, mw := range wares {
// return nil middleware to stop
result := mw(ctx, w, r)
if result == nil {
return ctx, false
}
ctx = result
}
}
}
// wildcard middlewares
if wild, params, _ := wildcardMW.GetValue(r.URL.Path); wild != nil {
if mw, ok := wild.(Middleware); ok {
ctx = mergeParams(ctx, params)
result := mw(ctx, w, r)
if result == nil {
return ctx, false
}
ctx = result
}
}
return ctx, true
}
// convert turns standard http middleware into kami Middleware if needed.
func convert(mw MiddlewareType) Middleware {
switch x := mw.(type) {
case Middleware:
return x
case func(context.Context, http.ResponseWriter, *http.Request) context.Context:
return Middleware(x)
case func(ContextHandler) ContextHandler:
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
var dh dummyHandler
x(&dh).ServeHTTPContext(ctx, w, r)
if !dh {
return nil
}
return ctx
}
case func(http.Handler) http.Handler:
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context {
var dh dummyHandler
x(&dh).ServeHTTP(w, r)
if !dh {
return nil
}
return ctx
}
}
panic(fmt.Errorf("unsupported MiddlewareType: %T", mw))
}
// dummyHandler is used to keep track of whether the next middleware was called or not.
type dummyHandler bool
func (dh *dummyHandler) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
*dh = true
}
func (dh *dummyHandler) ServeHTTPContext(_ context.Context, _ http.ResponseWriter, _ *http.Request) {
*dh = true
}
func containsWildcard(path string) bool {
return strings.Contains(path, "/:") || strings.Contains(path, "/*")
}