Skip to content

Commit

Permalink
feat: use radix tree instead of httprouter
Browse files Browse the repository at this point in the history
  • Loading branch information
vicanso committed Apr 9, 2020
1 parent e3a0f12 commit 34a43f4
Show file tree
Hide file tree
Showing 13 changed files with 1,104 additions and 141 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ elton的路由使用[httprouter](https://github.com/julienschmidt/httprouter),

```go
// 带参数的路由配置
e.GET("/books/:type", func(c *elton.Context) error {
e.GET("/books/{type}", func(c *elton.Context) error {
c.BodyBuffer = bytes.NewBufferString(c.Param("type"))
return nil
})
Expand Down Expand Up @@ -159,12 +159,12 @@ func main() {
## bench

```
BenchmarkRoutes-8 3489271 343 ns/op 376 B/op 4 allocs/op
BenchmarkGetFunctionName-8 129711422 9.23 ns/op 0 B/op 0 allocs/op
BenchmarkContextGet-8 14131228 84.4 ns/op 16 B/op 1 allocs/op
BenchmarkContextNewMap-8 183387170 6.52 ns/op 0 B/op 0 allocs/op
BenchmarkConvertServerTiming-8 1430475 839 ns/op 360 B/op 11 allocs/op
BenchmarkGetStatus-8 1000000000 0.272 ns/op 0 B/op 0 allocs/op
BenchmarkRWMutexSignedKeys-8 35028435 33.5 ns/op
BenchmarkAtomicSignedKeys-8 602747588 1.99 ns/op
BenchmarkRoutes-8 2808996 417 ns/op 424 B/op 5 allocs/op
BenchmarkGetFunctionName-8 130826358 9.31 ns/op 0 B/op 0 allocs/op
BenchmarkContextGet-8 14899309 79.9 ns/op 16 B/op 1 allocs/op
BenchmarkContextNewMap-8 184250533 6.47 ns/op 0 B/op 0 allocs/op
BenchmarkConvertServerTiming-8 1421496 854 ns/op 360 B/op 11 allocs/op
BenchmarkGetStatus-8 1000000000 0.542 ns/op 0 B/op 0 allocs/op
BenchmarkRWMutexSignedKeys-8 33935337 33.8 ns/op
BenchmarkAtomicSignedKeys-8 1000000000 0.402 ns/op
```
42 changes: 23 additions & 19 deletions context.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
// Copyright 2018 tree xie
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// MIT License

// Copyright (c) 2020 Tree Xie

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package elton

Expand All @@ -30,7 +38,6 @@ import (
"sync/atomic"
"time"

"github.com/julienschmidt/httprouter"
"github.com/vicanso/hes"
intranetip "github.com/vicanso/intranet-ip"
"github.com/vicanso/keygrip"
Expand Down Expand Up @@ -60,9 +67,7 @@ type (
// Next next function, it will be auto generated.
Next func() error
// Params route params
Params map[string]string
// RawParams http router params
RawParams httprouter.Params
Params *RouteParams
// StatusCode http response's status code, default is 0 which will be handle as 200
StatusCode int
// Body http response's body, which should be converted to bytes by response middlware.
Expand Down Expand Up @@ -106,7 +111,6 @@ func (c *Context) Reset() {
c.Route = ""
c.Next = nil
c.Params = nil
c.RawParams = nil
c.StatusCode = 0
c.Body = nil
c.BodyBuffer = nil
Expand Down Expand Up @@ -192,7 +196,7 @@ func (c *Context) Param(name string) string {
if c.Params == nil {
return ""
}
return c.Params[name]
return c.Params.Get(name)
}

// getCacheQuery get the cache query
Expand Down
8 changes: 4 additions & 4 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestReset(t *testing.T) {
Next: func() error {
return nil
},
Params: make(map[string]string),
Params: new(RouteParams),
StatusCode: 200,
Body: make(map[string]string),
BodyBuffer: bytes.NewBufferString("abcd"),
Expand Down Expand Up @@ -154,9 +154,9 @@ func TestParam(t *testing.T) {
assert := assert.New(t)
c := Context{}
assert.Equal(c.Param("name"), "", "params is not initialized, it should be nil")
c.Params = map[string]string{
"name": "tree.xie",
}
params := new(RouteParams)
params.Add("name", "tree.xie")
c.Params = params
assert.Equal("tree.xie", c.Param("name"))
}

Expand Down
34 changes: 21 additions & 13 deletions df.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
// Copyright 2018 tree xie
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// MIT License

// Copyright (c) 2020 Tree Xie

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package elton

Expand Down
72 changes: 26 additions & 46 deletions elton.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
// Copyright 2018 tree xie
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// MIT License

// Copyright (c) 2020 Tree Xie

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package elton

Expand All @@ -28,7 +36,6 @@ import (
"sync/atomic"
"time"

"github.com/julienschmidt/httprouter"
"github.com/vicanso/hes"
)

Expand All @@ -55,10 +62,9 @@ type (
Elton struct {
// status of elton
status int32
tree *node
// Server http server
Server *http.Server
// Router http router
Router *httprouter.Router
// Routers all router infos
Routers []*RouterInfo
// Middlewares middleware function
Expand All @@ -80,7 +86,6 @@ type (
// functionInfos the function address:name map
functionInfos map[uintptr]string
ctxPool sync.Pool
validators map[string]Validator
}
// TraceInfo trace's info
TraceInfo struct {
Expand Down Expand Up @@ -133,7 +138,7 @@ func New() *Elton {
// NewWithoutServer create an elton instance without http server
func NewWithoutServer() *Elton {
e := &Elton{
Router: httprouter.New(),
tree: new(node),
functionInfos: make(map[uintptr]string),
}
e.ctxPool.New = func() interface{} {
Expand Down Expand Up @@ -226,7 +231,7 @@ func (e *Elton) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
for _, preHandler := range e.PreMiddlewares {
preHandler(req)
}
fn, params, _ := e.Router.Lookup(req.Method, req.URL.Path)
fn, params := e.tree.FindRoute(methodMap[req.Method], req.URL.Path)
if fn != nil {
fn(resp, req, params)
return
Expand Down Expand Up @@ -258,17 +263,11 @@ func (e *Elton) Handle(method, path string, handlerList ...Handler) {
Method: method,
Path: path,
})
e.Router.Handle(method, path, func(resp http.ResponseWriter, req *http.Request, params httprouter.Params) {
e.tree.InsertRoute(methodMap[method], path, func(resp http.ResponseWriter, req *http.Request, params *RouteParams) {
c := e.ctxPool.Get().(*Context)
c.Reset()
e.fillContext(c, resp, req)
c.RawParams = params
if len(params) != 0 {
c.Params = make(map[string]string)
for _, item := range params {
c.Params[item.Key] = item.Value
}
}
c.Params = params

if e.GenerateID != nil {
c.ID = e.GenerateID()
Expand All @@ -295,17 +294,6 @@ func (e *Elton) Handle(method, path string, handlerList ...Handler) {
return nil
}

// 在最后一个handler执行时,如果有配置参数校验,则校验
if index == maxNext-1 && e.validators != nil {
for key, value := range c.Params {
if e.validators[key] != nil {
e := e.validators[key](value)
if e != nil {
return e
}
}
}
}
// 如果已执行完公共添加的中间件,执行handler list
if index >= maxMid {
fn = handlerList[index-maxMid]
Expand Down Expand Up @@ -378,14 +366,6 @@ func (e *Elton) Handle(method, path string, handlerList ...Handler) {
})
}

// AddValidator add validate function
func (e *Elton) AddValidator(key string, fn Validator) {
if e.validators == nil {
e.validators = make(map[string]Validator)
}
e.validators[key] = fn
}

// GET add http get method handle
func (e *Elton) GET(path string, handlerList ...Handler) {
e.Handle(http.MethodGet, path, handlerList...)
Expand Down
34 changes: 3 additions & 31 deletions elton_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"net/http"
"net/http/httptest"
"os"
"regexp"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -137,12 +136,12 @@ func TestHandle(t *testing.T) {
e.ALL(allMethods)
for index, r := range e.Routers {
p := path
if index >= 8 {
if index >= len(methods) {
p = allMethods
}
assert.Equal(p, r.Path)
}
assert.Equal(16, len(e.Routers), "method handle add fail")
assert.Equal(2*len(methods), len(e.Routers), "method handle add fail")
})
t.Run("group", func(t *testing.T) {
assert := assert.New(t)
Expand Down Expand Up @@ -180,7 +179,7 @@ func TestHandle(t *testing.T) {
resp := httptest.NewRecorder()
e.ServeHTTP(resp, req)
}
assert.Equal(doneCount, len(methods), "not all method request is done")
assert.Equal(len(methods), doneCount, "not all method request is done")
})

route := "/system/info"
Expand Down Expand Up @@ -288,33 +287,6 @@ func TestHandle(t *testing.T) {
})
}

func TestParamValidate(t *testing.T) {
e := New()
runMid := false
assert := assert.New(t)
e.AddValidator("id", func(value string) error {
reg := regexp.MustCompile(`^[0-9]{5}$`)
if !reg.MatchString(value) {
return errors.New("id should be 5 numbers")
}
return nil
})
e.Use(func(c *Context) error {
runMid = true
return c.Next()
})
e.GET("/:id", func(c *Context) error {
c.NoContent()
return nil
})
req := httptest.NewRequest("GET", "/1", nil)
resp := httptest.NewRecorder()
e.ServeHTTP(resp, req)
assert.True(runMid)
assert.Equal(500, resp.Code)
assert.Equal("id should be 5 numbers", resp.Body.String())
}

func TestErrorHandler(t *testing.T) {
t.Run("remove header", func(t *testing.T) {
assert := assert.New(t)
Expand Down
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ module github.com/vicanso/elton
go 1.12

require (
github.com/julienschmidt/httprouter v1.3.0
github.com/stretchr/testify v1.4.0
github.com/stretchr/testify v1.5.1
github.com/vicanso/hes v0.2.1
github.com/vicanso/intranet-ip v0.0.1
github.com/vicanso/keygrip v0.1.0
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/vicanso/hes v0.2.1 h1:jRFEADmiQ30koVY/sKwlkhyXM5B3QbVVizLqrjNJgPw=
github.com/vicanso/hes v0.2.1/go.mod h1:QcxOFmFfBQMhASTaLgnFayXYCgevdSeBVprt+o+3eKo=
github.com/vicanso/intranet-ip v0.0.1 h1:cYS+mExFsKqewWSuHtFwAqw/CO66GsheB/P1BPmSTx0=
Expand Down
Loading

0 comments on commit 34a43f4

Please sign in to comment.