@@ -7,50 +7,14 @@ package router
7
7
8
8
import (
9
9
"strings"
10
- "sync"
11
10
12
11
"github.com/savsgio/gotils"
13
12
)
14
13
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
51
15
52
16
// 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.
54
18
//
55
19
// The following rules are applied iteratively until no further processing can
56
20
// be done:
@@ -62,86 +26,133 @@ func releaseCleanPathBuffer(cpb *cleanPathBuffer) {
62
26
// that is, replace "/.." by "/" at the beginning of a path.
63
27
//
64
28
// 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
+ }
68
34
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 )
71
38
72
- return s
73
- }
39
+ n := len (p )
74
40
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.
81
44
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
85
48
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 ] == '/'
87
61
88
62
// 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).
91
66
92
- for cpb .r < cpb .n {
93
- // println(path[:cpb.r], " ####### ", string(path[cpb.r]), " ####### ", string(cpb.buf))
67
+ for r < n {
94
68
switch {
95
- case path [ cpb . r ] == '/' :
69
+ case p [ r ] == '/' :
96
70
// empty path element, trailing slash is added after the end
97
- cpb . r ++
71
+ r ++
98
72
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 ++
102
76
103
- case path [ cpb . r ] == '.' && path [ cpb . r + 1 ] == '/' :
77
+ case p [ r ] == '.' && p [ r + 1 ] == '/' :
104
78
// . element
105
- cpb . r ++
79
+ r += 2
106
80
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 ] == '/' ):
108
82
// .. element: remove to last /
109
- cpb . r += 2
83
+ r += 3
110
84
111
- if cpb . w > 1 {
85
+ if w > 1 {
112
86
// 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
+ }
117
97
}
118
-
119
98
}
120
99
121
100
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 ++
127
106
}
128
107
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 ++
134
113
}
135
114
}
136
115
}
137
116
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 ]
142
128
}
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
+ }
143
143
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
145
156
}
146
157
147
158
// returns all possible paths when the original path has optional arguments
0 commit comments