1
0
mirror of https://github.com/labstack/echo.git synced 2024-11-24 08:22:21 +02:00

Now using sync.Pool

Signed-off-by: Vishal Rana <vr@labstack.com>
This commit is contained in:
Vishal Rana 2016-02-04 14:40:08 -08:00
parent 443a0bb48d
commit f405794a7c
18 changed files with 317 additions and 235 deletions

View File

@ -47,7 +47,7 @@ type (
Redirect(int, string) error
Error(err error)
Logger() *log.Logger
X() *context
Context() *context
}
context struct {
@ -307,8 +307,8 @@ func (c *context) Logger() *log.Logger {
return c.echo.logger
}
// X returns the `context` instance.
func (c *context) X() *context {
// Context returns the `context` instance.
func (c *context) Context() *context {
return c
}

View File

@ -51,8 +51,8 @@ func TestContext(t *testing.T) {
assert.Nil(t, c.Socket())
// Param by id
c.X().pnames = []string{"id"}
c.X().pvalues = []string{"1"}
c.Context().pnames = []string{"id"}
c.Context().pvalues = []string{"1"}
assert.Equal(t, "1", c.P(0))
// Param by name
@ -68,13 +68,13 @@ func TestContext(t *testing.T) {
// JSON
testBindOk(t, c, ApplicationJSON)
c.X().request = test.NewRequest(POST, "/", strings.NewReader(incorrectContent))
c.Context().request = test.NewRequest(POST, "/", strings.NewReader(incorrectContent))
testBindError(t, c, ApplicationJSON)
// XML
c.X().request = test.NewRequest(POST, "/", strings.NewReader(userXML))
c.Context().request = test.NewRequest(POST, "/", strings.NewReader(userXML))
testBindOk(t, c, ApplicationXML)
c.X().request = test.NewRequest(POST, "/", strings.NewReader(incorrectContent))
c.Context().request = test.NewRequest(POST, "/", strings.NewReader(incorrectContent))
testBindError(t, c, ApplicationXML)
// Unsupported
@ -87,14 +87,14 @@ func TestContext(t *testing.T) {
tpl := &Template{
templates: template.Must(template.New("hello").Parse("Hello, {{.}}!")),
}
c.X().echo.SetRenderer(tpl)
c.Context().echo.SetRenderer(tpl)
err := c.Render(http.StatusOK, "hello", "Joe")
if assert.NoError(t, err) {
assert.Equal(t, http.StatusOK, rec.Status())
assert.Equal(t, "Hello, Joe!", rec.Body.String())
}
c.X().echo.renderer = nil
c.Context().echo.renderer = nil
err = c.Render(http.StatusOK, "hello", "Joe")
assert.Error(t, err)
@ -226,12 +226,12 @@ func TestContext(t *testing.T) {
// Error
rec = test.NewResponseRecorder()
c = NewContext(req, rec, e).X()
c = NewContext(req, rec, e).Context()
c.Error(errors.New("error"))
assert.Equal(t, http.StatusInternalServerError, c.Response().Status())
// reset
c.X().reset(req, test.NewResponseRecorder(), e)
c.Context().reset(req, test.NewResponseRecorder(), e)
}
func TestContextPath(t *testing.T) {

51
echo.go
View File

@ -554,8 +554,23 @@ func (e *Echo) SetEngine(t engine.Type) {
}
// Run runs a server.
func (e *Echo) Run(address string) {
config := &engine.Config{Address: address}
func (e *Echo) Run(addr string) {
c := &engine.Config{Address: addr}
e.RunWithConfig(c)
}
// RunTLS runs a server with TLS configuration.
func (e *Echo) RunTLS(addr, certfile, keyfile string) {
c := &engine.Config{
Address: addr,
TLSCertfile: certfile,
TLSKeyfile: keyfile,
}
e.RunWithConfig(c)
}
// RunWithConfig runs a server with engine configuration.
func (e *Echo) RunWithConfig(config *engine.Config) {
handler := func(req engine.Request, res engine.Response) {
if e.hook != nil {
e.hook(req, res)
@ -585,38 +600,6 @@ func (e *Echo) Run(address string) {
}
e.engine.Start()
// e.run(e.Server(addr))
}
// RunTLS runs a server with TLS configuration.
func (e *Echo) RunTLS(addr, crtFile, keyFile string) {
// e.run(e.Server(addr), crtFile, keyFile)
}
// RunServer runs a custom server.
func (e *Echo) RunServer(s *http.Server) {
// e.run(s)
}
// RunTLSServer runs a custom server with TLS configuration.
func (e *Echo) RunTLSServer(s *http.Server, crtFile, keyFile string) {
// e.run(s, crtFile, keyFile)
}
func (e *Echo) run(s *http.Server, files ...string) {
// s.Handler = e
// // TODO: Remove in Go 1.6+
// if e.http2 {
// http2.ConfigureServer(s, nil)
// }
// if len(files) == 0 {
// e.logger.Fatal(s.ListenAndServe())
// } else if len(files) == 2 {
// e.logger.Fatal(s.ListenAndServeTLS(files[0], files[1]))
// } else {
// e.logger.Fatal("invalid TLS configuration")
// }
}
func NewHTTPError(code int, msg ...string) *HTTPError {

View File

@ -307,9 +307,9 @@ func TestEchoGroup(t *testing.T) {
func TestEchoNotFound(t *testing.T) {
e := New()
req := test.NewRequest(GET, "/files", nil)
res := test.NewResponseRecorder()
e.ServeHTTP(req, res)
assert.Equal(t, http.StatusNotFound, res.Status())
rec := test.NewResponseRecorder()
e.ServeHTTP(req, rec)
assert.Equal(t, http.StatusNotFound, rec.Status())
}
func TestEchoMethodNotAllowed(t *testing.T) {
@ -318,9 +318,9 @@ func TestEchoMethodNotAllowed(t *testing.T) {
return c.String(http.StatusOK, "Echo!")
})
req := test.NewRequest(POST, "/", nil)
res := test.NewResponseRecorder()
e.ServeHTTP(req, res)
assert.Equal(t, http.StatusMethodNotAllowed, res.Status())
rec := test.NewResponseRecorder()
e.ServeHTTP(req, rec)
assert.Equal(t, http.StatusMethodNotAllowed, rec.Status())
}
func TestEchoHTTPError(t *testing.T) {
@ -349,8 +349,8 @@ func TestEchoHook(t *testing.T) {
}
})
req := test.NewRequest(GET, "/test/", nil)
res := test.NewResponseRecorder()
e.ServeHTTP(req, res)
rec := test.NewResponseRecorder()
e.ServeHTTP(req, rec)
assert.Equal(t, req.URL().Path(), "/test")
}
@ -370,7 +370,7 @@ func testMethod(t *testing.T, method, path string, e *Echo) {
func request(method, path string, e *Echo) (int, string) {
req := test.NewRequest(method, path, nil)
res := test.NewResponseRecorder()
e.ServeHTTP(req, res)
return res.Status(), res.Body.String()
rec := test.NewResponseRecorder()
e.ServeHTTP(req, rec)
return rec.Status(), rec.Body.String()
}

View File

@ -1,6 +1,9 @@
package engine
import "io"
import (
"io"
"time"
)
type (
Type uint8
@ -31,6 +34,8 @@ type (
Status() int
Size() int64
Committed() bool
SetWriter(io.Writer)
Writer() io.Writer
}
Header interface {
@ -49,7 +54,11 @@ type (
}
Config struct {
Address string
Address string
ReadTimeout time.Duration
WriteTimeout time.Duration
TLSCertfile string
TLSKeyfile string
}
)

View File

@ -1,6 +1,8 @@
package fasthttp
import (
"io"
"github.com/labstack/echo/engine"
"github.com/valyala/fasthttp"
)
@ -12,6 +14,7 @@ type (
status int
size int64
committed bool
writer io.Writer
}
)
@ -38,3 +41,11 @@ func (r *Response) Size() int64 {
func (r *Response) Committed() bool {
return r.committed
}
func (r *Response) SetWriter(w io.Writer) {
r.writer = w
}
func (r *Response) Writer() io.Writer {
return r.writer
}

View File

@ -1,8 +1,9 @@
package fasthttp
import (
"log"
"net/http"
"github.com/labstack/gommon/log"
)
import (
"github.com/labstack/echo/engine"
@ -27,7 +28,6 @@ func NewServer(config *engine.Config, handler engine.HandlerFunc) *Server {
func (s *Server) Start() {
fasthttp.ListenAndServe(s.config.Address, func(ctx *fasthttp.RequestCtx) {
println("FastHTTP")
req := &Request{
context: ctx,
url: &URL{ctx.URI()},

View File

@ -4,22 +4,26 @@ import "net/http"
type (
Header struct {
http.Header
header http.Header
}
)
func (h *Header) Add(key, val string) {
h.Header.Add(key, val)
h.header.Add(key, val)
}
func (h *Header) Del(key string) {
h.Header.Del(key)
h.header.Del(key)
}
func (h *Header) Get(key string) string {
return h.Header.Get(key)
return h.header.Get(key)
}
func (h *Header) Set(key, val string) {
h.Header.Set(key, val)
h.header.Set(key, val)
}
func (h *Header) reset(hdr http.Header) {
h.header = hdr
}

View File

@ -18,7 +18,7 @@ type (
func NewRequest(r *http.Request) *Request {
return &Request{
request: r,
url: NewURL(r.URL),
url: &URL{url: r.URL},
header: &Header{r.Header},
}
}
@ -54,3 +54,9 @@ func (r *Request) Body() io.ReadCloser {
func (r *Request) FormValue(name string) string {
return r.request.FormValue(name)
}
func (r *Request) reset(req *http.Request, h engine.Header, u engine.URL) {
r.request = req
r.header = h
r.url = u
}

View File

@ -1,7 +1,11 @@
package standard
import "net/http"
import "github.com/labstack/echo/engine"
import (
"io"
"net/http"
"github.com/labstack/echo/engine"
)
type (
Response struct {
@ -10,6 +14,7 @@ type (
status int
size int64
committed bool
writer io.Writer
}
)
@ -17,6 +22,7 @@ func NewResponse(w http.ResponseWriter) *Response {
return &Response{
response: w,
header: &Header{w.Header()},
writer: w,
}
}
@ -35,7 +41,7 @@ func (r *Response) WriteHeader(code int) {
}
func (r *Response) Write(b []byte) (n int, err error) {
n, err = r.response.Write(b)
n, err = r.writer.Write(b)
r.size += int64(n)
return
}
@ -51,3 +57,20 @@ func (r *Response) Size() int64 {
func (r *Response) Committed() bool {
return r.committed
}
func (r *Response) SetWriter(w io.Writer) {
r.writer = w
}
func (r *Response) Writer() io.Writer {
return r.writer
}
func (r *Response) reset(w http.ResponseWriter, h engine.Header) {
r.response = w
r.header = h
r.status = http.StatusOK
r.size = 0
r.committed = false
r.writer = w
}

View File

@ -3,6 +3,7 @@ package standard
import (
"log"
"net/http"
"sync"
"github.com/labstack/echo/engine"
)
@ -12,6 +13,14 @@ type (
*http.Server
config *engine.Config
handler engine.HandlerFunc
pool *Pool
}
Pool struct {
request sync.Pool
response sync.Pool
header sync.Pool
url sync.Pool
}
)
@ -20,13 +29,54 @@ func NewServer(config *engine.Config, handler engine.HandlerFunc) *Server {
Server: new(http.Server),
config: config,
handler: handler,
pool: &Pool{
request: sync.Pool{
New: func() interface{} {
return &Request{}
},
},
response: sync.Pool{
New: func() interface{} {
return &Response{}
},
},
header: sync.Pool{
New: func() interface{} {
return &Header{}
},
},
url: sync.Pool{
New: func() interface{} {
return &URL{}
},
},
},
}
}
func (s *Server) Start() {
s.Addr = s.config.Address
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s.handler(NewRequest(r), NewResponse(w))
// Request
req := s.pool.request.Get().(*Request)
reqHdr := s.pool.request.Get().(*Header)
reqURL := s.pool.request.Get().(*URL)
reqHdr.reset(r.Header)
reqURL.reset(r.URL)
req.reset(r, reqHdr, reqURL)
// Response
res := s.pool.request.Get().(*Response)
resHdr := s.pool.request.Get().(*Header)
res.reset(w, reqHdr)
s.handler(req, res)
s.pool.request.Put(req)
s.pool.header.Put(reqHdr)
s.pool.url.Put(reqURL)
s.pool.response.Put(res)
s.pool.header.Put(resHdr)
})
log.Fatal(s.ListenAndServe())
}

View File

@ -9,10 +9,6 @@ type (
}
)
func NewURL(u *url.URL) *URL {
return &URL{url: u}
}
func (u *URL) URL() *url.URL {
return u.url
}
@ -39,3 +35,7 @@ func (u *URL) QueryValue(name string) string {
}
return u.query.Get(name)
}
func (u *URL) reset(url *url.URL) {
u.url = url
}

View File

@ -1,74 +1,74 @@
package middleware
//
// import (
// "bufio"
// "compress/gzip"
// "io"
// "io/ioutil"
// "net"
// "net/http"
// "strings"
// "sync"
//
// "github.com/labstack/echo"
// )
//
// type (
// gzipWriter struct {
// io.Writer
// http.ResponseWriter
// }
// )
//
// func (w gzipWriter) Write(b []byte) (int, error) {
// if w.Header().Get(echo.ContentType) == "" {
// w.Header().Set(echo.ContentType, http.DetectContentType(b))
// }
// return w.Writer.Write(b)
// }
//
// func (w gzipWriter) Flush() error {
// return w.Writer.(*gzip.Writer).Flush()
// }
//
// func (w gzipWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
// return w.ResponseWriter.(http.Hijacker).Hijack()
// }
//
// func (w *gzipWriter) CloseNotify() <-chan bool {
// return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
// }
//
// var writerPool = sync.Pool{
// New: func() interface{} {
// return gzip.NewWriter(ioutil.Discard)
// },
// }
//
// // Gzip returns a middleware which compresses HTTP response using gzip compression
// // scheme.
// func Gzip() echo.MiddlewareFunc {
// scheme := "gzip"
//
// return func(h echo.HandlerFunc) echo.HandlerFunc {
// return func(c echo.Context) error {
// c.Response().Header().Add(echo.Vary, echo.AcceptEncoding)
// if strings.Contains(c.Request().Header().Get(echo.AcceptEncoding), scheme) {
// w := writerPool.Get().(*gzip.Writer)
// w.Reset(c.Response().Writer())
// defer func() {
// w.Close()
// writerPool.Put(w)
// }()
// gw := gzipWriter{Writer: w, ResponseWriter: c.Response().Writer()}
// c.Response().Header().Set(echo.ContentEncoding, scheme)
// c.Response().SetWriter(gw)
// }
// if err := h(c); err != nil {
// c.Error(err)
// }
// return nil
// }
// }
// }
import (
"bufio"
"compress/gzip"
"io"
"io/ioutil"
"net"
"net/http"
"strings"
"sync"
"github.com/labstack/echo"
"github.com/labstack/echo/engine"
)
type (
gzipWriter struct {
io.Writer
engine.Response
}
)
func (w gzipWriter) Write(b []byte) (int, error) {
if w.Header().Get(echo.ContentType) == "" {
w.Header().Set(echo.ContentType, http.DetectContentType(b))
}
return w.Writer.Write(b)
}
func (w gzipWriter) Flush() error {
return w.Writer.(*gzip.Writer).Flush()
}
func (w gzipWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.Response.(http.Hijacker).Hijack()
}
func (w *gzipWriter) CloseNotify() <-chan bool {
return w.Response.(http.CloseNotifier).CloseNotify()
}
var writerPool = sync.Pool{
New: func() interface{} {
return gzip.NewWriter(ioutil.Discard)
},
}
// Gzip returns a middleware which compresses HTTP response using gzip compression
// scheme.
func Gzip() echo.MiddlewareFunc {
scheme := "gzip"
return func(h echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Response().Header().Add(echo.Vary, echo.AcceptEncoding)
if strings.Contains(c.Request().Header().Get(echo.AcceptEncoding), scheme) {
w := writerPool.Get().(*gzip.Writer)
w.Reset(c.Response().Writer())
defer func() {
w.Close()
writerPool.Put(w)
}()
gw := gzipWriter{Writer: w, Response: c.Response()}
c.Response().Header().Set(echo.ContentEncoding, scheme)
c.Response().SetWriter(gw)
}
if err := h(c); err != nil {
c.Error(err)
}
return nil
}
}
}

View File

@ -1,73 +1,69 @@
package middleware
//
// import (
// "bytes"
// "compress/gzip"
// "net/http"
// "net/http/httptest"
// "testing"
// "time"
//
// "github.com/labstack/echo"
// "github.com/labstack/echo/test"
// "github.com/stretchr/testify/assert"
// )
//
// type closeNotifyingRecorder struct {
// *httptest.ResponseRecorder
// closed chan bool
// }
//
// func newCloseNotifyingRecorder() *closeNotifyingRecorder {
// return &closeNotifyingRecorder{
// test.NewResponseRecorder(),
// make(chan bool, 1),
// }
// }
//
// func (c *closeNotifyingRecorder) close() {
// c.closed <- true
// }
//
// func (c *closeNotifyingRecorder) CloseNotify() <-chan bool {
// return c.closed
// }
//
// func TestGzip(t *testing.T) {
// e := echo.New()
// req := test.NewRequest(echo.GET, "/", nil)
// res := test.NewResponseRecorder()
// c := echo.NewContext(req, res, e)
// h := func(c echo.Context) error {
// c.Response().Write([]byte("test")) // For Content-Type sniffing
// return nil
// }
//
// // Skip if no Accept-Encoding header
// Gzip()(h)(c)
// assert.Equal(t, http.StatusOK, res.Status())
// assert.Equal(t, "test", res.Body().String())
//
// req = test.NewRequest(echo.GET, "/", nil)
// req.Header.Set(echo.AcceptEncoding, "gzip")
// res = test.NewResponseRecorder()
// c = echo.NewContext(req, res, e)
//
// // Gzip
// Gzip()(h)(c)
// assert.Equal(t, http.StatusOK, res.Status())
// assert.Equal(t, "gzip", res.Header().Get(echo.ContentEncoding))
// assert.Contains(t, res.Header().Get(echo.ContentType), echo.TextPlain)
// r, err := gzip.NewReader(res.Body())
// defer r.Close()
// if assert.NoError(t, err) {
// buf := new(bytes.Buffer)
// buf.ReadFrom(r)
// assert.Equal(t, "test", buf.String())
// }
// }
//
import (
"bytes"
"compress/gzip"
"testing"
"github.com/labstack/echo"
"github.com/labstack/echo/test"
"github.com/stretchr/testify/assert"
)
type closeNotifyingRecorder struct {
*test.ResponseRecorder
closed chan bool
}
func newCloseNotifyingRecorder() *closeNotifyingRecorder {
return &closeNotifyingRecorder{
test.NewResponseRecorder(),
make(chan bool, 1),
}
}
func (c *closeNotifyingRecorder) close() {
c.closed <- true
}
func (c *closeNotifyingRecorder) CloseNotify() <-chan bool {
return c.closed
}
func TestGzip(t *testing.T) {
e := echo.New()
req := test.NewRequest(echo.GET, "/", nil)
rec := test.NewResponseRecorder()
c := echo.NewContext(req, rec, e)
h := func(c echo.Context) error {
c.Response().Write([]byte("test")) // For Content-Type sniffing
return nil
}
// Skip if no Accept-Encoding header
Gzip()(h)(c)
// assert.Equal(t, http.StatusOK, rec.Status())
assert.Equal(t, "test", rec.Body.String())
req = test.NewRequest(echo.GET, "/", nil)
req.Header().Set(echo.AcceptEncoding, "gzip")
rec = test.NewResponseRecorder()
c = echo.NewContext(req, rec, e)
// Gzip
Gzip()(h)(c)
// assert.Equal(t, http.StatusOK, rec.Status())
assert.Equal(t, "gzip", rec.Header().Get(echo.ContentEncoding))
assert.Contains(t, rec.Header().Get(echo.ContentType), echo.TextPlain)
r, err := gzip.NewReader(rec.Body)
defer r.Close()
if assert.NoError(t, err) {
buf := new(bytes.Buffer)
buf.ReadFrom(r)
assert.Equal(t, "test", buf.String())
}
}
// func TestGzipFlush(t *testing.T) {
// res := test.NewResponseRecorder()
// buf := new(bytes.Buffer)
@ -104,7 +100,7 @@ package middleware
// t.Fatal("Flush didn't flush any data")
// }
// }
//
// func TestGzipCloseNotify(t *testing.T) {
// rec := newCloseNotifyingRecorder()
// buf := new(bytes.Buffer)

View File

@ -15,8 +15,8 @@ func TestLogger(t *testing.T) {
// Note: Just for the test coverage, not a real test.
e := echo.New()
req := test.NewRequest(echo.GET, "/", nil)
res := test.NewResponseRecorder()
c := echo.NewContext(req, res, e)
rec := test.NewResponseRecorder()
c := echo.NewContext(req, rec, e)
// Status 2xx
h := func(c echo.Context) error {
@ -25,16 +25,16 @@ func TestLogger(t *testing.T) {
Logger()(h)(c)
// Status 3xx
res = test.NewResponseRecorder()
c = echo.NewContext(req, res, e)
rec = test.NewResponseRecorder()
c = echo.NewContext(req, rec, e)
h = func(c echo.Context) error {
return c.String(http.StatusTemporaryRedirect, "test")
}
Logger()(h)(c)
// Status 4xx
res = test.NewResponseRecorder()
c = echo.NewContext(req, res, e)
rec = test.NewResponseRecorder()
c = echo.NewContext(req, rec, e)
h = func(c echo.Context) error {
return c.String(http.StatusNotFound, "test")
}
@ -42,8 +42,8 @@ func TestLogger(t *testing.T) {
// Status 5xx with empty path
req = test.NewRequest(echo.GET, "", nil)
res = test.NewResponseRecorder()
c = echo.NewContext(req, res, e)
rec = test.NewResponseRecorder()
c = echo.NewContext(req, rec, e)
h = func(c echo.Context) error {
return errors.New("error")
}
@ -53,8 +53,8 @@ func TestLogger(t *testing.T) {
func TestLoggerIPAddress(t *testing.T) {
e := echo.New()
req := test.NewRequest(echo.GET, "/", nil)
res := test.NewResponseRecorder()
c := echo.NewContext(req, res, e)
rec := test.NewResponseRecorder()
c := echo.NewContext(req, rec, e)
buf := new(bytes.Buffer)
e.Logger().SetOutput(buf)
ip := "127.0.0.1"

View File

@ -13,12 +13,12 @@ func TestRecover(t *testing.T) {
e := echo.New()
e.SetDebug(true)
req := test.NewRequest(echo.GET, "/", nil)
res := test.NewResponseRecorder()
c := echo.NewContext(req, res, e)
rec := test.NewResponseRecorder()
c := echo.NewContext(req, rec, e)
h := func(c echo.Context) error {
panic("test")
}
Recover()(h)(c)
assert.Equal(t, http.StatusInternalServerError, res.Status())
assert.Contains(t, res.Body.String(), "panic recover")
assert.Equal(t, http.StatusInternalServerError, rec.Status())
assert.Contains(t, rec.Body.String(), "panic recover")
}

View File

@ -274,7 +274,7 @@ func (n *node) check405() HandlerFunc {
}
func (r *Router) Find(method, path string, context Context) (h HandlerFunc, e *Echo) {
x := context.X()
x := context.Context()
h = notFoundHandler
e = r.echo
cn := r.tree // Current node as root

View File

@ -529,16 +529,16 @@ func TestRouterParamNames(t *testing.T) {
// Route > /users/:id
h, _ = r.Find(GET, "/users/1", c)
if assert.NotNil(t, h) {
assert.Equal(t, "id", c.X().pnames[0])
assert.Equal(t, "id", c.Context().pnames[0])
assert.Equal(t, "1", c.P(0))
}
// Route > /users/:uid/files/:fid
h, _ = r.Find(GET, "/users/1/files/1", c)
if assert.NotNil(t, h) {
assert.Equal(t, "uid", c.X().pnames[0])
assert.Equal(t, "uid", c.Context().pnames[0])
assert.Equal(t, "1", c.P(0))
assert.Equal(t, "fid", c.X().pnames[1])
assert.Equal(t, "fid", c.Context().pnames[1])
assert.Equal(t, "1", c.P(1))
}
}
@ -556,7 +556,7 @@ func TestRouterAPI(t *testing.T) {
for _, route := range api {
h, _ := r.Find(route.Method, route.Path, c)
if assert.NotNil(t, h) {
for i, n := range c.X().pnames {
for i, n := range c.Context().pnames {
if assert.NotEmpty(t, n) {
assert.Equal(t, ":"+n, c.P(i))
}