Skip to content

Commit 8e0e37e

Browse files
committed
Initial commit
0 parents  commit 8e0e37e

File tree

11 files changed

+553
-0
lines changed

11 files changed

+553
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.idea
2+
.DS_Store
3+
tags
4+
coverage.out

.hound.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
go:
2+
enabled: true

.travis.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
language: go
2+
go:
3+
- 1.4
4+
- 1.5
5+
- 1.6
6+
- tip
7+
before_install:
8+
- go get github.com/modocache/gover
9+
- go get github.com/axw/gocov/gocov
10+
- go get github.com/mattn/goveralls
11+
- go get golang.org/x/tools/cmd/cover
12+
script:
13+
- go test -v -cover -race -coverprofile=coverage.out
14+
- $HOME/gopath/bin/gover
15+
after_script:
16+
- goveralls -coverprofile=coverage.out -service=travis-ci -repotoken='SRBCsblTmbxSlR6cR6APELIJUWMlN47CA'

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2015-2016 Carlos Alexandro Becker
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Vardius - goapi
2+
================
3+
[![Build Status](https://travis-ci.org/Vardius/goapi.svg?branch=master)](https://travis-ci.org/Vardius/goapi) [![](https://godoc.org/github.com/vardius/goapi?status.svg)](http://godoc.org/github.com/vardius/goapi) [![Coverage Status](https://coveralls.io/repos/github/Vardius/goapi/badge.svg?branch=master)](https://coveralls.io/github/Vardius/goapi?branch=master)
4+
5+
The fastest Go API micro framwework, HTTP request router, multiplexer, mux.
6+
7+
ABOUT
8+
==================================================
9+
Contributors:
10+
11+
* [Rafał Lorenz](http://rafallorenz.com)
12+
13+
Want to contribute ? Feel free to send pull requests!
14+
15+
Have problems, bugs, feature ideas?
16+
We are using the github [issue tracker](https://github.com/vardius/goapi/issues) to manage them.
17+
18+
HOW TO USE
19+
==================================================
20+
21+
22+
License
23+
-------
24+
25+
This package is released under the MIT license. See the complete license in the package:
26+
27+
[LICENSE](LICENSE.md)

middleware.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package goapi
2+
3+
import (
4+
"net/http"
5+
"sort"
6+
)
7+
8+
type (
9+
middlewares []*middleware
10+
middleware struct {
11+
path string
12+
priority int
13+
handler MiddlewareFunc
14+
}
15+
Error interface {
16+
error
17+
Status() int
18+
}
19+
MiddlewareFunc func(*http.Request) Error
20+
)
21+
22+
func (m middlewares) Len() int { return len(m) }
23+
func (m middlewares) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
24+
func (m middlewares) Less(i, j int) bool { return m[i].priority < m[j].priority }
25+
26+
func sortByPriority(m middlewares) {
27+
sort.Sort(middlewares(m))
28+
}

middleware_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package goapi
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestPriority(t *testing.T) {
10+
var m middlewares
11+
m = append(m, &middleware{priority: 1}, &middleware{priority: 4}, &middleware{priority: 0}, &middleware{priority: 10})
12+
sortByPriority(m)
13+
14+
assert.Equal(t, 0, m[0].priority)
15+
assert.Equal(t, 1, m[1].priority)
16+
assert.Equal(t, 4, m[2].priority)
17+
assert.Equal(t, 10, m[3].priority)
18+
}

router.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package goapi
2+
3+
import (
4+
"net/http"
5+
"regexp"
6+
"strings"
7+
"sync"
8+
)
9+
10+
type (
11+
tree map[string]*route
12+
route struct {
13+
path string
14+
nodes tree
15+
nodesMu sync.RWMutex
16+
handler http.HandlerFunc
17+
middleware middlewares
18+
regexp *regexp.Regexp
19+
isEndPoint bool
20+
}
21+
)
22+
23+
func (r *route) getRoute(paths []string) *route {
24+
if len(paths) > 0 && paths[0] != "" {
25+
r.nodesMu.RLock()
26+
defer r.nodesMu.RUnlock()
27+
if route := r.nodes[paths[0]]; route != nil {
28+
return route.getRoute(paths[1:])
29+
} else {
30+
for path, route := range r.nodes {
31+
if len(path) > 0 && path[:1] == ":" {
32+
if route.regexp == nil {
33+
return route.getRoute(paths[1:])
34+
} else if route.regexp.MatchString(paths[0]) {
35+
return route.getRoute(paths[1:])
36+
}
37+
}
38+
}
39+
}
40+
} else if len(paths) == 0 && r.isEndPoint {
41+
return r
42+
}
43+
return nil
44+
}
45+
46+
func (r *route) addRoute(paths []string, f http.HandlerFunc) {
47+
if len(paths) > 0 && paths[0] != "" {
48+
r.nodesMu.Lock()
49+
defer r.nodesMu.Unlock()
50+
if r.nodes[paths[0]] == nil {
51+
r.nodes[paths[0]] = newRoute(paths[0])
52+
}
53+
r.nodes[paths[0]].addRoute(paths[1:], f)
54+
} else {
55+
r.setEndPoint(f)
56+
}
57+
}
58+
59+
func (r *route) setEndPoint(f http.HandlerFunc) {
60+
if len(r.path) > 0 && r.path[:1] == ":" {
61+
if parts := strings.Split(r.path, ":"); len(parts) == 3 {
62+
r.setRegexp(parts[2])
63+
}
64+
}
65+
r.isEndPoint = true
66+
r.handler = f
67+
}
68+
69+
func (r *route) setRegexp(exp string) {
70+
reg, err := regexp.Compile(exp)
71+
if err == nil {
72+
r.regexp = reg
73+
}
74+
}
75+
76+
func newRoute(path string) *route {
77+
return &route{
78+
path: path,
79+
nodes: make(tree),
80+
}
81+
}

router_test.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package goapi
2+
3+
import (
4+
"net/http"
5+
"strings"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestGetRootRoute(t *testing.T) {
12+
r := newRoute("/")
13+
paths := strings.Split(strings.Trim("/", "/"), "/")
14+
r.addRoute(paths, func(w http.ResponseWriter, req *http.Request) {})
15+
16+
r.getRoute(paths)
17+
18+
assert.Nil(t, r.getRoute([]string{""}))
19+
assert.Nil(t, r.getRoute([]string{"x"}))
20+
assert.Nil(t, r.getRoute([]string{"", "x"}))
21+
assert.Nil(t, r.getRoute([]string{"x", "y"}))
22+
assert.NotNil(t, r.getRoute([]string{}))
23+
}
24+
25+
func TestGetStrictRoute(t *testing.T) {
26+
r := newRoute("/")
27+
paths := strings.Split(strings.Trim("/x", "/"), "/")
28+
r.addRoute(paths, func(w http.ResponseWriter, req *http.Request) {})
29+
30+
r.getRoute(paths)
31+
32+
assert.Nil(t, r.getRoute([]string{}))
33+
assert.Nil(t, r.getRoute([]string{""}))
34+
assert.Nil(t, r.getRoute([]string{"", "x"}))
35+
assert.Nil(t, r.getRoute([]string{"x", "y"}))
36+
assert.NotNil(t, r.getRoute([]string{"x"}))
37+
}
38+
39+
func TestGetParamRoute(t *testing.T) {
40+
r := newRoute("/")
41+
paths := strings.Split(strings.Trim("/:x", "/"), "/")
42+
r.addRoute(paths, func(w http.ResponseWriter, req *http.Request) {})
43+
44+
r.getRoute(paths)
45+
46+
assert.Nil(t, r.getRoute([]string{}))
47+
assert.Nil(t, r.getRoute([]string{""}))
48+
assert.Nil(t, r.getRoute([]string{"", "x"}))
49+
assert.Nil(t, r.getRoute([]string{"x", "y"}))
50+
assert.NotNil(t, r.getRoute([]string{"x"}))
51+
assert.NotNil(t, r.getRoute([]string{"y"}))
52+
}
53+
54+
func TestGetRegexRoute(t *testing.T) {
55+
r := newRoute("/")
56+
paths := strings.Split(strings.Trim("/:x:r([a-z]+)go", "/"), "/")
57+
r.addRoute(paths, func(w http.ResponseWriter, req *http.Request) {})
58+
59+
r.getRoute(paths)
60+
61+
assert.Nil(t, r.getRoute([]string{}))
62+
assert.Nil(t, r.getRoute([]string{""}))
63+
assert.Nil(t, r.getRoute([]string{"", "x"}))
64+
assert.Nil(t, r.getRoute([]string{"x", "y"}))
65+
assert.Nil(t, r.getRoute([]string{"x"}))
66+
assert.Nil(t, r.getRoute([]string{"y"}))
67+
assert.Nil(t, r.getRoute([]string{"rego", "y"}))
68+
assert.NotNil(t, r.getRoute([]string{"rego"}))
69+
}
70+
71+
func TestAddRootRoute(t *testing.T) {
72+
r := newRoute("/")
73+
paths := strings.Split(strings.Trim("/", "/"), "/")
74+
r.addRoute(paths, func(w http.ResponseWriter, req *http.Request) {})
75+
76+
assert.Equal(t, true, r.isEndPoint)
77+
assert.Equal(t, tree{}, r.nodes)
78+
}
79+
80+
func TestAddEmptyRootRoute(t *testing.T) {
81+
r := newRoute("")
82+
paths := strings.Split(strings.Trim("/", "/"), "/")
83+
r.addRoute(paths, func(w http.ResponseWriter, req *http.Request) {})
84+
85+
assert.Equal(t, true, r.isEndPoint)
86+
assert.Equal(t, tree{}, r.nodes)
87+
}
88+
89+
func TestAddEmptyRootRouteTwo(t *testing.T) {
90+
r := newRoute("")
91+
paths := strings.Split(strings.Trim("", "/"), "/")
92+
r.addRoute(paths, func(w http.ResponseWriter, req *http.Request) {})
93+
94+
assert.Equal(t, true, r.isEndPoint)
95+
assert.Equal(t, tree{}, r.nodes)
96+
}
97+
98+
func TestAddRoute(t *testing.T) {
99+
r := newRoute("/")
100+
paths := strings.Split(strings.Trim("/example", "/"), "/")
101+
r.addRoute(paths, func(w http.ResponseWriter, req *http.Request) {})
102+
103+
assert.Equal(t, false, r.isEndPoint)
104+
if assert.NotNil(t, r.nodes["example"]) {
105+
assert.Equal(t, true, r.nodes["example"].isEndPoint)
106+
assert.Equal(t, "example", r.nodes["example"].path)
107+
assert.Nil(t, r.nodes["example"].regexp)
108+
}
109+
}
110+
111+
func TestAddParamRoute(t *testing.T) {
112+
r := newRoute("/")
113+
paths := strings.Split(strings.Trim("/:example", "/"), "/")
114+
r.addRoute(paths, func(w http.ResponseWriter, req *http.Request) {})
115+
116+
assert.Equal(t, false, r.isEndPoint)
117+
if assert.NotNil(t, r.nodes[":example"]) {
118+
assert.Equal(t, true, r.nodes[":example"].isEndPoint)
119+
assert.Equal(t, ":example", r.nodes[":example"].path)
120+
assert.Nil(t, r.nodes[":example"].regexp)
121+
}
122+
}
123+
124+
func TestAddRegexpRoute(t *testing.T) {
125+
r := newRoute("/")
126+
paths := strings.Split(strings.Trim("/:example:r([a-z]+)go", "/"), "/")
127+
r.addRoute(paths, func(w http.ResponseWriter, req *http.Request) {})
128+
129+
assert.Equal(t, false, r.isEndPoint)
130+
131+
route := r.nodes[":example:r([a-z]+)go"]
132+
if assert.NotNil(t, route) {
133+
assert.Equal(t, true, route.isEndPoint)
134+
assert.Equal(t, ":example:r([a-z]+)go", route.path)
135+
if assert.NotNil(t, route.regexp) {
136+
assert.Equal(t, true, route.regexp.MatchString("rego"))
137+
}
138+
}
139+
}

0 commit comments

Comments
 (0)