Skip to content

Commit f905dab

Browse files
author
Sergio Andrés Virviescas Santana
authored
Align features with julienschmidt/httprouter (#18)
* Align features with julienschmidt/httprouter * Add extra functionalities * Tests refactor * Add go.1.13 support
1 parent 8c72a81 commit f905dab

File tree

12 files changed

+1208
-1442
lines changed

12 files changed

+1208
-1442
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go:
55
- 1.10.x
66
- 1.11.x
77
- 1.12.x
8+
- 1.13.x
89
- tip
910

1011
os:

examples/auth/auth.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"log"
77
"strings"
88

9-
"github.com/elithrar/simple-scrypt"
9+
scrypt "github.com/elithrar/simple-scrypt"
1010
"github.com/fasthttp/router"
1111
"github.com/valyala/fasthttp"
1212
)
@@ -43,7 +43,7 @@ func parseBasicAuth(auth string) (username, password string, ok bool) {
4343

4444
// BasicAuth is the basic auth handler
4545
func BasicAuth(h fasthttp.RequestHandler, requiredUser string, requiredPasswordHash []byte) fasthttp.RequestHandler {
46-
return fasthttp.RequestHandler(func(ctx *fasthttp.RequestCtx) {
46+
return func(ctx *fasthttp.RequestCtx) {
4747
// Get the Basic Authentication credentials
4848
user, password, hasAuth := basicAuth(ctx)
4949

@@ -77,7 +77,7 @@ func BasicAuth(h fasthttp.RequestHandler, requiredUser string, requiredPasswordH
7777
// Request Basic Authentication otherwise
7878
ctx.Error(fasthttp.StatusMessage(fasthttp.StatusUnauthorized), fasthttp.StatusUnauthorized)
7979
ctx.Response.Header.Set("WWW-Authenticate", "Basic realm=Restricted")
80-
})
80+
}
8181
}
8282

8383
// Index is the index handler

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/fasthttp/router
33
go 1.13
44

55
require (
6-
github.com/savsgio/gotils v0.0.0-20190925070755-524bc4f47500
6+
github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f
77
github.com/valyala/bytebufferpool v1.0.0
88
github.com/valyala/fasthttp v1.8.0
99
)

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ github.com/klauspost/compress v1.8.2 h1:Bx0qjetmNjdFXASH02NSAREKpiaDwkO1DRZ3dV2K
22
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
33
github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
44
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
5-
github.com/savsgio/gotils v0.0.0-20190925070755-524bc4f47500 h1:9Pi10H7E8E79/x2HSe1FmMGd7BJ1WAqDKzwjpv+ojFg=
6-
github.com/savsgio/gotils v0.0.0-20190925070755-524bc4f47500/go.mod h1:lHhJedqxCoHN+zMtwGNTXWmF0u9Jt363FYRhV6g0CdY=
5+
github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f h1:PgA+Olipyj258EIEYnpFFONrrCcAIWNUNoFhUfMqAGY=
6+
github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f/go.mod h1:lHhJedqxCoHN+zMtwGNTXWmF0u9Jt363FYRhV6g0CdY=
77
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
88
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
99
github.com/valyala/fasthttp v1.8.0 h1:actnGGBYtGQmxVaZxyZpp57Vcc2NhcO7mMN0IMwCC0w=

path.go

Lines changed: 100 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -7,50 +7,14 @@ package router
77

88
import (
99
"strings"
10-
"sync"
1110

1211
"github.com/savsgio/gotils"
1312
)
1413

15-
type cleanPathBuffer struct {
16-
n int
17-
r int
18-
w int
19-
trailing bool
20-
buf []byte
21-
}
22-
23-
var cleanPathBufferPool = sync.Pool{
24-
New: func() interface{} {
25-
return &cleanPathBuffer{
26-
n: 0,
27-
r: 0,
28-
w: 1,
29-
trailing: false,
30-
buf: make([]byte, 140),
31-
}
32-
},
33-
}
34-
35-
func (cpb *cleanPathBuffer) reset() {
36-
cpb.n = 0
37-
cpb.r = 0
38-
cpb.w = 1
39-
cpb.trailing = false
40-
// cpb.buf = cpb.buf[:0]
41-
}
42-
43-
func acquireCleanPathBuffer() *cleanPathBuffer {
44-
return cleanPathBufferPool.Get().(*cleanPathBuffer)
45-
}
46-
47-
func releaseCleanPathBuffer(cpb *cleanPathBuffer) {
48-
cpb.reset()
49-
cleanPathBufferPool.Put(cpb)
50-
}
14+
const stackBufSize = 128
5115

5216
// CleanPath is the URL version of path.Clean, it returns a canonical URL path
53-
// for path, eliminating . and .. elements.
17+
// for p, eliminating . and .. elements.
5418
//
5519
// The following rules are applied iteratively until no further processing can
5620
// be done:
@@ -62,86 +26,133 @@ func releaseCleanPathBuffer(cpb *cleanPathBuffer) {
6226
// that is, replace "/.." by "/" at the beginning of a path.
6327
//
6428
// If the result of this process is an empty string, "/" is returned
65-
func CleanPath(path string) string {
66-
cpb := acquireCleanPathBuffer()
67-
cleanPathWithBuffer(cpb, path)
29+
func CleanPath(p string) string {
30+
// Turn empty string into "/"
31+
if p == "" {
32+
return "/"
33+
}
6834

69-
s := string(cpb.buf)
70-
releaseCleanPathBuffer(cpb)
35+
// Reasonably sized buffer on stack to avoid allocations in the common case.
36+
// If a larger buffer is required, it gets allocated dynamically.
37+
buf := make([]byte, 0, stackBufSize)
7138

72-
return s
73-
}
39+
n := len(p)
7440

75-
func cleanPathWithBuffer(cpb *cleanPathBuffer, path string) {
76-
// Turn empty string into "/"
77-
if path == "" {
78-
cpb.buf = append(cpb.buf[:0], '/')
79-
return
80-
}
41+
// Invariants:
42+
// reading from path; r is index of next byte to process.
43+
// writing to buf; w is index of next byte to write.
8144

82-
cpb.n = len(path)
83-
cpb.buf = gotils.ExtendByteSlice(cpb.buf, len(path)+1)
84-
cpb.buf[0] = '/'
45+
// path must start with '/'
46+
r := 1
47+
w := 1
8548

86-
cpb.trailing = cpb.n > 2 && path[cpb.n-1] == '/'
49+
if p[0] != '/' {
50+
r = 0
51+
52+
if n+1 > stackBufSize {
53+
buf = make([]byte, n+1)
54+
} else {
55+
buf = buf[:n+1]
56+
}
57+
buf[0] = '/'
58+
}
59+
60+
trailing := n > 1 && p[n-1] == '/'
8761

8862
// A bit more clunky without a 'lazybuf' like the path package, but the loop
89-
// gets completely inlined (bufApp). So in contrast to the path package this
90-
// loop has no expensive function calls (except 1x make)
63+
// gets completely inlined (bufApp calls).
64+
// So in contrast to the path package this loop has no expensive function
65+
// calls (except make, if needed).
9166

92-
for cpb.r < cpb.n {
93-
// println(path[:cpb.r], " ####### ", string(path[cpb.r]), " ####### ", string(cpb.buf))
67+
for r < n {
9468
switch {
95-
case path[cpb.r] == '/':
69+
case p[r] == '/':
9670
// empty path element, trailing slash is added after the end
97-
cpb.r++
71+
r++
9872

99-
case path[cpb.r] == '.' && cpb.r+1 == cpb.n:
100-
cpb.trailing = true
101-
cpb.r++
73+
case p[r] == '.' && r+1 == n:
74+
trailing = true
75+
r++
10276

103-
case path[cpb.r] == '.' && path[cpb.r+1] == '/':
77+
case p[r] == '.' && p[r+1] == '/':
10478
// . element
105-
cpb.r++
79+
r += 2
10680

107-
case path[cpb.r] == '.' && path[cpb.r+1] == '.' && (cpb.r+2 == cpb.n || path[cpb.r+2] == '/'):
81+
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
10882
// .. element: remove to last /
109-
cpb.r += 2
83+
r += 3
11084

111-
if cpb.w > 1 {
85+
if w > 1 {
11286
// can backtrack
113-
cpb.w--
114-
115-
for cpb.w > 1 && cpb.buf[cpb.w] != '/' {
116-
cpb.w--
87+
w--
88+
89+
if len(buf) == 0 {
90+
for w > 1 && p[w] != '/' {
91+
w--
92+
}
93+
} else {
94+
for w > 1 && buf[w] != '/' {
95+
w--
96+
}
11797
}
118-
11998
}
12099

121100
default:
122-
// real path element.
123-
// add slash if needed
124-
if cpb.w > 1 {
125-
cpb.buf[cpb.w] = '/'
126-
cpb.w++
101+
// Real path element.
102+
// Add slash if needed
103+
if w > 1 {
104+
bufApp(&buf, p, w, '/')
105+
w++
127106
}
128107

129-
// copy element
130-
for cpb.r < cpb.n && path[cpb.r] != '/' {
131-
cpb.buf[cpb.w] = path[cpb.r]
132-
cpb.w++
133-
cpb.r++
108+
// Copy element
109+
for r < n && p[r] != '/' {
110+
bufApp(&buf, p, w, p[r])
111+
w++
112+
r++
134113
}
135114
}
136115
}
137116

138-
// re-append trailing slash
139-
if cpb.trailing && cpb.w > 1 {
140-
cpb.buf[cpb.w] = '/'
141-
cpb.w++
117+
// Re-append trailing slash
118+
if trailing && w > 1 {
119+
bufApp(&buf, p, w, '/')
120+
w++
121+
}
122+
123+
// If the original string was not modified (or only shortened at the end),
124+
// return the respective substring of the original string.
125+
// Otherwise return a new string from the buffer.
126+
if len(buf) == 0 {
127+
return p[:w]
142128
}
129+
return string(buf[:w])
130+
}
131+
132+
// Internal helper to lazily create a buffer if necessary.
133+
// Calls to this function get inlined.
134+
func bufApp(buf *[]byte, s string, w int, c byte) {
135+
b := *buf
136+
if len(b) == 0 {
137+
// No modification of the original string so far.
138+
// If the next character is the same as in the original string, we do
139+
// not yet have to allocate a buffer.
140+
if s[w] == c {
141+
return
142+
}
143143

144-
cpb.buf = cpb.buf[:cpb.w]
144+
// Otherwise use either the stack buffer, if it is large enough, or
145+
// allocate a new buffer on the heap, and copy all previous characters.
146+
if l := len(s); l > cap(b) {
147+
*buf = make([]byte, len(s))
148+
} else {
149+
*buf = (*buf)[:l]
150+
}
151+
b = *buf
152+
153+
copy(b, s[:w])
154+
}
155+
b[w] = c
145156
}
146157

147158
// returns all possible paths when the original path has optional arguments

0 commit comments

Comments
 (0)