1
1
package router
2
2
3
3
import (
4
+ "errors"
4
5
"fmt"
5
6
"net/http"
6
7
"sort"
7
8
"strings"
9
+ "sync"
10
+ "sync/atomic"
8
11
"time"
9
12
10
13
"github.com/kataras/iris/v12/context"
@@ -39,8 +42,18 @@ type (
39
42
// on the given context's response status code.
40
43
FireErrorCode (ctx * context.Context )
41
44
}
45
+
46
+ // RouteAdder is an optional interface that can be implemented by a `RequestHandler`.
47
+ RouteAdder interface {
48
+ // AddRoute should add a route to the request handler directly.
49
+ AddRoute (* Route ) error
50
+ }
42
51
)
43
52
53
+ // ErrNotRouteAdder throws on `AddRouteUnsafe` when a registered `RequestHandler`
54
+ // does not implements the optional `AddRoute(*Route) error` method.
55
+ var ErrNotRouteAdder = errors .New ("request handler does not implement AddRoute method" )
56
+
44
57
type routerHandler struct {
45
58
// Config.
46
59
disablePathCorrection bool
@@ -59,8 +72,103 @@ type routerHandler struct {
59
72
errorDefaultHandlers context.Handlers // the main handler(s) for default error code handlers, when not registered directly by the end-developer.
60
73
}
61
74
62
- var _ RequestHandler = (* routerHandler )(nil )
63
- var _ HTTPErrorHandler = (* routerHandler )(nil )
75
+ var (
76
+ _ RequestHandler = (* routerHandler )(nil )
77
+ _ HTTPErrorHandler = (* routerHandler )(nil )
78
+ )
79
+
80
+ type routerHandlerDynamic struct {
81
+ RequestHandler
82
+ rw sync.RWMutex
83
+
84
+ locked uint32
85
+ }
86
+
87
+ // RouteExists reports whether a particular route exists.
88
+ func (h * routerHandlerDynamic ) RouteExists (ctx * context.Context , method , path string ) (exists bool ) {
89
+ h .lock (false , func () error {
90
+ exists = h .RequestHandler .RouteExists (ctx , method , path )
91
+ return nil
92
+ })
93
+
94
+ return
95
+ }
96
+
97
+ func (h * routerHandlerDynamic ) AddRoute (r * Route ) error {
98
+ if v , ok := h .RequestHandler .(RouteAdder ); ok {
99
+ return h .lock (true , func () error {
100
+ return v .AddRoute (r )
101
+ })
102
+ }
103
+
104
+ return ErrNotRouteAdder
105
+ }
106
+
107
+ func (h * routerHandlerDynamic ) lock (writeAccess bool , fn func () error ) error {
108
+ if atomic .CompareAndSwapUint32 (& h .locked , 0 , 1 ) {
109
+ if writeAccess {
110
+ h .rw .Lock ()
111
+ } else {
112
+ h .rw .RLock ()
113
+ }
114
+
115
+ err := fn ()
116
+
117
+ // check agan because fn may called the unlock method.
118
+ if atomic .CompareAndSwapUint32 (& h .locked , 1 , 0 ) {
119
+ if writeAccess {
120
+ h .rw .Unlock ()
121
+ } else {
122
+ h .rw .RUnlock ()
123
+ }
124
+ }
125
+
126
+ return err
127
+ }
128
+
129
+ return fn ()
130
+ }
131
+
132
+ func (h * routerHandlerDynamic ) Build (provider RoutesProvider ) error {
133
+ // Build can be called inside HandleRequest if the route handler
134
+ // calls the RefreshRouter method, and it will stuck on the rw.Lock() call,
135
+ // so use a custom version of it.
136
+ // h.rw.Lock()
137
+ // defer h.rw.Unlock()
138
+
139
+ return h .lock (true , func () error {
140
+ return h .RequestHandler .Build (provider )
141
+ })
142
+ }
143
+
144
+ func (h * routerHandlerDynamic ) HandleRequest (ctx * context.Context ) {
145
+ h .lock (false , func () error {
146
+ h .RequestHandler .HandleRequest (ctx )
147
+ return nil
148
+ })
149
+ }
150
+
151
+ func (h * routerHandlerDynamic ) FireErrorCode (ctx * context.Context ) {
152
+ h .lock (false , func () error {
153
+ h .RequestHandler .FireErrorCode (ctx )
154
+ return nil
155
+ })
156
+ }
157
+
158
+ // NewDynamicHandler returns a new router handler which is responsible handle each request
159
+ // with routes that can be added in serve-time.
160
+ // It's a wrapper of the `NewDefaultHandler`.
161
+ // It's being used when the `ConfigurationReadOnly.GetEnableDynamicHandler` is true.
162
+ func NewDynamicHandler (config context.ConfigurationReadOnly , logger * golog.Logger ) RequestHandler /* #2167 */ {
163
+ handler := NewDefaultHandler (config , logger )
164
+ return wrapDynamicHandler (handler )
165
+ }
166
+
167
+ func wrapDynamicHandler (handler RequestHandler ) RequestHandler {
168
+ return & routerHandlerDynamic {
169
+ RequestHandler : handler ,
170
+ }
171
+ }
64
172
65
173
// NewDefaultHandler returns the handler which is responsible
66
174
// to map the request with a route (aka mux implementation).
@@ -71,6 +179,7 @@ func NewDefaultHandler(config context.ConfigurationReadOnly, logger *golog.Logge
71
179
fireMethodNotAllowed bool
72
180
enablePathIntelligence bool
73
181
forceLowercaseRouting bool
182
+ dynamicHandlerEnabled bool
74
183
)
75
184
76
185
if config != nil { // #2147
@@ -79,16 +188,23 @@ func NewDefaultHandler(config context.ConfigurationReadOnly, logger *golog.Logge
79
188
fireMethodNotAllowed = config .GetFireMethodNotAllowed ()
80
189
enablePathIntelligence = config .GetEnablePathIntelligence ()
81
190
forceLowercaseRouting = config .GetForceLowercaseRouting ()
191
+ dynamicHandlerEnabled = config .GetEnableDynamicHandler ()
82
192
}
83
193
84
- return & routerHandler {
194
+ handler := & routerHandler {
85
195
disablePathCorrection : disablePathCorrection ,
86
196
disablePathCorrectionRedirection : disablePathCorrectionRedirection ,
87
197
fireMethodNotAllowed : fireMethodNotAllowed ,
88
198
enablePathIntelligence : enablePathIntelligence ,
89
199
forceLowercaseRouting : forceLowercaseRouting ,
90
200
logger : logger ,
91
201
}
202
+
203
+ if dynamicHandlerEnabled {
204
+ return wrapDynamicHandler (handler )
205
+ }
206
+
207
+ return handler
92
208
}
93
209
94
210
func (h * routerHandler ) getTree (statusCode int , method , subdomain string ) * trie {
0 commit comments