Skip to content

Commit 3fc08c9

Browse files
author
qm012
committed
handle level 1 router
add more comments update comment add example fix benchmark not found add comment and update test method gin_integration_test.go#L407 update comment and lastedNode directly assign current node optimize code Optimize the search next router logic optimize code Adjust the matching rules Adjust the matching order update condition code
1 parent 690aa2b commit 3fc08c9

File tree

4 files changed

+129
-33
lines changed

4 files changed

+129
-33
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ coverage.out
44
count.out
55
test
66
profile.out
7-
tmp.out
7+
tmp.out

gin_integration_test.go

+45-3
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,33 @@ import (
2222
"github.com/stretchr/testify/assert"
2323
)
2424

25-
func testRequest(t *testing.T, url string) {
25+
// params[0]=url example:http://127.0.0.1:8080/index (cannot be empty)
26+
// params[1]=response body (custom compare content)
27+
func testRequest(t *testing.T, params ...string) {
28+
29+
if len(params) == 0 {
30+
t.Fatal("url cannot be empty")
31+
}
32+
2633
tr := &http.Transport{
2734
TLSClientConfig: &tls.Config{
2835
InsecureSkipVerify: true,
2936
},
3037
}
3138
client := &http.Client{Transport: tr}
3239

33-
resp, err := client.Get(url)
40+
resp, err := client.Get(params[0])
3441
assert.NoError(t, err)
3542
defer resp.Body.Close()
3643

3744
body, ioerr := ioutil.ReadAll(resp.Body)
3845
assert.NoError(t, ioerr)
39-
assert.Equal(t, "it worked", string(body), "resp body should match")
46+
47+
var expected = "it worked"
48+
if len(params) > 1 {
49+
expected = params[1]
50+
}
51+
assert.Equal(t, expected, string(body), "resp body should match")
4052
assert.Equal(t, "200 OK", resp.Status, "should get a 200")
4153
}
4254

@@ -373,3 +385,33 @@ func testGetRequestHandler(t *testing.T, h http.Handler, url string) {
373385
assert.Equal(t, "it worked", w.Body.String(), "resp body should match")
374386
assert.Equal(t, 200, w.Code, "should get a 200")
375387
}
388+
389+
func TestRunDynamicRouting(t *testing.T) {
390+
router := New()
391+
router.GET("/aa/*xx", func(c *Context) { c.String(http.StatusOK, "/aa/*xx") })
392+
router.GET("/ab/*xx", func(c *Context) { c.String(http.StatusOK, "/ab/*xx") })
393+
router.GET("/", func(c *Context) { c.String(http.StatusOK, "home") })
394+
router.GET("/:cc", func(c *Context) { c.String(http.StatusOK, "/:cc") })
395+
router.GET("/:cc/cc", func(c *Context) { c.String(http.StatusOK, "/:cc/cc") })
396+
router.GET("/get/test/abc/", func(c *Context) { c.String(http.StatusOK, "/get/test/abc/") })
397+
router.GET("/get/:param/abc/", func(c *Context) { c.String(http.StatusOK, "/get/:param/abc/") })
398+
399+
ts := httptest.NewServer(router)
400+
defer ts.Close()
401+
402+
testRequest(t, ts.URL+"/", "home")
403+
testRequest(t, ts.URL+"/aa/aa", "/aa/*xx")
404+
testRequest(t, ts.URL+"/ab/ab", "/ab/*xx")
405+
testRequest(t, ts.URL+"/all", "/:cc")
406+
testRequest(t, ts.URL+"/all/cc", "/:cc/cc")
407+
testRequest(t, ts.URL+"/a/cc", "/:cc/cc")
408+
testRequest(t, ts.URL+"/a", "/:cc")
409+
testRequest(t, ts.URL+"/get/test/abc/", "/get/test/abc/")
410+
testRequest(t, ts.URL+"/get/te/abc/", "/get/:param/abc/")
411+
testRequest(t, ts.URL+"/get/xx/abc/", "/get/:param/abc/")
412+
testRequest(t, ts.URL+"/get/tt/abc/", "/get/:param/abc/")
413+
testRequest(t, ts.URL+"/get/a/abc/", "/get/:param/abc/")
414+
testRequest(t, ts.URL+"/get/t/abc/", "/get/:param/abc/")
415+
testRequest(t, ts.URL+"/get/aa/abc/", "/get/:param/abc/")
416+
testRequest(t, ts.URL+"/get/abas/abc/", "/get/:param/abc/")
417+
}

tree.go

+59-27
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,6 @@ type node struct {
118118
fullPath string
119119
}
120120

121-
type skip struct {
122-
path string
123-
paramNode *node
124-
}
125-
126121
// Increments priority of the given child and reorders if necessary
127122
func (n *node) incrementChildPrio(pos int) int {
128123
cs := n.children
@@ -405,7 +400,23 @@ type nodeValue struct {
405400
// made if a handle exists with an extra (without the) trailing slash for the
406401
// given path.
407402
func (n *node) getValue(path string, params *Params, unescape bool) (value nodeValue) {
408-
var skipped *skip
403+
// path: /abc/123/def
404+
// level 1 router:abc
405+
// level 2 router:123
406+
// level 3 router:def
407+
var (
408+
skippedPath string
409+
latestNode = n // not found `level 2 router` use latestNode
410+
411+
// match '/' count
412+
// matchNum < 1: `level 2 router` not found,the current node needs to be equal to latestNode
413+
// matchNum >= 1: `level (2 or 3 or 4 or ...) router`: Normal handling
414+
matchNum int // each match will accumulate
415+
)
416+
// if path = '/', no need to look for router
417+
if len(path) == 1 {
418+
matchNum = 1
419+
}
409420

410421
walk: // Outer loop for walking the tree
411422
for {
@@ -418,32 +429,41 @@ walk: // Outer loop for walking the tree
418429
idxc := path[0]
419430
for i, c := range []byte(n.indices) {
420431
if c == idxc {
421-
if strings.HasPrefix(n.children[len(n.children)-1].path, ":") {
422-
skipped = &skip{
423-
path: prefix + path,
424-
paramNode: &node{
425-
path: n.path,
426-
wildChild: n.wildChild,
427-
nType: n.nType,
428-
priority: n.priority,
429-
children: n.children,
430-
handlers: n.handlers,
431-
fullPath: n.fullPath,
432-
},
432+
// strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild
433+
if n.wildChild {
434+
skippedPath = prefix + path
435+
latestNode = &node{
436+
path: n.path,
437+
wildChild: n.wildChild,
438+
nType: n.nType,
439+
priority: n.priority,
440+
children: n.children,
441+
handlers: n.handlers,
442+
fullPath: n.fullPath,
433443
}
434444
}
435445

436446
n = n.children[i]
447+
448+
// match '/', If this condition is matched, the next route is found
449+
if len(n.fullPath) != 0 && n.wildChild {
450+
matchNum++
451+
}
437452
continue walk
438453
}
439454
}
440455

456+
// level 2 router not found,the current node needs to be equal to latestNode
457+
if matchNum < 1 {
458+
n = latestNode
459+
}
460+
441461
// If there is no wildcard pattern, recommend a redirection
442462
if !n.wildChild {
443463
// Nothing found.
444464
// We can recommend to redirect to the same URL without a
445465
// trailing slash if a leaf exists for that path.
446-
value.tsr = (path == "/" && n.handlers != nil)
466+
value.tsr = path == "/" && n.handlers != nil
447467
return
448468
}
449469

@@ -483,6 +503,16 @@ walk: // Outer loop for walking the tree
483503
if len(n.children) > 0 {
484504
path = path[end:]
485505
n = n.children[0]
506+
// next node,the latestNode needs to be equal to currentNode and handle next router
507+
latestNode = n
508+
// not found router in (level 1 router and handle next node),skipped cannot execute
509+
// example:
510+
// * /:cc/cc
511+
// call /a/cc expectations:match/200 Actual:match/200
512+
// call /a/dd expectations:unmatch/404 Actual: panic
513+
// call /addr/dd/aa expectations:unmatch/404 Actual: panic
514+
// skippedPath: It can only be executed if the secondary route is not found
515+
skippedPath = ""
486516
continue walk
487517
}
488518

@@ -533,8 +563,12 @@ walk: // Outer loop for walking the tree
533563
}
534564
}
535565
}
536-
566+
// path = n.path
537567
if path == prefix {
568+
// level 2 router not found and latestNode.wildChild is ture
569+
if matchNum < 1 && latestNode.wildChild {
570+
n = latestNode.children[len(latestNode.children)-1]
571+
}
538572
// We should have reached the node containing the handle.
539573
// Check if this node has a handle registered.
540574
if value.handlers = n.handlers; value.handlers != nil {
@@ -564,18 +598,16 @@ walk: // Outer loop for walking the tree
564598
return
565599
}
566600

567-
if path != "/" && skipped != nil && strings.HasSuffix(skipped.path, path) {
568-
path = skipped.path
569-
n = skipped.paramNode
570-
skipped = nil
601+
if path != "/" && strings.HasSuffix(skippedPath, path) {
602+
path = skippedPath
603+
n = latestNode
604+
skippedPath = ""
571605
continue walk
572606
}
573607

574608
// Nothing found. We can recommend to redirect to the same URL with an
575609
// extra trailing slash if a leaf exists for that path
576-
value.tsr = (path == "/") ||
577-
(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
578-
path == prefix[:len(prefix)-1] && n.handlers != nil)
610+
value.tsr = true
579611
return
580612
}
581613
}

tree_test.go

+24-2
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ func TestTreeWildcard(t *testing.T) {
154154
"/info/:user/public",
155155
"/info/:user/project/:project",
156156
"/info/:user/project/golang",
157+
"/aa/*xx",
158+
"/ab/*xx",
159+
"/:cc",
160+
"/:cc/cc",
161+
"/get/test/abc/",
162+
"/get/:param/abc/",
157163
}
158164
for _, route := range routes {
159165
tree.addRoute(route, fakeHandler(route))
@@ -186,6 +192,22 @@ func TestTreeWildcard(t *testing.T) {
186192
{"/info/gordon/public", false, "/info/:user/public", Params{Param{Key: "user", Value: "gordon"}}},
187193
{"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}},
188194
{"/info/gordon/project/golang", false, "/info/:user/project/golang", Params{Param{Key: "user", Value: "gordon"}}},
195+
{"/aa/aa", false, "/aa/*xx", Params{Param{Key: "xx", Value: "/aa"}}},
196+
{"/ab/ab", false, "/ab/*xx", Params{Param{Key: "xx", Value: "/ab"}}},
197+
{"/a", false, "/:cc", Params{Param{Key: "cc", Value: "a"}}},
198+
// * level 1 router match param will be Intercept first
199+
// new PR handle (/all /all/cc /a/cc)
200+
{"/all", false, "/:cc", Params{Param{Key: "cc", Value: "ll"}}},
201+
{"/all/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "ll"}}},
202+
{"/a/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: ""}}},
203+
{"/get/test/abc/", false, "/get/test/abc/", nil},
204+
{"/get/te/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "te"}}},
205+
{"/get/xx/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "xx"}}},
206+
{"/get/tt/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "tt"}}},
207+
{"/get/a/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "a"}}},
208+
{"/get/t/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "t"}}},
209+
{"/get/aa/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "aa"}}},
210+
{"/get/abas/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "abas"}}},
189211
})
190212

191213
checkPriorities(t, tree)
@@ -565,8 +587,8 @@ func TestTreeFindCaseInsensitivePath(t *testing.T) {
565587
"/u/öpfêl",
566588
"/v/Äpfêl/",
567589
"/v/Öpfêl",
568-
"/w/♬", // 3 byte
569-
"/w/♭/", // 3 byte, last byte differs
590+
"/w/♬", // 3 byte
591+
"/w/♭/", // 3 byte, last byte differs
570592
"/w/𠜎", // 4 byte
571593
"/w/𠜏/", // 4 byte
572594
longPath,

0 commit comments

Comments
 (0)