Skip to content

Commit 70acd57

Browse files
authored
Fix case when routeNotFound handler is lost when new route is added to the router (#2219)
1 parent 690e339 commit 70acd57

File tree

2 files changed

+72
-16
lines changed

2 files changed

+72
-16
lines changed

router.go

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,12 @@ func (r *Router) insert(method, path string, t kind, rm routeMethod) {
224224
}
225225
currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
226226
} else if lcpLen < prefixLen {
227-
// Split node
227+
// Split node into two before we insert new node.
228+
// This happens when we are inserting path that is submatch of any existing inserted paths.
229+
// For example, we have node `/test` and now are about to insert `/te/*`. In that case
230+
// 1. overlapping part is `/te` that is used as parent node
231+
// 2. `st` is part from existing node that is not matching - it gets its own node (child to `/te`)
232+
// 3. `/*` is the new part we are about to insert (child to `/te`)
228233
n := newNode(
229234
currentNode.kind,
230235
currentNode.prefix[lcpLen:],
@@ -235,6 +240,7 @@ func (r *Router) insert(method, path string, t kind, rm routeMethod) {
235240
currentNode.paramsCount,
236241
currentNode.paramChild,
237242
currentNode.anyChild,
243+
currentNode.notFoundHandler,
238244
)
239245
// Update parent path for all children to new node
240246
for _, child := range currentNode.staticChildren {
@@ -259,6 +265,7 @@ func (r *Router) insert(method, path string, t kind, rm routeMethod) {
259265
currentNode.anyChild = nil
260266
currentNode.isLeaf = false
261267
currentNode.isHandler = false
268+
currentNode.notFoundHandler = nil
262269

263270
// Only Static children could reach here
264271
currentNode.addStaticChild(n)
@@ -273,7 +280,7 @@ func (r *Router) insert(method, path string, t kind, rm routeMethod) {
273280
}
274281
} else {
275282
// Create child node
276-
n = newNode(t, search[lcpLen:], currentNode, nil, "", new(routeMethods), 0, nil, nil)
283+
n = newNode(t, search[lcpLen:], currentNode, nil, "", new(routeMethods), 0, nil, nil, nil)
277284
if rm.handler != nil {
278285
n.addMethod(method, &rm)
279286
n.paramsCount = len(rm.pnames)
@@ -292,7 +299,7 @@ func (r *Router) insert(method, path string, t kind, rm routeMethod) {
292299
continue
293300
}
294301
// Create child node
295-
n := newNode(t, search, currentNode, nil, rm.ppath, new(routeMethods), 0, nil, nil)
302+
n := newNode(t, search, currentNode, nil, rm.ppath, new(routeMethods), 0, nil, nil, nil)
296303
if rm.handler != nil {
297304
n.addMethod(method, &rm)
298305
n.paramsCount = len(rm.pnames)
@@ -319,20 +326,32 @@ func (r *Router) insert(method, path string, t kind, rm routeMethod) {
319326
}
320327
}
321328

322-
func newNode(t kind, pre string, p *node, sc children, originalPath string, mh *routeMethods, paramsCount int, paramChildren, anyChildren *node) *node {
329+
func newNode(
330+
t kind,
331+
pre string,
332+
p *node,
333+
sc children,
334+
originalPath string,
335+
methods *routeMethods,
336+
paramsCount int,
337+
paramChildren,
338+
anyChildren *node,
339+
notFoundHandler *routeMethod,
340+
) *node {
323341
return &node{
324-
kind: t,
325-
label: pre[0],
326-
prefix: pre,
327-
parent: p,
328-
staticChildren: sc,
329-
originalPath: originalPath,
330-
methods: mh,
331-
paramsCount: paramsCount,
332-
paramChild: paramChildren,
333-
anyChild: anyChildren,
334-
isLeaf: sc == nil && paramChildren == nil && anyChildren == nil,
335-
isHandler: mh.isHandler(),
342+
kind: t,
343+
label: pre[0],
344+
prefix: pre,
345+
parent: p,
346+
staticChildren: sc,
347+
originalPath: originalPath,
348+
methods: methods,
349+
paramsCount: paramsCount,
350+
paramChild: paramChildren,
351+
anyChild: anyChildren,
352+
isLeaf: sc == nil && paramChildren == nil && anyChildren == nil,
353+
isHandler: methods.isHandler(),
354+
notFoundHandler: notFoundHandler,
336355
}
337356
}
338357

router_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,6 +1286,43 @@ func TestNotFoundRouteStaticKind(t *testing.T) {
12861286
}
12871287
}
12881288

1289+
func TestRouter_notFoundRouteWithNodeSplitting(t *testing.T) {
1290+
e := New()
1291+
r := e.router
1292+
1293+
r.Add(http.MethodGet, "/test*", handlerHelper("ID", 0))
1294+
r.Add(RouteNotFound, "/*", handlerHelper("ID", 1))
1295+
r.Add(RouteNotFound, "/test", handlerHelper("ID", 2))
1296+
1297+
// Tree before:
1298+
// 1 `/`
1299+
// 1.1 `*` (any) ID=1
1300+
// 1.2 `test` (static) ID=2
1301+
// 1.2.1 `*` (any) ID=0
1302+
1303+
// node with path `test` has routeNotFound handler from previous Add call. Now when we insert `/te/st*` into router tree
1304+
// This means that node `test` is split into `te` and `st` nodes and new node `/st*` is inserted.
1305+
// On that split `/test` routeNotFound handler must not be lost.
1306+
r.Add(http.MethodGet, "/te/st*", handlerHelper("ID", 3))
1307+
// Tree after:
1308+
// 1 `/`
1309+
// 1.1 `*` (any) ID=1
1310+
// 1.2 `te` (static)
1311+
// 1.2.1 `st` (static) ID=2
1312+
// 1.2.1.1 `*` (any) ID=0
1313+
// 1.2.2 `/st` (static)
1314+
// 1.2.2.1 `*` (any) ID=3
1315+
1316+
c := e.NewContext(nil, nil).(*context)
1317+
r.Find(http.MethodPut, "/test", c)
1318+
1319+
c.handler(c)
1320+
1321+
testValue, _ := c.Get("ID").(int)
1322+
assert.Equal(t, 2, testValue)
1323+
assert.Equal(t, "/test", c.Path())
1324+
}
1325+
12891326
// Issue #1509
12901327
func TestRouterParamStaticConflict(t *testing.T) {
12911328
e := New()

0 commit comments

Comments
 (0)