Skip to content

Commit 00e0459

Browse files
author
Sergio Andres Virviescas Santana
committed
Squashed commit of the following:
commit 64f9917f6c5636b301f43a8a8c8a9de0e8c0f8b5 Author: Sergio Andres Virviescas Santana <[email protected]> Date: Sun Dec 30 22:29:15 2018 +0100 General fixes commit fefd45ee1d266fe0cc7be8a2e501e6943260ddc4 Author: Sergio Andres Virviescas Santana <[email protected]> Date: Sun Dec 30 22:16:22 2018 +0100 Fixes commit 42801773c2cb92038a61e99e7eeef0964bc4cada Author: Sergio Andres Virviescas Santana <[email protected]> Date: Fri Dec 28 23:27:56 2018 +0100 Remove unsed function commit 4c0e664a2073394262d51f50e45376a3e478ffe0 Author: Sergio Andres Virviescas Santana <[email protected]> Date: Fri Dec 28 23:23:22 2018 +0100 Added BenchmarkRouterRedirectTrailingSlash commit c07cc54d47e19fae4229d3f2205e806ebe258cb0 Author: Sergio Andres Virviescas Santana <[email protected]> Date: Fri Dec 28 23:18:50 2018 +0100 Fixes to non break API commit f0cd9995c478278aa3d7992d46192ee98fd8add8 Author: Sergio Andres Virviescas Santana <[email protected]> Date: Wed Dec 26 18:05:08 2018 +0100 Reduce extra allocations to 0 commit fe4e3adf60a99314df7e117c6d14d091e88674f8 Author: Sergio Andres Virviescas Santana <[email protected]> Date: Wed Dec 26 17:15:20 2018 +0100 Reduce 1 alloc and a insignificant time commit 57a058e3c74d4ea514c1154e2423665e2f4e8183 Author: Sergio Andres Virviescas Santana <[email protected]> Date: Wed Dec 26 16:15:59 2018 +0100 Avoided defer releaseCleanPathBuffer commit 42a3f75dc63fb7be02a58bbe11c21505540f714a Author: Sergio Andres Virviescas Santana <[email protected]> Date: Wed Dec 26 15:48:03 2018 +0100 Added some improvements
1 parent 495ecaf commit 00e0459

File tree

12 files changed

+242
-127
lines changed

12 files changed

+242
-127
lines changed

LICENSE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
Copyright (c) 2015-2016, 招牌疯子
2+
Copyright (c) 2018-present Sergio Andres Virviescas Santana, fasthttp
3+
24
All rights reserved.
35

46
Redistribution and use in source and binary forms, with or without

README.md

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,10 @@ This router is optimized for high performance and a small memory footprint. It s
1818
- I fork this repo is just because there is no router for `fasthttp` at that time. And `router` is the FIRST router for `fasthttp`.
1919
- `router` has been used in my online production and processes 17 million requests per day. It is fast and stable, so I decide to release a stable version.
2020

21-
#### Releases
22-
23-
- [2016.10.24] [v0.1.0](https://github.com/fasthttp/router/releases/tag/v0.1.0) The first release version of `router`.
2421

2522
## Features
2623

27-
**Best Performance:** FastHttpRouter is **one of the fastest** go web frameworks in the [go-web-framework-benchmark](https://github.com/smallnest/go-web-framework-benchmark). Even faster than httprouter itself.
24+
**Best Performance:** Router is **one of the fastest** go web frameworks in the [go-web-framework-benchmark](https://github.com/smallnest/go-web-framework-benchmark). Even faster than httprouter itself.
2825

2926
- Basic Test: The first test case is to mock 0 ms, 10 ms, 100 ms, 500 ms processing time in handlers. The concurrency clients are 5000.
3027

@@ -52,7 +49,7 @@ If you don't like it, you can [turn off this behavior](http://godoc.org/github.c
5249
slash at no extra cost, the router can also fix wrong cases and remove
5350
superfluous path elements (like `../` or `//`).
5451
Is [CAPTAIN CAPS LOCK](http://www.urbandictionary.com/define.php?term=Captain+Caps+Lock) one of your users?
55-
FastHttpRouter can help him by making a case-insensitive look-up and redirecting him
52+
Router can help him by making a case-insensitive look-up and redirecting him
5653
to the correct URL.
5754

5855
**Parameters in your routing pattern:** Stop parsing the requested URL path,
@@ -92,19 +89,19 @@ import (
9289
)
9390

9491
func Index(ctx *fasthttp.RequestCtx) {
95-
fmt.Fprint(ctx, "Welcome!\n")
92+
ctx.WriteString("Welcome!")
9693
}
9794

9895
func Hello(ctx *fasthttp.RequestCtx) {
99-
fmt.Fprintf(ctx, "hello, %s!\n", ctx.UserValue("name"))
96+
fmt.Fprintf(ctx, "Hello, %s!\n", ctx.UserValue("name"))
10097
}
10198

10299
func main() {
103-
router := router.New()
104-
router.GET("/", Index)
105-
router.GET("/hello/:name", Hello)
100+
r := router.New()
101+
r.GET("/", Index)
102+
r.GET("/hello/:name", Hello)
106103

107-
log.Fatal(fasthttp.ListenAndServe(":8080", router.Handler))
104+
log.Fatal(fasthttp.ListenAndServe(":8080", r.Handler))
108105
}
109106
```
110107

@@ -180,9 +177,9 @@ For even better scalability, the child nodes on each tree level are ordered by p
180177

181178
Because fasthttp doesn't provide http.Handler. See this [description](https://github.com/valyala/fasthttp#switching-from-nethttp-to-fasthttp).
182179

183-
Fasthttp works with [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler) functions instead of objects implementing Handler interface. So a FastHttpRouter provides a [Handler](https://godoc.org/github.com/fasthttp/router#Router.Handler) interface to implement the fasthttp.ListenAndServe interface.
180+
Fasthttp works with [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler) functions instead of objects implementing Handler interface. So a Router provides a [Handler](https://godoc.org/github.com/fasthttp/router#Router.Handler) interface to implement the fasthttp.ListenAndServe interface.
184181

185-
Just try it out for yourself, the usage of FastHttpRouter is very straightforward. The package is compact and minimalistic, but also probably one of the easiest routers to set up.
182+
Just try it out for yourself, the usage of Router is very straightforward. The package is compact and minimalistic, but also probably one of the easiest routers to set up.
186183

187184
## Where can I find Middleware *X*?
188185

@@ -204,13 +201,13 @@ The `NotFound` handler can for example be used to serve static files from the ro
204201

205202
```go
206203
// Serve static files from the ./public directory
207-
router.NotFound = fasthttp.FSHandler("./public", 0)
204+
r.NotFound = fasthttp.FSHandler("./public", 0)
208205
```
209206

210207
But this approach sidesteps the strict core rules of this router to avoid routing problems. A cleaner approach is to use a distinct sub-path for serving files, like `/static/*filepath` or `/files/*filepath`.
211208

212-
## Web Frameworks based on FastHttpRouter
209+
## Web Frameworks based on Router
213210

214211
If the HttpRouter is a bit too minimalistic for you, you might try one of the following more high-level 3rd-party web frameworks building upon the HttpRouter package:
215212

216-
- Waiting for you to do this...
213+
- [Atreugo](https://github.com/savsgio/atreugo)

examples/auth/auth.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ func main() {
7171
user := "gordon"
7272
pass := "secret!"
7373

74-
router := router.New()
75-
router.GET("/", Index)
76-
router.GET("/protected/", BasicAuth(Protected, user, pass))
74+
r := router.New()
75+
r.GET("/", Index)
76+
r.GET("/protected/", BasicAuth(Protected, user, pass))
7777

78-
log.Fatal(fasthttp.ListenAndServe(":8080", router.Handler))
78+
log.Fatal(fasthttp.ListenAndServe(":8080", r.Handler))
7979
}

examples/basic/basic.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ func QueryArgs(ctx *fasthttp.RequestCtx) {
3232
}
3333

3434
func main() {
35-
router := router.New()
36-
router.GET("/", Index)
37-
router.GET("/hello/:name", Hello)
38-
router.GET("/multi/:name/:word", MultiParams)
39-
router.GET("/ping", QueryArgs)
35+
r := router.New()
36+
r.GET("/", Index)
37+
r.GET("/hello/:name", Hello)
38+
r.GET("/multi/:name/:word", MultiParams)
39+
r.GET("/ping", QueryArgs)
4040

41-
log.Fatal(fasthttp.ListenAndServe(":8080", router.Handler))
41+
log.Fatal(fasthttp.ListenAndServe(":8080", r.Handler))
4242
}

examples/hosts/hosts.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ func (hs HostSwitch) CheckHost(ctx *fasthttp.RequestCtx) {
3737

3838
func main() {
3939
// Initialize a router as usual
40-
router := router.New()
41-
router.GET("/", Index)
42-
router.GET("/hello/:name", Hello)
40+
r := router.New()
41+
r.GET("/", Index)
42+
r.GET("/hello/:name", Hello)
4343

4444
// Make a new HostSwitch and insert the router (our http handler)
4545
// for example.com and port 12345
4646
hs := make(HostSwitch)
47-
hs["example.com:12345"] = router.Handler
47+
hs["example.com:12345"] = r.Handler
4848

4949
// Use the HostSwitch to listen and serve on port 12345
5050
log.Fatal(fasthttp.ListenAndServe(":12345", hs.CheckHost))

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ module github.com/fasthttp/router
22

33
require (
44
github.com/savsgio/gotils v0.0.0-20181102112957-636156ff2e0e
5+
github.com/valyala/bytebufferpool v1.0.0
56
github.com/valyala/fasthttp v1.0.0
67
)

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ github.com/klauspost/compress v1.4.0 h1:8nsMz3tWa9SWWPL60G1V6CUsf4lLjWLTNEtibhe8
22
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
33
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e h1:+lIPJOWl+jSiJOc70QXJ07+2eg2Jy2EC7Mi11BWujeM=
44
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
5+
github.com/savsgio/gotils v0.0.0-20181102112957-636156ff2e0e h1:OaPrIuz2RcPhWlSWeV8mYrUzU3SNTIZAzkjhGfCU7bw=
56
github.com/savsgio/gotils v0.0.0-20181102112957-636156ff2e0e/go.mod h1:w803/Fg1m0hrp1ZT9KNfQe4E4+WOMMFLcgzPvOcye10=
67
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
78
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=

path.go

Lines changed: 89 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,51 @@
55

66
package router
77

8+
import (
9+
"sync"
10+
11+
"github.com/savsgio/gotils"
12+
)
13+
14+
type cleanPathBuffer struct {
15+
n int
16+
r int
17+
w int
18+
trailing bool
19+
buf []byte
20+
}
21+
22+
var cleanPathBufferPool = sync.Pool{
23+
New: func() interface{} {
24+
return &cleanPathBuffer{
25+
n: 0,
26+
r: 0,
27+
w: 1,
28+
trailing: false,
29+
buf: make([]byte, 140),
30+
}
31+
},
32+
}
33+
34+
func (cpb *cleanPathBuffer) reset() {
35+
cpb.n = 0
36+
cpb.r = 0
37+
cpb.w = 1
38+
cpb.trailing = false
39+
// cpb.buf = cpb.buf[:0]
40+
}
41+
42+
func acquireCleanPathBuffer() *cleanPathBuffer {
43+
return cleanPathBufferPool.Get().(*cleanPathBuffer)
44+
}
45+
46+
func releaseCleanPathBuffer(cpb *cleanPathBuffer) {
47+
cpb.reset()
48+
cleanPathBufferPool.Put(cpb)
49+
}
50+
851
// CleanPath is the URL version of path.Clean, it returns a canonical URL path
9-
// for p, eliminating . and .. elements.
52+
// for path, eliminating . and .. elements.
1053
//
1154
// The following rules are applied iteratively until no further processing can
1255
// be done:
@@ -18,106 +61,84 @@ package router
1861
// that is, replace "/.." by "/" at the beginning of a path.
1962
//
2063
// If the result of this process is an empty string, "/" is returned
21-
func CleanPath(p string) string {
22-
// Turn empty string into "/"
23-
if p == "" {
24-
return "/"
25-
}
64+
func CleanPath(path string) string {
65+
cpb := acquireCleanPathBuffer()
66+
cleanPathWithBuffer(cpb, path)
2667

27-
n := len(p)
28-
var buf []byte
68+
s := string(cpb.buf)
69+
releaseCleanPathBuffer(cpb)
2970

30-
// Invariants:
31-
// reading from path; r is index of next byte to process.
32-
// writing to buf; w is index of next byte to write.
33-
34-
// path must start with '/'
35-
r := 1
36-
w := 1
71+
return s
72+
}
3773

38-
if p[0] != '/' {
39-
r = 0
40-
buf = make([]byte, n+1)
41-
buf[0] = '/'
74+
func cleanPathWithBuffer(cpb *cleanPathBuffer, path string) {
75+
// Turn empty string into "/"
76+
if path == "" {
77+
cpb.buf = append(cpb.buf[:0], '/')
78+
return
4279
}
4380

44-
trailing := n > 2 && p[n-1] == '/'
81+
cpb.n = len(path)
82+
cpb.buf = gotils.ExtendByteSlice(cpb.buf, len(path)+1)
83+
cpb.buf[0] = '/'
84+
85+
cpb.trailing = cpb.n > 2 && path[cpb.n-1] == '/'
4586

4687
// A bit more clunky without a 'lazybuf' like the path package, but the loop
4788
// gets completely inlined (bufApp). So in contrast to the path package this
4889
// loop has no expensive function calls (except 1x make)
4990

50-
for r < n {
91+
for cpb.r < cpb.n {
92+
// println(path[:cpb.r], " ####### ", string(path[cpb.r]), " ####### ", string(cpb.buf))
5193
switch {
52-
case p[r] == '/':
94+
case path[cpb.r] == '/':
5395
// empty path element, trailing slash is added after the end
54-
r++
96+
cpb.r++
5597

56-
case p[r] == '.' && r+1 == n:
57-
trailing = true
58-
r++
98+
case path[cpb.r] == '.' && cpb.r+1 == cpb.n:
99+
cpb.trailing = true
100+
cpb.r++
59101

60-
case p[r] == '.' && p[r+1] == '/':
102+
case path[cpb.r] == '.' && path[cpb.r+1] == '/':
61103
// . element
62-
r++
104+
cpb.r++
63105

64-
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
106+
case path[cpb.r] == '.' && path[cpb.r+1] == '.' && (cpb.r+2 == cpb.n || path[cpb.r+2] == '/'):
65107
// .. element: remove to last /
66-
r += 2
108+
cpb.r += 2
67109

68-
if w > 1 {
110+
if cpb.w > 1 {
69111
// can backtrack
70-
w--
71-
72-
if buf == nil {
73-
for w > 1 && p[w] != '/' {
74-
w--
75-
}
76-
} else {
77-
for w > 1 && buf[w] != '/' {
78-
w--
79-
}
112+
cpb.w--
113+
114+
for cpb.w > 1 && cpb.buf[cpb.w] != '/' {
115+
cpb.w--
80116
}
117+
81118
}
82119

83120
default:
84121
// real path element.
85122
// add slash if needed
86-
if w > 1 {
87-
bufApp(&buf, p, w, '/')
88-
w++
123+
if cpb.w > 1 {
124+
cpb.buf[cpb.w] = '/'
125+
cpb.w++
89126
}
90127

91128
// copy element
92-
for r < n && p[r] != '/' {
93-
bufApp(&buf, p, w, p[r])
94-
w++
95-
r++
129+
for cpb.r < cpb.n && path[cpb.r] != '/' {
130+
cpb.buf[cpb.w] = path[cpb.r]
131+
cpb.w++
132+
cpb.r++
96133
}
97134
}
98135
}
99136

100137
// re-append trailing slash
101-
if trailing && w > 1 {
102-
bufApp(&buf, p, w, '/')
103-
w++
138+
if cpb.trailing && cpb.w > 1 {
139+
cpb.buf[cpb.w] = '/'
140+
cpb.w++
104141
}
105142

106-
if buf == nil {
107-
return p[:w]
108-
}
109-
return string(buf[:w])
110-
}
111-
112-
// internal helper to lazily create a buffer if necessary
113-
func bufApp(buf *[]byte, s string, w int, c byte) {
114-
if *buf == nil {
115-
if s[w] == c {
116-
return
117-
}
118-
119-
*buf = make([]byte, len(s))
120-
copy(*buf, s[:w])
121-
}
122-
(*buf)[w] = c
143+
cpb.buf = cpb.buf[:cpb.w]
123144
}

path_test.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ var cleanTests = []struct {
6666
func TestPathClean(t *testing.T) {
6767
for _, test := range cleanTests {
6868
if s := CleanPath(test.path); s != test.result {
69-
t.Errorf("CleanPath(%q) = %q, want %q", test.path, s, test.result)
69+
t.Errorf("CleanPath(%s) = %s, want %s", test.path, s, test.result)
7070
}
7171
if s := CleanPath(test.result); s != test.result {
72-
t.Errorf("CleanPath(%q) = %q, want %q", test.result, s, test.result)
72+
t.Errorf("CleanPath(%s) = %s, want %s", test.result, s, test.result)
7373
}
7474
}
7575
}
@@ -90,3 +90,21 @@ func TestPathCleanMallocs(t *testing.T) {
9090
}
9191
}
9292
}
93+
94+
func BenchmarkCleanPathWithBuffer(b *testing.B) {
95+
path := "/../bench/"
96+
cpb := acquireCleanPathBuffer()
97+
98+
for i := 0; i < b.N; i++ {
99+
cleanPathWithBuffer(cpb, path)
100+
cpb.reset()
101+
}
102+
}
103+
104+
func BenchmarkCleanPath(b *testing.B) {
105+
path := "/../bench/"
106+
107+
for i := 0; i < b.N; i++ {
108+
CleanPath(path)
109+
}
110+
}

0 commit comments

Comments
 (0)