Skip to content

Commit 682d637

Browse files
Emmanuel T Odekerakyll
authored andcommitted
handler: use longest prefix, shortest subpath matches to query if no route is found (#19)
Fixes #18 If any pathConfig is the shortest prefix of a query, find and return it.
1 parent 5ad859d commit 682d637

File tree

2 files changed

+68
-1
lines changed

2 files changed

+68
-1
lines changed

handler.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ func (pset pathConfigSet) Swap(i, j int) {
185185
}
186186

187187
func (pset pathConfigSet) find(path string) (pc *pathConfig, subpath string) {
188+
// Fast path with binary search to retrieve exact matches
189+
// e.g. given pset ["/", "/abc", "/xyz"], path "/def" won't match.
188190
i := sort.Search(len(pset), func(i int) bool {
189191
return pset[i].path >= path
190192
})
@@ -194,5 +196,30 @@ func (pset pathConfigSet) find(path string) (pc *pathConfig, subpath string) {
194196
if i > 0 && strings.HasPrefix(path, pset[i-1].path+"/") {
195197
return &pset[i-1], path[len(pset[i-1].path)+1:]
196198
}
197-
return nil, ""
199+
200+
// Slow path, now looking for the longest prefix/shortest subpath i.e.
201+
// e.g. given pset ["/", "/abc/", "/abc/def/", "/xyz"/]
202+
// * query "/abc/foo" returns "/abc/" with a subpath of "foo"
203+
// * query "/x" returns "/" with a subpath of "x"
204+
lenShortestSubpath := len(path)
205+
var bestMatchConfig *pathConfig
206+
207+
// After binary search with the >= lexicographic comparison,
208+
// nothing greater than i will be a prefix of path.
209+
max := i
210+
for i := 0; i < max; i++ {
211+
ps := pset[i]
212+
if len(ps.path) >= len(path) {
213+
// We previously didn't find the path by search, so any
214+
// route with equal or greater length is NOT a match.
215+
continue
216+
}
217+
sSubpath := strings.TrimPrefix(path, ps.path)
218+
if len(sSubpath) < lenShortestSubpath {
219+
subpath = sSubpath
220+
lenShortestSubpath = len(sSubpath)
221+
bestMatchConfig = &pset[i]
222+
}
223+
}
224+
return bestMatchConfig, subpath
198225
}

handler_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,46 @@ func TestPathConfigSetFind(t *testing.T) {
207207
want: "/portmidi",
208208
subpath: "foo",
209209
},
210+
{
211+
paths: []string{"/example/helloworld", "/", "/y", "/foo"},
212+
query: "/x",
213+
want: "/",
214+
subpath: "x",
215+
},
216+
{
217+
paths: []string{"/example/helloworld", "/", "/y", "/foo"},
218+
query: "/",
219+
want: "/",
220+
subpath: "",
221+
},
222+
{
223+
paths: []string{"/example/helloworld", "/", "/y", "/foo"},
224+
query: "/example",
225+
want: "/",
226+
subpath: "example",
227+
},
228+
{
229+
paths: []string{"/example/helloworld", "/", "/y", "/foo"},
230+
query: "/example/foo",
231+
want: "/",
232+
subpath: "example/foo",
233+
},
234+
{
235+
paths: []string{"/example/helloworld", "/", "/y", "/foo"},
236+
query: "/y",
237+
want: "/y",
238+
},
239+
{
240+
paths: []string{"/example/helloworld", "/", "/y", "/foo"},
241+
query: "/x/y/",
242+
want: "/",
243+
subpath: "x/y/",
244+
},
245+
{
246+
paths: []string{"/example/helloworld", "/y", "/foo"},
247+
query: "/x",
248+
want: "",
249+
},
210250
}
211251
emptyToNil := func(s string) string {
212252
if s == "" {

0 commit comments

Comments
 (0)