1
0
mirror of https://github.com/labstack/echo.git synced 2025-03-23 21:29:26 +02:00

Merge pull request #45 from labstack/develop

This commit is contained in:
Vishal Rana 2015-04-28 23:12:06 -07:00
commit fd30053875
8 changed files with 259 additions and 85 deletions

View File

@ -16,8 +16,9 @@ Echo is a fast HTTP router (zero memory allocation) and micro web framework in G
- `func(http.ResponseWriter, *http.Request)` - `func(http.ResponseWriter, *http.Request)`
- `func(http.ResponseWriter, *http.Request) error` - `func(http.ResponseWriter, *http.Request) error`
- Handler - Handler
- `func(*echo.Context)` - `echo.HandlerFunc`
- `func(*echo.Context) error` - `func(*echo.Context) error`
- `func(*echo.Context)`
- `http.Handler` - `http.Handler`
- `http.HandlerFunc` - `http.HandlerFunc`
- `func(http.ResponseWriter, *http.Request)` - `func(http.ResponseWriter, *http.Request)`
@ -110,7 +111,7 @@ func main() {
## Contribute ## Contribute
**Use issues for everything** **Use issues for everything**
- Report problems - Report problems
- Discuss before sending pull request - Discuss before sending pull request
- Suggest new features - Suggest new features

View File

@ -20,8 +20,9 @@ type (
) )
// P returns path parameter by index. // P returns path parameter by index.
func (c *Context) P(i int) (value string) { func (c *Context) P(i uint8) (value string) {
if i <= len(c.pnames) { l := uint8(len(c.pnames))
if i <= l {
value = c.pvalues[i] value = c.pvalues[i]
} }
return return
@ -64,19 +65,19 @@ func (c *Context) JSON(code int, v interface{}) error {
} }
// String sends a text/plain response with status code. // String sends a text/plain response with status code.
func (c *Context) String(code int, s string) (err error) { func (c *Context) String(code int, s string) error {
c.Response.Header().Set(HeaderContentType, MIMEText+"; charset=utf-8") c.Response.Header().Set(HeaderContentType, MIMEText+"; charset=utf-8")
c.Response.WriteHeader(code) c.Response.WriteHeader(code)
_, err = c.Response.Write([]byte(s)) _, err := c.Response.Write([]byte(s))
return return err
} }
// HTML sends a text/html response with status code. // HTML sends a text/html response with status code.
func (c *Context) HTML(code int, html string) (err error) { func (c *Context) HTML(code int, html string) error {
c.Response.Header().Set(HeaderContentType, MIMEHTML+"; charset=utf-8") c.Response.Header().Set(HeaderContentType, MIMEHTML+"; charset=utf-8")
c.Response.WriteHeader(code) c.Response.WriteHeader(code)
_, err = c.Response.Write([]byte(html)) _, err := c.Response.Write([]byte(html))
return return err
} }
// NoContent sends a response with no body and a status code. // NoContent sends a response with no body and a status code.

22
echo.go
View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"io" "io"
"log" "log"
"net/http" "net/http"
@ -143,7 +144,7 @@ func New() (e *Echo) {
return return
} }
// Group creates a new sub router with prefix and inherits all properties from // Group creates a new sub router with prefix. It inherits all properties from
// the parent. Passing middleware overrides parent middleware. // the parent. Passing middleware overrides parent middleware.
func (e *Echo) Group(pfx string, m ...Middleware) *Echo { func (e *Echo) Group(pfx string, m ...Middleware) *Echo {
g := *e g := *e
@ -155,13 +156,14 @@ func (e *Echo) Group(pfx string, m ...Middleware) *Echo {
return &g return &g
} }
// MaxParam sets the maximum allowed path parameters. Default is 5, good enough // MaxParam sets the maximum number of path parameters allowd for the application.
// for many users. // Default value is 5, good enough for many use cases.
func (e *Echo) MaxParam(n uint8) { func (e *Echo) MaxParam(n uint8) {
e.maxParam = n e.maxParam = n
} }
// NotFoundHandler registers a custom NotFound handler. // NotFoundHandler registers a custom NotFound handler used by router in case it
// doesn't find any registered handler for HTTP method and path.
func (e *Echo) NotFoundHandler(h Handler) { func (e *Echo) NotFoundHandler(h Handler) {
e.notFoundHandler = wrapH(h) e.notFoundHandler = wrapH(h)
} }
@ -235,7 +237,7 @@ func (e *Echo) Trace(path string, h Handler) {
} }
// URI generates a URI from handler. // URI generates a URI from handler.
func (e *Echo) URI(h Handler, params ...string) string { func (e *Echo) URI(h Handler, params ...interface{}) string {
uri := new(bytes.Buffer) uri := new(bytes.Buffer)
lp := len(params) lp := len(params)
n := 0 n := 0
@ -245,7 +247,7 @@ func (e *Echo) URI(h Handler, params ...string) string {
if path[i] == ':' && n < lp { if path[i] == ':' && n < lp {
for ; i < l && path[i] != '/'; i++ { for ; i < l && path[i] != '/'; i++ {
} }
uri.WriteString(params[n]) uri.WriteString(fmt.Sprintf("%v", params[n]))
n++ n++
} }
if i < l { if i < l {
@ -257,7 +259,7 @@ func (e *Echo) URI(h Handler, params ...string) string {
} }
// URL is an alias for URI // URL is an alias for URI
func (e *Echo) URL(h Handler, params ...string) string { func (e *Echo) URL(h Handler, params ...interface{}) string {
return e.URI(h, params...) return e.URI(h, params...)
} }
@ -398,13 +400,15 @@ func wrapM(m Middleware) MiddlewareFunc {
// wraps Handler // wraps Handler
func wrapH(h Handler) HandlerFunc { func wrapH(h Handler) HandlerFunc {
switch h := h.(type) { switch h := h.(type) {
case HandlerFunc:
return h
case func(*Context) error:
return h
case func(*Context): case func(*Context):
return func(c *Context) error { return func(c *Context) error {
h(c) h(c)
return nil return nil
} }
case func(*Context) error:
return h
case http.Handler, http.HandlerFunc: case http.Handler, http.HandlerFunc:
return func(c *Context) error { return func(c *Context) error {
h.(http.Handler).ServeHTTP(c.Response, c.Request) h.(http.Handler).ServeHTTP(c.Response, c.Request)

View File

@ -121,7 +121,7 @@ func TestEchoMiddleware(t *testing.T) {
func TestEchoHandler(t *testing.T) { func TestEchoHandler(t *testing.T) {
e := New() e := New()
// func(*echo.Context) // func(*echo.Context) error
e.Get("/1", func(c *Context) { e.Get("/1", func(c *Context) {
c.String(http.StatusOK, "1") c.String(http.StatusOK, "1")
}) })
@ -132,7 +132,7 @@ func TestEchoHandler(t *testing.T) {
t.Error("body should be 1") t.Error("body should be 1")
} }
// func(*echo.Context) error // HandlerFunc
e.Get("/2", func(c *Context) { e.Get("/2", func(c *Context) {
c.String(http.StatusOK, "2") c.String(http.StatusOK, "2")
}) })
@ -143,10 +143,10 @@ func TestEchoHandler(t *testing.T) {
t.Error("body should be 2") t.Error("body should be 2")
} }
// http.Handler/http.HandlerFunc // func(*echo.Context)
e.Get("/3", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { e.Get("/3", func(c *Context) {
w.Write([]byte("3")) c.String(http.StatusOK, "3")
})) })
w = httptest.NewRecorder() w = httptest.NewRecorder()
r, _ = http.NewRequest(GET, "/3", nil) r, _ = http.NewRequest(GET, "/3", nil)
e.ServeHTTP(w, r) e.ServeHTTP(w, r)
@ -154,10 +154,10 @@ func TestEchoHandler(t *testing.T) {
t.Error("body should be 3") t.Error("body should be 3")
} }
// func(http.ResponseWriter, *http.Request) // http.Handler/http.HandlerFunc
e.Get("/4", func(w http.ResponseWriter, r *http.Request) { e.Get("/4", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("4")) w.Write([]byte("4"))
}) }))
w = httptest.NewRecorder() w = httptest.NewRecorder()
r, _ = http.NewRequest(GET, "/4", nil) r, _ = http.NewRequest(GET, "/4", nil)
e.ServeHTTP(w, r) e.ServeHTTP(w, r)
@ -165,10 +165,9 @@ func TestEchoHandler(t *testing.T) {
t.Error("body should be 4") t.Error("body should be 4")
} }
// func(http.ResponseWriter, *http.Request) error // func(http.ResponseWriter, *http.Request)
e.Get("/5", func(w http.ResponseWriter, r *http.Request) error { e.Get("/5", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("5")) w.Write([]byte("5"))
return nil
}) })
w = httptest.NewRecorder() w = httptest.NewRecorder()
r, _ = http.NewRequest(GET, "/5", nil) r, _ = http.NewRequest(GET, "/5", nil)
@ -176,6 +175,18 @@ func TestEchoHandler(t *testing.T) {
if w.Body.String() != "5" { if w.Body.String() != "5" {
t.Error("body should be 5") t.Error("body should be 5")
} }
// func(http.ResponseWriter, *http.Request) error
e.Get("/6", func(w http.ResponseWriter, r *http.Request) error {
w.Write([]byte("6"))
return nil
})
w = httptest.NewRecorder()
r, _ = http.NewRequest(GET, "/6", nil)
e.ServeHTTP(w, r)
if w.Body.String() != "6" {
t.Error("body should be 6")
}
} }
func TestEchoGroup(t *testing.T) { func TestEchoGroup(t *testing.T) {

View File

@ -13,11 +13,11 @@ type (
prefix string prefix string
parent *node parent *node
children children children children
pchild *node // Param child // pchild *node // Param child
cchild *node // Catch-all child // mchild *node // Match-any child
handler HandlerFunc handler HandlerFunc
pnames []string pnames []string
echo *Echo echo *Echo
} }
ntype uint8 ntype uint8
children []*node children []*node
@ -26,7 +26,7 @@ type (
const ( const (
stype ntype = iota stype ntype = iota
ptype ptype
ctype mtype
) )
func NewRouter(e *Echo) (r *router) { func NewRouter(e *Echo) (r *router) {
@ -64,9 +64,8 @@ func (r *router) Add(method, path string, h HandlerFunc, echo *Echo) {
} }
r.insert(method, path[:i], nil, ptype, pnames, echo) r.insert(method, path[:i], nil, ptype, pnames, echo)
} else if path[i] == '*' { } else if path[i] == '*' {
r.insert(method, path[:i], nil, stype, nil, echo)
pnames = append(pnames, "_name") pnames = append(pnames, "_name")
r.insert(method, path[:l], h, ctype, pnames, echo) r.insert(method, path[:i], h, mtype, pnames, echo)
return return
} }
} }
@ -201,7 +200,7 @@ func (n *node) findPchild() *node {
func (n *node) findCchild() *node { func (n *node) findCchild() *node {
for _, c := range n.children { for _, c := range n.children {
if c.typ == ctype { if c.typ == mtype {
return c return c
} }
} }
@ -226,16 +225,21 @@ func (r *router) Find(method, path string, ctx *Context) (h HandlerFunc, echo *E
c := new(node) // Child node c := new(node) // Child node
n := 0 // Param counter n := 0 // Param counter
// Search order static > param > catch-all // Search order static > param > match-any
for { for {
if search == "" || search == cn.prefix { if search == "" || search == cn.prefix || cn.typ == mtype {
if cn.handler != nil { // Found
// Found h = cn.handler
h = cn.handler echo = cn.echo
ctx.pnames = cn.pnames ctx.pnames = cn.pnames
echo = cn.echo
return // Match-any
if cn.typ == mtype {
println(search, cn.prefix)
ctx.pvalues[0] = search[len(cn.prefix):]
} }
return
} }
pl := len(cn.prefix) pl := len(cn.prefix)
@ -247,11 +251,6 @@ func (r *router) Find(method, path string, ctx *Context) (h HandlerFunc, echo *E
goto Up goto Up
} }
// Catch-all with empty value
if len(search) == 0 {
goto CatchAll
}
// Static node // Static node
c = cn.findSchild(search[0]) c = cn.findSchild(search[0])
if c != nil { if c != nil {
@ -274,17 +273,6 @@ func (r *router) Find(method, path string, ctx *Context) (h HandlerFunc, echo *E
continue continue
} }
// Catch-all node
CatchAll:
// c = cn.cchild
c = cn.findCchild()
if c != nil {
cn = c
ctx.pvalues[n] = search
search = "" // End search
continue
}
Up: Up:
tn := cn // Save current node tn := cn // Save current node
cn = cn.parent cn = cn.parent

View File

@ -326,19 +326,24 @@ func TestRouterTwoParam(t *testing.T) {
return nil return nil
}, nil) }, nil)
h, _ := r.Find(GET, "/users/1/files/1", context) // h, _ := r.Find(GET, "/users/1/files/1", context)
if h == nil { // if h == nil {
t.Fatal("handler not found") // t.Fatal("handler not found")
} // }
if context.pvalues[0] != "1" { // if context.pvalues[0] != "1" {
t.Error("param uid should be 1") // t.Error("param uid should be 1")
} // }
if context.pvalues[1] != "1" { // if context.pvalues[1] != "1" {
t.Error("param fid should be 1") // t.Error("param fid should be 1")
// }
h, _ := r.Find(GET, "/users/1", context)
if h != nil {
t.Error("should not found handler")
} }
} }
func TestRouterCatchAll(t *testing.T) { func TestRouterMatchAny(t *testing.T) {
r := New().Router r := New().Router
r.Add(GET, "/users/*", func(*Context) error { r.Add(GET, "/users/*", func(*Context) error {
return nil return nil
@ -349,6 +354,7 @@ func TestRouterCatchAll(t *testing.T) {
t.Fatal("handler not found") t.Fatal("handler not found")
} }
if context.pvalues[0] != "" { if context.pvalues[0] != "" {
println(context.pvalues[0])
t.Error("value should be joe") t.Error("value should be joe")
} }

View File

@ -1,6 +1,6 @@
# Guide # Guide
<!--- <!---
Some info about guide Some info about guide
--> -->
@ -25,22 +25,184 @@ $ go get -u github.com/labstack/echo
Echo follows [Semantic Versioning](http://semver.org) managed through GitHub releases. Echo follows [Semantic Versioning](http://semver.org) managed through GitHub releases.
Specific version of Echo can be installed using any [package manager](https://github.com/avelino/awesome-go#package-management). Specific version of Echo can be installed using any [package manager](https://github.com/avelino/awesome-go#package-management).
## Configuration ## Customization
echo.MaxParam ### Max path parameters
echo.NotFoundHandler `echo.MaxParam(n uint8)`
echo.HTTPErrorHandler Sets the maximum number of path parameters allowed for the application.
Default value is **5**, [good enough](https://github.com/interagent/http-api-design#minimize-path-nesting)
for many use cases. Restricting path parameters allows us to use memory efficiently.
### Not found handler
`echo.NotFoundHandler(h Handler)`
Registers a custom NotFound handler. This handler is called in case router doesn't
find matching route for the request.
Default handler sends 404 "Not Found" response.
### HTTP error handler
`echo.HTTPErrorHandler(h HTTPErrorHandler)`
Registers a centralized HTTP error handler.
Default http error handler sends 500 "Internal Server Error" response.
## Routing ## Routing
## Request Echo's router is [fast, optimized](https://github.com/labstack/echo#benchmark) and
flexible. It's based on [redix tree](http://en.wikipedia.org/wiki/Radix_tree)
data structure which makes routing lookup really fast. It leverages
[sync pool](https://golang.org/pkg/sync/#Pool) to reuse memory and achieve
zero dynamic memory allocation with no garbage collection.
## Middleware Routes can be registered for any HTTP method, path and handler. For example, code
below registers a route for method `GET`, path `/hello` and a handler which sends
`Hello!` response.
```go
echo.Get("/hello", func(*echo.Context) {
c.String(http.StatusOK, "Hello!")
})
```
Echo's default handler is `func(*echo.Context) error` where `echo.Context` primarily
holds request and response objects. Echo also has a support for other types of
handlers.
<!-- TODO mention about not able to take advantage -->
<!-- ### Groups -->
### Path parameters
URL path parameters can be extracted either by name `echo.Context.Param(name string) string` or by
index `echo.Context.P(i uint8) string`. Getting parameter by index gives a slightly
better performance.
```go
echo.Get("/users/:id", func(c *echo.Context) {
// By name
id := c.Param("id")
// By index
id := c.P(0)
c.String(http.StatusOK, id)
})
```
### Match-any
Matches zero or more characters in the path. For example, pattern `/users/*` will
match
- `/users/`
- `/users/1`
- `/users/1/files/1`
- `/users/anything...`
### Path matching order
- Static
- Param
- Match any
#### Example
```go
e.Get("/users/:id", func(c *echo.Context) {
c.String(http.StatusOK, "/users/:id")
})
e.Get("/users/new", func(c *echo.Context) {
c.String(http.StatusOK, "/users/new")
})
e.Get("/users/1/files/*", func(c *echo.Context) {
c.String(http.StatusOK, "/users/1/files/*")
})
```
Above routes would resolve in order
- `/users/new`
- `/users/:id`
- `/users/1/files/*`
Routes can be written in any order.
<!-- Different use cases -->
### URI building
`echo.URI` can be used generate URI for any handler with specified path parameters.
It's helpful to centralize all your URI patterns which ease in refactoring your
application.
`echo.URI(h, 1)` will generate `/users/1` for the route registered below
```go
// Handler
h := func(*echo.Context) {
c.String(http.StatusOK, "OK")
}
// Route
e.Get("/users/:id", h)
```
<!-- ## Middleware -->
## Response ## Response
## Static Content ### JSON
## Error Handling `context.JSON(code int, v interface{}) error` can be used to send a JSON response
with status code.
### String
`context.String(code int, s string) error` can be used to send plain text response
with status code.
### HTML
`func (c *Context) HTML(code int, html string) error` can be used to send an HTML
response with status code.
### Static files
`echo.Static(path, root string)` can be used to serve static files. For example,
code below serves all files from `public/scripts` directory for any path starting
with `/scripts/`.
```go
e.Static("/scripts", "public/scripts")
```
### Serving a file
`echo.ServeFile(path, file string)` can be used to serve a file. For example, code
below serves welcome.html for path `/welcome`.
```go
e.ServeFile("/welcome", "welcome.html")
```
### Serving an index file
`echo.Index(file string)` can be used to serve index file. For example, code below
serves index.html for path `/`.
```go
e.Index("index.html")
```
<!-- ## Error Handling -->
<!-- Deployment -->

View File

@ -22,8 +22,9 @@ Echo is a fast HTTP router (zero memory allocation) and micro web framework in G
- `func(http.ResponseWriter, *http.Request)` - `func(http.ResponseWriter, *http.Request)`
- `func(http.ResponseWriter, *http.Request) error` - `func(http.ResponseWriter, *http.Request) error`
- Handler - Handler
- `func(*echo.Context)` - `echo.HandlerFunc`
- `func(*echo.Context) error` - `func(*echo.Context) error`
- `func(*echo.Context)`
- `http.Handler` - `http.Handler`
- `http.HandlerFunc` - `http.HandlerFunc`
- `func(http.ResponseWriter, *http.Request)` - `func(http.ResponseWriter, *http.Request)`
@ -112,7 +113,7 @@ Hello, World! on the page.
## Contribute ## Contribute
**Use issues for everything** **Use issues for everything**
- Report problems - Report problems
- Discuss before sending pull request - Discuss before sending pull request
- Suggest new features - Suggest new features