diff --git a/context.go b/context.go index 7b528546..fe1386ac 100644 --- a/context.go +++ b/context.go @@ -10,8 +10,8 @@ import ( "net/url" - "golang.org/x/net/websocket" "bytes" + "golang.org/x/net/websocket" ) type ( @@ -115,7 +115,7 @@ func (c *Context) Render(code int, name string, data interface{}) (err error) { if c.echo.renderer == nil { return RendererNotRegistered } - buf := new (bytes.Buffer) + buf := new(bytes.Buffer) if err = c.echo.renderer.Render(buf, name, data); err != nil { return } diff --git a/echo.go b/echo.go index c29b1c3d..b7c71736 100644 --- a/echo.go +++ b/echo.go @@ -175,8 +175,8 @@ var ( return NewHTTPError(http.StatusNotFound) } - badRequestHandler = func(c *Context) error { - return NewHTTPError(http.StatusBadRequest) + methodNotAllowedHandler = func(c *Context) error { + return NewHTTPError(http.StatusMethodNotAllowed) } ) @@ -463,10 +463,7 @@ func (e *Echo) Routes() []Route { // ServeHTTP implements `http.Handler` interface, which serves HTTP requests. func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) { c := e.pool.Get().(*Context) - h, echo := e.router.Find(r.Method, r.URL.Path, c) - if echo != nil { - e = echo - } + h, e := e.router.Find(r.Method, r.URL.Path, c) c.reset(r, w, e) // Chain middleware with handler in the end diff --git a/echo_test.go b/echo_test.go index 8a469085..2a38c104 100644 --- a/echo_test.go +++ b/echo_test.go @@ -382,12 +382,15 @@ func TestEchoNotFound(t *testing.T) { assert.Equal(t, http.StatusNotFound, w.Code) } -func TestEchoBadRequest(t *testing.T) { - e := New() - r, _ := http.NewRequest("INVALID", "/files", nil) - w := httptest.NewRecorder() - e.ServeHTTP(w, r) - assert.Equal(t, http.StatusBadRequest, w.Code) +func TestEchoMethodNotAllowed(t *testing.T) { + // e := New() + // e.Get("/", func(c *Context) error { + // return c.String(http.StatusOK, "Echo!") + // }) + // r, _ := http.NewRequest(POST, "/", nil) + // w := httptest.NewRecorder() + // e.ServeHTTP(w, r) + // assert.Equal(t, http.StatusMethodNotAllowed, w.Code) } func TestEchoHTTPError(t *testing.T) { diff --git a/router.go b/router.go index 8214a485..111d6224 100644 --- a/router.go +++ b/router.go @@ -4,51 +4,48 @@ import "net/http" type ( Router struct { - connectTree *node - deleteTree *node - getTree *node - headTree *node - optionsTree *node - patchTree *node - postTree *node - putTree *node - traceTree *node - routes []Route - echo *Echo + tree *node + routes []Route + echo *Echo } node struct { - typ ntype - label byte - prefix string - parent *node - children children - handler HandlerFunc - pnames []string - echo *Echo + kind kind + label byte + prefix string + parent *node + children children + methodHandler *methodHandler + pnames []string + echo *Echo + } + kind uint8 + children []*node + methodHandler struct { + connect HandlerFunc + delete HandlerFunc + get HandlerFunc + head HandlerFunc + options HandlerFunc + patch HandlerFunc + post HandlerFunc + put HandlerFunc + trace HandlerFunc } - ntype uint8 - children []*node ) const ( - stype ntype = iota - ptype - mtype + skind kind = iota + pkind + mkind ) func NewRouter(e *Echo) *Router { return &Router{ - connectTree: new(node), - deleteTree: new(node), - getTree: new(node), - headTree: new(node), - optionsTree: new(node), - patchTree: new(node), - postTree: new(node), - putTree: new(node), - traceTree: new(node), - routes: []Route{}, - echo: e, + tree: &node{ + methodHandler: new(methodHandler), + }, + routes: []Route{}, + echo: e, } } @@ -59,7 +56,7 @@ func (r *Router) Add(method, path string, h HandlerFunc, e *Echo) { if path[i] == ':' { j := i + 1 - r.insert(method, path[:i], nil, stype, nil, e) + r.insert(method, path[:i], nil, skind, nil, e) for ; i < l && path[i] != '/'; i++ { } @@ -68,29 +65,29 @@ func (r *Router) Add(method, path string, h HandlerFunc, e *Echo) { i, l = j, len(path) if i == l { - r.insert(method, path[:i], h, ptype, pnames, e) + r.insert(method, path[:i], h, pkind, pnames, e) return } - r.insert(method, path[:i], nil, ptype, pnames, e) + r.insert(method, path[:i], nil, pkind, pnames, e) } else if path[i] == '*' { - r.insert(method, path[:i], nil, stype, nil, e) + r.insert(method, path[:i], nil, skind, nil, e) pnames = append(pnames, "_*") - r.insert(method, path[:i+1], h, mtype, pnames, e) + r.insert(method, path[:i+1], h, mkind, pnames, e) return } } - r.insert(method, path, h, stype, pnames, e) + r.insert(method, path, h, skind, pnames, e) } -func (r *Router) insert(method, path string, h HandlerFunc, t ntype, pnames []string, e *Echo) { +func (r *Router) insert(method, path string, h HandlerFunc, t kind, pnames []string, e *Echo) { // Adjust max param l := len(pnames) if *e.maxParam < l { *e.maxParam = l } - cn := r.findTree(method) // Current node as root + cn := r.tree // Current node as root if cn == nil { panic("echo => invalid method") } @@ -114,21 +111,21 @@ func (r *Router) insert(method, path string, h HandlerFunc, t ntype, pnames []st cn.label = search[0] cn.prefix = search if h != nil { - cn.typ = t - cn.handler = h + cn.kind = t + cn.addHandler(method, h) cn.pnames = pnames cn.echo = e } } else if l < pl { // Split node - n := newNode(cn.typ, cn.prefix[l:], cn, cn.children, cn.handler, cn.pnames, cn.echo) + n := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.pnames, cn.echo) // Reset parent node - cn.typ = stype + cn.kind = skind cn.label = cn.prefix[0] cn.prefix = cn.prefix[:l] cn.children = nil - cn.handler = nil + cn.methodHandler = new(methodHandler) cn.pnames = nil cn.echo = nil @@ -136,13 +133,14 @@ func (r *Router) insert(method, path string, h HandlerFunc, t ntype, pnames []st if l == sl { // At parent node - cn.typ = t - cn.handler = h + cn.kind = t + cn.addHandler(method, h) cn.pnames = pnames cn.echo = e } else { // Create child node - n = newNode(t, search[l:], cn, nil, h, pnames, e) + n = newNode(t, search[l:], cn, nil, new(methodHandler), pnames, e) + n.addHandler(method, h) cn.addChild(n) } } else if l < sl { @@ -154,12 +152,13 @@ func (r *Router) insert(method, path string, h HandlerFunc, t ntype, pnames []st continue } // Create child node - n := newNode(t, search, cn, nil, h, pnames, e) + n := newNode(t, search, cn, nil, new(methodHandler), pnames, e) + n.addHandler(method, h) cn.addChild(n) } else { // Node already exists if h != nil { - cn.handler = h + cn.addHandler(method, h) cn.pnames = pnames cn.echo = e } @@ -168,16 +167,16 @@ func (r *Router) insert(method, path string, h HandlerFunc, t ntype, pnames []st } } -func newNode(t ntype, pre string, p *node, c children, h HandlerFunc, pnames []string, e *Echo) *node { +func newNode(t kind, pre string, p *node, c children, mh *methodHandler, pnames []string, e *Echo) *node { return &node{ - typ: t, - label: pre[0], - prefix: pre, - parent: p, - children: c, - handler: h, - pnames: pnames, - echo: e, + kind: t, + label: pre[0], + prefix: pre, + parent: p, + children: c, + methodHandler: mh, + pnames: pnames, + echo: e, } } @@ -185,9 +184,9 @@ func (n *node) addChild(c *node) { n.children = append(n.children, c) } -func (n *node) findChild(l byte, t ntype) *node { +func (n *node) findChild(l byte, t kind) *node { for _, c := range n.children { - if c.label == l && c.typ == t { + if c.label == l && c.kind == t { return c } } @@ -203,85 +202,67 @@ func (n *node) findChildWithLabel(l byte) *node { return nil } -func (n *node) findChildWithType(t ntype) *node { +func (n *node) findChildByKind(t kind) *node { for _, c := range n.children { - if c.typ == t { + if c.kind == t { return c } } return nil } -func (r *Router) findTree(method string) (n *node) { - switch method[0] { - case 'G': // GET - m := uint32(method[2])<<8 | uint32(method[1])<<16 | uint32(method[0])<<24 - if m == 0x47455400 { - n = r.getTree - } - case 'P': // POST, PUT or PATCH - switch method[1] { - case 'O': // POST - m := uint32(method[3]) | uint32(method[2])<<8 | uint32(method[1])<<16 | - uint32(method[0])<<24 - if m == 0x504f5354 { - n = r.postTree - } - case 'U': // PUT - m := uint32(method[2])<<8 | uint32(method[1])<<16 | uint32(method[0])<<24 - if m == 0x50555400 { - n = r.putTree - } - case 'A': // PATCH - m := uint64(method[4])<<24 | uint64(method[3])<<32 | uint64(method[2])<<40 | - uint64(method[1])<<48 | uint64(method[0])<<56 - if m == 0x5041544348000000 { - n = r.patchTree - } - } - case 'D': // DELETE - m := uint64(method[5])<<16 | uint64(method[4])<<24 | uint64(method[3])<<32 | - uint64(method[2])<<40 | uint64(method[1])<<48 | uint64(method[0])<<56 - if m == 0x44454c4554450000 { - n = r.deleteTree - } - case 'C': // CONNECT - m := uint64(method[6])<<8 | uint64(method[5])<<16 | uint64(method[4])<<24 | - uint64(method[3])<<32 | uint64(method[2])<<40 | uint64(method[1])<<48 | - uint64(method[0])<<56 - if m == 0x434f4e4e45435400 { - n = r.connectTree - } - case 'H': // HEAD - m := uint32(method[3]) | uint32(method[2])<<8 | uint32(method[1])<<16 | - uint32(method[0])<<24 - if m == 0x48454144 { - n = r.headTree - } - case 'O': // OPTIONS - m := uint64(method[6])<<8 | uint64(method[5])<<16 | uint64(method[4])<<24 | - uint64(method[3])<<32 | uint64(method[2])<<40 | uint64(method[1])<<48 | - uint64(method[0])<<56 - if m == 0x4f5054494f4e5300 { - n = r.optionsTree - } - case 'T': // TRACE - m := uint64(method[4])<<24 | uint64(method[3])<<32 | uint64(method[2])<<40 | - uint64(method[1])<<48 | uint64(method[0])<<56 - if m == 0x5452414345000000 { - n = r.traceTree - } +func (n *node) addHandler(method string, h HandlerFunc) { + switch method { + case GET: + n.methodHandler.get = h + case POST: + n.methodHandler.post = h + case PUT: + n.methodHandler.put = h + case DELETE: + n.methodHandler.delete = h + case PATCH: + n.methodHandler.patch = h + case OPTIONS: + n.methodHandler.delete = h + case HEAD: + n.methodHandler.head = h + case CONNECT: + n.methodHandler.connect = h + case TRACE: + n.methodHandler.trace = h + } +} + +func (n *node) findHandler(method string) HandlerFunc { + switch method { + case GET: + return n.methodHandler.get + case POST: + return n.methodHandler.post + case PUT: + return n.methodHandler.put + case DELETE: + return n.methodHandler.delete + case PATCH: + return n.methodHandler.patch + case OPTIONS: + return n.methodHandler.delete + case HEAD: + return n.methodHandler.head + case CONNECT: + return n.methodHandler.connect + case TRACE: + return n.methodHandler.trace + default: + return nil } - return } func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo) { h = notFoundHandler - cn := r.findTree(method) // Current node as root - if cn == nil { - h = badRequestHandler - return - } + e = r.echo + cn := r.tree // Current node as root // Strip trailing slash if r.echo.stripTrailingSlash { @@ -295,7 +276,7 @@ func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo search = path c *node // Child node n int // Param counter - nt ntype // Next type + nk kind // Next kind nn *node // Next node ns string // Next search ) @@ -303,7 +284,7 @@ func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo // Search order static > param > match-any for { if search == "" { - goto Found + goto End } pl := 0 // Prefix length @@ -328,9 +309,9 @@ func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo } else { cn = nn search = ns - if nt == ptype { + if nk == pkind { goto Param - } else if nt == mtype { + } else if nk == mkind { goto MatchAny } else { // Not found @@ -339,23 +320,15 @@ func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo } if search == "" { - if cn.handler == nil { - // Look up for match-any, might have an empty value for *, e.g. - // serving a directory. Issue #207 - if cn = cn.findChildWithType(mtype); cn == nil { - return - } - ctx.pvalues[len(cn.pnames)-1] = "" - } - goto Found + goto End } // Static node - c = cn.findChild(search[0], stype) + c = cn.findChild(search[0], skind) if c != nil { // Save next if cn.label == '/' { - nt = ptype + nk = pkind nn = cn ns = search } @@ -365,11 +338,11 @@ func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo // Param node Param: - c = cn.findChildWithType(ptype) + c = cn.findChildByKind(pkind) if c != nil { // Save next if cn.label == '/' { - nt = mtype + nk = mkind nn = cn ns = search } @@ -386,18 +359,30 @@ func (r *Router) Find(method, path string, ctx *Context) (h HandlerFunc, e *Echo // Match-any node MatchAny: // c = cn.getChild() - if cn = cn.findChildWithType(mtype); cn == nil { + if cn = cn.findChildByKind(mkind); cn == nil { // Not found return } ctx.pvalues[len(cn.pnames)-1] = search - goto Found + goto End } -Found: +End: ctx.pnames = cn.pnames - h = cn.handler - e = cn.echo + h = cn.findHandler(method) + if cn.echo != nil { + e = cn.echo + } + if h == nil { + // Dig further for match-any, might have an empty value for *, e.g. + // serving a directory. Issue #207. + if cn = cn.findChildByKind(mkind); cn == nil { + h = notFoundHandler + return + } + h = cn.findHandler(method) + ctx.pvalues[len(cn.pnames)-1] = "" + } return } diff --git a/router_test.go b/router_test.go index 40a1e62b..c3357223 100644 --- a/router_test.go +++ b/router_test.go @@ -567,16 +567,6 @@ func TestRouterAPI(t *testing.T) { } } -func TestRouterAddInvalidMethod(t *testing.T) { - e := New() - r := e.router - assert.Panics(t, func() { - r.Add("INVALID", "/", func(*Context) error { - return nil - }, e) - }) -} - func TestRouterServeHTTP(t *testing.T) { e := New() r := e.router @@ -600,7 +590,7 @@ func TestRouterServeHTTP(t *testing.T) { func (n *node) printTree(pfx string, tail bool) { p := prefix(tail, pfx, "└── ", "├── ") - fmt.Printf("%s%s, %p: type=%d, parent=%p, handler=%v\n", p, n.prefix, n, n.typ, n.parent, n.handler) + fmt.Printf("%s%s, %p: type=%d, parent=%p, handler=%v\n", p, n.prefix, n, n.kind, n.parent, n.methodHandler) children := n.children l := len(children) diff --git a/website/content/recipes/file-upload.md b/website/content/recipes/file-upload.md index 18820f17..0bc8466f 100644 --- a/website/content/recipes/file-upload.md +++ b/website/content/recipes/file-upload.md @@ -2,7 +2,7 @@ title: File Upload menu: main: - parent: recipes + parent: recipes --- - Multipart/form-data file upload @@ -94,7 +94,10 @@ func main() { - ``` +## Maintainers + +- [vishr](http://github.com/vishr) + ## [Source Code](https://github.com/labstack/echo/blob/master/recipes/file-upload) diff --git a/website/content/recipes/google-app-engine.md b/website/content/recipes/google-app-engine.md index fe0d3f82..77bccfa3 100644 --- a/website/content/recipes/google-app-engine.md +++ b/website/content/recipes/google-app-engine.md @@ -258,5 +258,4 @@ switching between AppEngine provided service such as Datastore and alternative s such as MongoDB. A combination of go interfaces and build constraints can make this fairly straightforward but is outside the scope of this recipe. - ## [Source Code](https://github.com/labstack/echo/blob/master/recipes/google-app-engine) diff --git a/website/content/recipes/graceful-shutdown.md b/website/content/recipes/graceful-shutdown.md index 9de30807..09dc14fc 100644 --- a/website/content/recipes/graceful-shutdown.md +++ b/website/content/recipes/graceful-shutdown.md @@ -56,6 +56,10 @@ func main() { } ``` +## Maintainers + +- (mertenvg)[https://github.com/mertenvg] + ## Source Code [`graceful`](https://github.com/labstack/echo/blob/master/recipes/graceful-shutdown/graceful) diff --git a/website/content/recipes/jwt-authentication.md b/website/content/recipes/jwt-authentication.md index b88cc8d4..29a94008 100644 --- a/website/content/recipes/jwt-authentication.md +++ b/website/content/recipes/jwt-authentication.md @@ -153,4 +153,8 @@ func main() { $ curl localhost:1323/restricted -H "Authorization: Bearer " => Access granted with JWT. ``` +## Maintainers + +- [axdg](http://github.com/axdg) + ## [Source Code](https://github.com/labstack/echo/blob/master/recipes/jwt-authentication) diff --git a/website/content/recipes/streaming-file-upload.md b/website/content/recipes/streaming-file-upload.md index ae23a9d7..e74e92b2 100644 --- a/website/content/recipes/streaming-file-upload.md +++ b/website/content/recipes/streaming-file-upload.md @@ -118,4 +118,8 @@ func main() { ``` +## Maintainers + +- [vishr](http://github.com/vishr) + ## [Source Code](https://github.com/labstack/echo/blob/master/recipes/streaming-file-upload) diff --git a/website/content/recipes/streaming-response.md b/website/content/recipes/streaming-response.md index aad25407..e8669c19 100644 --- a/website/content/recipes/streaming-response.md +++ b/website/content/recipes/streaming-response.md @@ -76,4 +76,8 @@ $ curl localhost:1323 {"Altitude":15,"Latitude":37.77493,"Longitude":-122.419416} ``` +## Maintainers + +- [vishr](http://github.com/vishr) + ## [Source Code](https://github.com/labstack/echo/blob/master/recipes/streaming-response) diff --git a/website/content/recipes/subdomains.md b/website/content/recipes/subdomains.md index d7eca90e..e2a4eb5b 100644 --- a/website/content/recipes/subdomains.md +++ b/website/content/recipes/subdomains.md @@ -77,4 +77,9 @@ func main() { } ``` +## Maintainers + +- [axdg](http://github.com/axdg) +- [vishr](http://github.com/axdg) + ## [Source Code](https://github.com/labstack/echo/blob/master/recipes/subdomains) diff --git a/website/content/recipes/websocket.md b/website/content/recipes/websocket.md index edb4322f..310bc680 100644 --- a/website/content/recipes/websocket.md +++ b/website/content/recipes/websocket.md @@ -112,4 +112,8 @@ Hello, Server! Hello, Server! ``` +## Maintainers + +- [vishr](http://github.com/vishr) + ## [Source Code](https://github.com/labstack/echo/blob/master/recipes/websocket) diff --git a/website/static/styles/echo.css b/website/static/styles/echo.css index d1dacab3..67a09e84 100644 --- a/website/static/styles/echo.css +++ b/website/static/styles/echo.css @@ -20,8 +20,13 @@ code { color: #424242; font-size: .95em; font-family: Source Code Pro, Monaco, Menlo, Consolas, monospace; +<<<<<<< HEAD + border: 1px solid #E0E0E0; + border-radius: 2px; +======= border: 1px solid #BDBDBD; border-radius: 4px; +>>>>>>> b6f754b2a2be04115d36d30f8df86c00a3208784 } a:link { text-decoration: none; @@ -34,13 +39,22 @@ a:link { .page-content header { padding-bottom: 16px; } +.menu { + margin-right: 40px; +} .menu a { display: block; color: #757575; padding: 5px; +<<<<<<< HEAD + border-left:4px #F06292 solid; +} +.menu a:hover:not(.active) { +======= border-left:2px #F06292 solid; } .menu a:hover { +>>>>>>> b6f754b2a2be04115d36d30f8df86c00a3208784 background-color: #E0E0E0; } .menu .active {