1
0
mirror of https://github.com/labstack/echo.git synced 2024-12-24 20:14:31 +02:00

Merge pull request #76 from labstack/websocket

New definition for Echo.HandlerFunc
This commit is contained in:
Vishal Rana 2015-05-20 16:39:46 -07:00
commit 1e117621e9
23 changed files with 267 additions and 215 deletions

View File

@ -9,14 +9,14 @@ Echo is a fast HTTP router (zero memory allocation) and micro web framework in G
- `echo.MiddlewareFunc`
- `func(echo.HandlerFunc) echo.HandlerFunc`
- `echo.HandlerFunc`
- `func(*echo.Context) *echo.HTTPError`
- `func(*echo.Context) error`
- `func(http.Handler) http.Handler`
- `http.Handler`
- `http.HandlerFunc`
- `func(http.ResponseWriter, *http.Request)`
- Handler
- `echo.HandlerFunc`
- `func(*echo.Context) *echo.HTTPError`
- `func(*echo.Context) error`
- `http.Handler`
- `http.HandlerFunc`
- `func(http.ResponseWriter, *http.Request)`
@ -84,7 +84,7 @@ import (
)
// Handler
func hello(c *echo.Context) *echo.HTTPError {
func hello(c *echo.Context) error {
return c.String(http.StatusOK, "Hello, World!\n")
}
@ -133,34 +133,34 @@ var (
// Handlers
//----------
func createUser(c *echo.Context) *echo.HTTPError {
func createUser(c *echo.Context) error {
u := &user{
ID: seq,
}
if he := c.Bind(u); he != nil {
return he
if err := c.Bind(u); err != nil {
return err
}
users[u.ID] = u
seq++
return c.JSON(http.StatusCreated, u)
}
func getUser(c *echo.Context) *echo.HTTPError {
func getUser(c *echo.Context) error {
id, _ := strconv.Atoi(c.Param("id"))
return c.JSON(http.StatusOK, users[id])
}
func updateUser(c *echo.Context) *echo.HTTPError {
func updateUser(c *echo.Context) error {
u := new(user)
if he := c.Bind(u); he != nil {
return he
if err := c.Bind(u); err != nil {
return err
}
id, _ := strconv.Atoi(c.Param("id"))
users[id].Name = u.Name
return c.JSON(http.StatusOK, users[id])
}
func deleteUser(c *echo.Context) *echo.HTTPError {
func deleteUser(c *echo.Context) error {
id, _ := strconv.Atoi(c.Param("id"))
delete(users, id)
return c.NoContent(http.StatusNoContent)
@ -218,22 +218,19 @@ var (
)
// Render HTML
func (t *Template) Render(w io.Writer, name string, data interface{}) *echo.HTTPError {
if err := t.templates.ExecuteTemplate(w, name, data); err != nil {
return &echo.HTTPError{Error: err}
}
return nil
func (t *Template) Render(w io.Writer, name string, data interface{}) error {
return t.templates.ExecuteTemplate(w, name, data)
}
//----------
// Handlers
//----------
func welcome(c *echo.Context) *echo.HTTPError {
func welcome(c *echo.Context) error {
return c.Render(http.StatusOK, "welcome", "Joe")
}
func createUser(c *echo.Context) *echo.HTTPError {
func createUser(c *echo.Context) error {
u := new(user)
if err := c.Bind(u); err != nil {
return err
@ -242,11 +239,11 @@ func createUser(c *echo.Context) *echo.HTTPError {
return c.JSON(http.StatusCreated, u)
}
func getUsers(c *echo.Context) *echo.HTTPError {
func getUsers(c *echo.Context) error {
return c.JSON(http.StatusOK, users)
}
func getUser(c *echo.Context) *echo.HTTPError {
func getUser(c *echo.Context) error {
return c.JSON(http.StatusOK, users[c.P(0)])
}
@ -268,7 +265,7 @@ func main() {
s := stats.New()
e.Use(s.Handler)
// Route
e.Get("/stats", func(c *echo.Context) *echo.HTTPError {
e.Get("/stats", func(c *echo.Context) error {
return c.JSON(http.StatusOK, s.Data())
})
@ -297,7 +294,7 @@ func main() {
// Cached templates
templates: template.Must(template.ParseFiles("public/views/welcome.html")),
}
e.Renderer(t)
e.SetRenderer(t)
e.Get("/welcome", welcome)
//-------
@ -306,20 +303,20 @@ func main() {
// Group with parent middleware
a := e.Group("/admin")
a.Use(func(c *echo.Context) *echo.HTTPError {
a.Use(func(c *echo.Context) error {
// Security middleware
return nil
})
a.Get("", func(c *echo.Context) *echo.HTTPError {
a.Get("", func(c *echo.Context) error {
return c.String(http.StatusOK, "Welcome admin!")
})
// Group with no parent middleware
g := e.Group("/files", func(c *echo.Context) *echo.HTTPError {
g := e.Group("/files", func(c *echo.Context) error {
// Security middleware
return nil
})
g.Get("", func(c *echo.Context) *echo.HTTPError {
g.Get("", func(c *echo.Context) error {
return c.String(http.StatusOK, "Your files!")
})
@ -350,7 +347,7 @@ import (
)
// Handler
func hello(c *echo.Context) *echo.HTTPError {
func hello(c *echo.Context) error {
return c.String(http.StatusOK, "Hello, World!\n")
}
@ -359,7 +356,7 @@ func main() {
e := echo.New()
// Debug mode
e.Debug(true)
e.SetDebug(true)
//------------
// Middleware

View File

@ -3,6 +3,8 @@ package echo
import (
"encoding/json"
"net/http"
"golang.org/x/net/websocket"
)
type (
@ -11,6 +13,7 @@ type (
Context struct {
Request *http.Request
Response *Response
Socket *websocket.Conn
pnames []string
pvalues []string
store store
@ -53,15 +56,15 @@ func (c *Context) Param(name string) (value string) {
// Bind binds the request body into specified type v. Default binder does it
// based on Content-Type header.
func (c *Context) Bind(i interface{}) *HTTPError {
func (c *Context) Bind(i interface{}) error {
return c.echo.binder(c.Request, i)
}
// Render invokes the registered HTML template renderer and sends a text/html
// response with status code.
func (c *Context) Render(code int, name string, data interface{}) *HTTPError {
func (c *Context) Render(code int, name string, data interface{}) error {
if c.echo.renderer == nil {
return &HTTPError{Error: RendererNotRegistered}
return RendererNotRegistered
}
c.Response.Header().Set(ContentType, TextHTML+"; charset=utf-8")
c.Response.WriteHeader(code)
@ -69,44 +72,37 @@ func (c *Context) Render(code int, name string, data interface{}) *HTTPError {
}
// JSON sends an application/json response with status code.
func (c *Context) JSON(code int, i interface{}) *HTTPError {
func (c *Context) JSON(code int, i interface{}) error {
c.Response.Header().Set(ContentType, ApplicationJSON+"; charset=utf-8")
c.Response.WriteHeader(code)
if err := json.NewEncoder(c.Response).Encode(i); err != nil {
return &HTTPError{Error: err}
}
return nil
return json.NewEncoder(c.Response).Encode(i)
}
// String sends a text/plain response with status code.
func (c *Context) String(code int, s string) *HTTPError {
func (c *Context) String(code int, s string) error {
c.Response.Header().Set(ContentType, TextPlain+"; charset=utf-8")
c.Response.WriteHeader(code)
if _, err := c.Response.Write([]byte(s)); err != nil {
return &HTTPError{Error: err}
}
return nil
_, err := c.Response.Write([]byte(s))
return err
}
// HTML sends a text/html response with status code.
func (c *Context) HTML(code int, html string) *HTTPError {
func (c *Context) HTML(code int, html string) error {
c.Response.Header().Set(ContentType, TextHTML+"; charset=utf-8")
c.Response.WriteHeader(code)
if _, err := c.Response.Write([]byte(html)); err != nil {
return &HTTPError{Error: err}
}
return nil
_, err := c.Response.Write([]byte(html))
return err
}
// NoContent sends a response with no body and a status code.
func (c *Context) NoContent(code int) *HTTPError {
func (c *Context) NoContent(code int) error {
c.Response.WriteHeader(code)
return nil
}
// Error invokes the registered HTTP error handler.
func (c *Context) Error(he *HTTPError) {
c.echo.httpErrorHandler(he, c)
func (c *Context) Error(err error) {
c.echo.httpErrorHandler(err, c)
}
// Get retrieves data from the context.

View File

@ -16,17 +16,14 @@ type (
}
)
func (t *Template) Render(w io.Writer, name string, data interface{}) *HTTPError {
if err := t.templates.ExecuteTemplate(w, name, data); err != nil {
return &HTTPError{Error: err}
}
return nil
func (t *Template) Render(w io.Writer, name string, data interface{}) error {
return t.templates.ExecuteTemplate(w, name, data)
}
func TestContext(t *testing.T) {
b, _ := json.Marshal(u1)
r, _ := http.NewRequest(POST, "/users/1", bytes.NewReader(b))
c := NewContext(r, &Response{Writer: httptest.NewRecorder()}, New())
c := NewContext(r, NewResponse(httptest.NewRecorder()), New())
//------
// Bind

96
echo.go
View File

@ -14,6 +14,7 @@ import (
"sync"
"github.com/mattn/go-colorable"
"golang.org/x/net/websocket"
)
type (
@ -33,24 +34,23 @@ type (
HTTPError struct {
Code int
Message string
Error error
}
Middleware interface{}
MiddlewareFunc func(HandlerFunc) HandlerFunc
Handler interface{}
HandlerFunc func(*Context) *HTTPError
HandlerFunc func(*Context) error
// HTTPErrorHandler is a centralized HTTP error handler.
HTTPErrorHandler func(*HTTPError, *Context)
HTTPErrorHandler func(error, *Context)
BindFunc func(*http.Request, interface{}) *HTTPError
BindFunc func(*http.Request, interface{}) error
// Renderer is the interface that wraps the Render method.
//
// Render renders the HTML template with given name and specified data.
// It writes the output to w.
Renderer interface {
Render(w io.Writer, name string, data interface{}) *HTTPError
Render(w io.Writer, name string, data interface{}) error
}
)
@ -120,6 +120,18 @@ var (
RendererNotRegistered = errors.New("echo ⇒ renderer not registered")
)
func NewHTTPError(code int, msgs ...string) *HTTPError {
he := &HTTPError{Code: code}
if len(msgs) == 0 {
he.Message = http.StatusText(code)
}
return he
}
func (e *HTTPError) Error() string {
return e.Message
}
// New creates an Echo instance.
func New() (e *Echo) {
e = &Echo{
@ -135,22 +147,22 @@ func New() (e *Echo) {
//----------
e.SetMaxParam(5)
e.notFoundHandler = func(c *Context) *HTTPError {
return &HTTPError{Code: http.StatusNotFound}
e.notFoundHandler = func(c *Context) error {
return NewHTTPError(http.StatusNotFound)
}
e.SetHTTPErrorHandler(func(he *HTTPError, c *Context) {
if he.Code == 0 {
he.Code = http.StatusInternalServerError
e.SetHTTPErrorHandler(func(err error, c *Context) {
code := http.StatusInternalServerError
msg := http.StatusText(code)
if he, ok := err.(*HTTPError); ok {
code = he.Code
msg = he.Message
}
if he.Message == "" {
he.Message = http.StatusText(he.Code)
if e.Debug() {
msg = err.Error()
}
if e.debug && he.Error != nil {
he.Message = he.Error.Error()
}
http.Error(c.Response, he.Message, he.Code)
http.Error(c.Response, msg, code)
})
e.SetBinder(func(r *http.Request, v interface{}) *HTTPError {
e.SetBinder(func(r *http.Request, v interface{}) error {
ct := r.Header.Get(ContentType)
err := UnsupportedMediaType
if strings.HasPrefix(ct, ApplicationJSON) {
@ -158,10 +170,7 @@ func New() (e *Echo) {
} else if strings.HasPrefix(ct, ApplicationForm) {
err = nil
}
if err != nil {
return &HTTPError{Error: err}
}
return nil
return err
})
return
}
@ -261,6 +270,21 @@ func (e *Echo) Trace(path string, h Handler) {
e.add(TRACE, path, h)
}
// WebSocket adds a WebSocket route > handler to the router.
func (e *Echo) WebSocket(path string, h HandlerFunc) {
e.Get(path, func(c *Context) *HTTPError {
wss := websocket.Server{
Handler: func(ws *websocket.Conn) {
c.Socket = ws
c.Response.status = http.StatusSwitchingProtocols
h(c)
},
}
wss.ServeHTTP(c.Response.writer, c.Request)
return nil
})
}
func (e *Echo) add(method, path string, h Handler) {
key := runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
e.uris[key] = path
@ -280,7 +304,7 @@ func (e *Echo) Favicon(file string) {
// Static serves static files.
func (e *Echo) Static(path, root string) {
fs := http.StripPrefix(path, http.FileServer(http.Dir(root)))
e.Get(path+"*", func(c *Context) *HTTPError {
e.Get(path+"*", func(c *Context) error {
fs.ServeHTTP(c.Response, c.Request)
return nil
})
@ -288,7 +312,7 @@ func (e *Echo) Static(path, root string) {
// ServeFile serves a file.
func (e *Echo) ServeFile(path, file string) {
e.Get(path, func(c *Context) *HTTPError {
e.Get(path, func(c *Context) error {
http.ServeFile(c.Response, c.Request, file)
return nil
})
@ -376,16 +400,16 @@ func wrapMiddleware(m Middleware) MiddlewareFunc {
return m
case HandlerFunc:
return wrapHandlerFuncMW(m)
case func(*Context) *HTTPError:
case func(*Context) error:
return wrapHandlerFuncMW(m)
case func(http.Handler) http.Handler:
return func(h HandlerFunc) HandlerFunc {
return func(c *Context) (he *HTTPError) {
return func(c *Context) (err error) {
m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.Response.Writer = w
c.Response.writer = w
c.Request = r
he = h(c)
})).ServeHTTP(c.Response.Writer, c.Request)
err = h(c)
})).ServeHTTP(c.Response.writer, c.Request)
return
}
}
@ -403,9 +427,9 @@ func wrapMiddleware(m Middleware) MiddlewareFunc {
// Wraps HandlerFunc middleware
func wrapHandlerFuncMW(m HandlerFunc) MiddlewareFunc {
return func(h HandlerFunc) HandlerFunc {
return func(c *Context) *HTTPError {
if he := m(c); he != nil {
return he
return func(c *Context) error {
if err := m(c); err != nil {
return err
}
return h(c)
}
@ -415,9 +439,9 @@ func wrapHandlerFuncMW(m HandlerFunc) MiddlewareFunc {
// Wraps http.HandlerFunc middleware
func wrapHTTPHandlerFuncMW(m http.HandlerFunc) MiddlewareFunc {
return func(h HandlerFunc) HandlerFunc {
return func(c *Context) *HTTPError {
return func(c *Context) error {
if !c.Response.committed {
m.ServeHTTP(c.Response.Writer, c.Request)
m.ServeHTTP(c.Response.writer, c.Request)
}
return h(c)
}
@ -429,15 +453,15 @@ func wrapHandler(h Handler) HandlerFunc {
switch h := h.(type) {
case HandlerFunc:
return h
case func(*Context) *HTTPError:
case func(*Context) error:
return h
case http.Handler, http.HandlerFunc:
return func(c *Context) *HTTPError {
return func(c *Context) error {
h.(http.Handler).ServeHTTP(c.Response, c.Request)
return nil
}
case func(http.ResponseWriter, *http.Request):
return func(c *Context) *HTTPError {
return func(c *Context) error {
h(c.Response, c.Request)
return nil
}

View File

@ -67,7 +67,7 @@ func TestEchoMiddleware(t *testing.T) {
// MiddlewareFunc
e.Use(MiddlewareFunc(func(h HandlerFunc) HandlerFunc {
return func(c *Context) *HTTPError {
return func(c *Context) error {
b.WriteString("a")
return h(c)
}
@ -75,14 +75,14 @@ func TestEchoMiddleware(t *testing.T) {
// func(echo.HandlerFunc) echo.HandlerFunc
e.Use(func(h HandlerFunc) HandlerFunc {
return func(c *Context) *HTTPError {
return func(c *Context) error {
b.WriteString("b")
return h(c)
}
})
// func(*echo.Context) *HTTPError
e.Use(func(c *Context) *HTTPError {
// func(*echo.Context) error
e.Use(func(c *Context) error {
b.WriteString("c")
return nil
})
@ -111,7 +111,7 @@ func TestEchoMiddleware(t *testing.T) {
})
// Route
e.Get("/hello", func(c *Context) *HTTPError {
e.Get("/hello", func(c *Context) error {
return c.String(http.StatusOK, "world")
})
@ -130,7 +130,7 @@ func TestEchoHandler(t *testing.T) {
e := New()
// HandlerFunc
e.Get("/1", HandlerFunc(func(c *Context) *HTTPError {
e.Get("/1", HandlerFunc(func(c *Context) error {
return c.String(http.StatusOK, "1")
}))
w := httptest.NewRecorder()
@ -140,8 +140,8 @@ func TestEchoHandler(t *testing.T) {
t.Error("body should be 1")
}
// func(*echo.Context) *HTTPError
e.Get("/2", func(c *Context) *HTTPError {
// func(*echo.Context) error
e.Get("/2", func(c *Context) error {
return c.String(http.StatusOK, "2")
})
w = httptest.NewRecorder()
@ -177,11 +177,11 @@ func TestEchoHandler(t *testing.T) {
func TestEchoGroup(t *testing.T) {
b := new(bytes.Buffer)
e := New()
e.Use(func(*Context) *HTTPError {
e.Use(func(*Context) error {
b.WriteString("1")
return nil
})
e.Get("/users", func(*Context) *HTTPError { return nil })
e.Get("/users", func(*Context) error { return nil })
w := httptest.NewRecorder()
r, _ := http.NewRequest(GET, "/users", nil)
e.ServeHTTP(w, r)
@ -191,11 +191,11 @@ func TestEchoGroup(t *testing.T) {
// Group
g1 := e.Group("/group1")
g1.Use(func(*Context) *HTTPError {
g1.Use(func(*Context) error {
b.WriteString("2")
return nil
})
g1.Get("/home", func(*Context) *HTTPError { return nil })
g1.Get("/home", func(*Context) error { return nil })
b.Reset()
w = httptest.NewRecorder()
r, _ = http.NewRequest(GET, "/group1/home", nil)
@ -205,11 +205,11 @@ func TestEchoGroup(t *testing.T) {
}
// Group with no parent middleware
g2 := e.Group("/group2", func(*Context) *HTTPError {
g2 := e.Group("/group2", func(*Context) error {
b.WriteString("3")
return nil
})
g2.Get("/home", func(*Context) *HTTPError { return nil })
g2.Get("/home", func(*Context) error { return nil })
b.Reset()
w = httptest.NewRecorder()
r, _ = http.NewRequest(GET, "/group2/home", nil)
@ -221,7 +221,7 @@ func TestEchoGroup(t *testing.T) {
// Nested group
g3 := e.Group("/group3")
g4 := g3.Group("/group4")
g4.Get("/home", func(c *Context) *HTTPError {
g4.Get("/home", func(c *Context) error {
return c.NoContent(http.StatusOK)
})
w = httptest.NewRecorder()
@ -234,7 +234,7 @@ func TestEchoGroup(t *testing.T) {
func TestEchoMethod(t *testing.T) {
e := New()
h := func(*Context) *HTTPError { return nil }
h := func(*Context) error { return nil }
e.Connect("/", h)
e.Delete("/", h)
e.Get("/", h)
@ -248,9 +248,9 @@ func TestEchoMethod(t *testing.T) {
func TestEchoURL(t *testing.T) {
e := New()
static := func(*Context) *HTTPError { return nil }
getUser := func(*Context) *HTTPError { return nil }
getFile := func(*Context) *HTTPError { return nil }
static := func(*Context) error { return nil }
getUser := func(*Context) error { return nil }
getFile := func(*Context) error { return nil }
e.Get("/static/file", static)
e.Get("/users/:id", getUser)
e.Get("/users/:uid/files/:fid", getFile)

View File

@ -24,34 +24,34 @@ var (
// Handlers
//----------
func createUser(c *echo.Context) *echo.HTTPError {
func createUser(c *echo.Context) error {
u := &user{
ID: seq,
}
if he := c.Bind(u); he != nil {
return he
if err := c.Bind(u); err != nil {
return err
}
users[u.ID] = u
seq++
return c.JSON(http.StatusCreated, u)
}
func getUser(c *echo.Context) *echo.HTTPError {
func getUser(c *echo.Context) error {
id, _ := strconv.Atoi(c.Param("id"))
return c.JSON(http.StatusOK, users[id])
}
func updateUser(c *echo.Context) *echo.HTTPError {
func updateUser(c *echo.Context) error {
u := new(user)
if he := c.Bind(u); he != nil {
return he
if err := c.Bind(u); err != nil {
return err
}
id, _ := strconv.Atoi(c.Param("id"))
users[id].Name = u.Name
return c.JSON(http.StatusOK, users[id])
}
func deleteUser(c *echo.Context) *echo.HTTPError {
func deleteUser(c *echo.Context) error {
id, _ := strconv.Atoi(c.Param("id"))
delete(users, id)
return c.NoContent(http.StatusNoContent)

View File

@ -8,7 +8,7 @@ import (
)
// Handler
func hello(c *echo.Context) *echo.HTTPError {
func hello(c *echo.Context) error {
return c.String(http.StatusOK, "Hello, World!\n")
}

View File

@ -8,7 +8,7 @@ import (
)
// Handler
func hello(c *echo.Context) *echo.HTTPError {
func hello(c *echo.Context) error {
return c.String(http.StatusOK, "Hello, World!\n")
}

View File

@ -29,22 +29,19 @@ var (
)
// Render HTML
func (t *Template) Render(w io.Writer, name string, data interface{}) *echo.HTTPError {
if err := t.templates.ExecuteTemplate(w, name, data); err != nil {
return &echo.HTTPError{Error: err}
}
return nil
func (t *Template) Render(w io.Writer, name string, data interface{}) error {
return t.templates.ExecuteTemplate(w, name, data)
}
//----------
// Handlers
//----------
func welcome(c *echo.Context) *echo.HTTPError {
func welcome(c *echo.Context) error {
return c.Render(http.StatusOK, "welcome", "Joe")
}
func createUser(c *echo.Context) *echo.HTTPError {
func createUser(c *echo.Context) error {
u := new(user)
if err := c.Bind(u); err != nil {
return err
@ -53,11 +50,11 @@ func createUser(c *echo.Context) *echo.HTTPError {
return c.JSON(http.StatusCreated, u)
}
func getUsers(c *echo.Context) *echo.HTTPError {
func getUsers(c *echo.Context) error {
return c.JSON(http.StatusOK, users)
}
func getUser(c *echo.Context) *echo.HTTPError {
func getUser(c *echo.Context) error {
return c.JSON(http.StatusOK, users[c.P(0)])
}
@ -79,7 +76,7 @@ func main() {
s := stats.New()
e.Use(s.Handler)
// Route
e.Get("/stats", func(c *echo.Context) *echo.HTTPError {
e.Get("/stats", func(c *echo.Context) error {
return c.JSON(http.StatusOK, s.Data())
})
@ -117,20 +114,20 @@ func main() {
// Group with parent middleware
a := e.Group("/admin")
a.Use(func(c *echo.Context) *echo.HTTPError {
a.Use(func(c *echo.Context) error {
// Security middleware
return nil
})
a.Get("", func(c *echo.Context) *echo.HTTPError {
a.Get("", func(c *echo.Context) error {
return c.String(http.StatusOK, "Welcome admin!")
})
// Group with no parent middleware
g := e.Group("/files", func(c *echo.Context) *echo.HTTPError {
g := e.Group("/files", func(c *echo.Context) error {
// Security middleware
return nil
})
g.Get("", func(c *echo.Context) *echo.HTTPError {
g.Get("", func(c *echo.Context) error {
return c.String(http.StatusOK, "Your files!")
})

View File

@ -16,10 +16,10 @@ const (
// BasicAuth returns an HTTP basic authentication middleware.
func BasicAuth(fn AuthFunc) echo.HandlerFunc {
return func(c *echo.Context) (he *echo.HTTPError) {
return func(c *echo.Context) error {
auth := c.Request.Header.Get(echo.Authorization)
i := 0
he = &echo.HTTPError{Code: http.StatusUnauthorized}
he := echo.NewHTTPError(http.StatusUnauthorized)
for ; i < len(auth); i++ {
c := auth[i]
@ -33,31 +33,31 @@ func BasicAuth(fn AuthFunc) echo.HandlerFunc {
// Ignore case
if i == 0 {
if c != Basic[i] && c != 'b' {
return
return he
}
} else {
if c != Basic[i] {
return
return he
}
}
} else {
// Extract credentials
b, err := base64.StdEncoding.DecodeString(auth[i:])
if err != nil {
return
return he
}
cred := string(b)
for i := 0; i < len(cred); i++ {
if cred[i] == ':' {
// Verify credentials
if !fn(cred[:i], cred[i+1:]) {
return
return he
}
return nil
}
}
}
}
return
return he
}
}

View File

@ -25,13 +25,13 @@ func Gzip() echo.MiddlewareFunc {
scheme := "gzip"
return func(h echo.HandlerFunc) echo.HandlerFunc {
return func(c *echo.Context) *echo.HTTPError {
return func(c *echo.Context) error {
if strings.Contains(c.Request.Header.Get(echo.AcceptEncoding), scheme) {
w := gzip.NewWriter(c.Response.Writer)
w := gzip.NewWriter(c.Response.Writer())
defer w.Close()
gw := gzipWriter{Writer: w, Response: c.Response}
c.Response.Header().Set(echo.ContentEncoding, scheme)
c.Response = &echo.Response{Writer: gw}
c.Response = echo.NewResponse(gw)
}
return h(c)
}

View File

@ -14,9 +14,9 @@ func TestGzip(t *testing.T) {
// Empty Accept-Encoding header
req, _ := http.NewRequest(echo.GET, "/", nil)
w := httptest.NewRecorder()
res := &echo.Response{Writer: w}
res := echo.NewResponse(w)
c := echo.NewContext(req, res, echo.New())
h := func(c *echo.Context) *echo.HTTPError {
h := func(c *echo.Context) error {
return c.String(http.StatusOK, "test")
}
Gzip()(h)(c)
@ -28,7 +28,7 @@ func TestGzip(t *testing.T) {
// Content-Encoding header
req.Header.Set(echo.AcceptEncoding, "gzip")
w = httptest.NewRecorder()
c.Response = &echo.Response{Writer: w}
c.Response = echo.NewResponse(w)
Gzip()(h)(c)
ce := w.Header().Get(echo.ContentEncoding)
if ce != "gzip" {

View File

@ -10,10 +10,10 @@ import (
func Logger() echo.MiddlewareFunc {
return func(h echo.HandlerFunc) echo.HandlerFunc {
return func(c *echo.Context) *echo.HTTPError {
return func(c *echo.Context) error {
start := time.Now()
if he := h(c); he != nil {
c.Error(he)
if err := h(c); err != nil {
c.Error(err)
}
end := time.Now()
method := c.Request.Method

View File

@ -1 +1,36 @@
package middleware
import (
"github.com/labstack/echo"
"net/http"
"net/http/httptest"
"testing"
)
func TestLogger(t *testing.T) {
e := echo.New()
req, _ := http.NewRequest(echo.GET, "/", nil)
w := httptest.NewRecorder()
res := echo.NewResponse(w)
c := echo.NewContext(req, res, e)
// Status 2xx
h := func(c *echo.Context) error {
return c.String(http.StatusOK, "test")
}
Logger()(h)(c)
// Status 4xx
c.Response = echo.NewResponse(w)
h = func(c *echo.Context) error {
return c.String(http.StatusNotFound, "test")
}
Logger()(h)(c)
// Status 5xx
c.Response = echo.NewResponse(w)
h = func(c *echo.Context) error {
return c.String(http.StatusInternalServerError, "test")
}
Logger()(h)(c)
}

View File

@ -13,15 +13,13 @@ import (
func Recover() echo.MiddlewareFunc {
// TODO: Provide better stack trace `https://github.com/go-errors/errors` `https://github.com/docker/libcontainer/tree/master/stacktrace`
return func(h echo.HandlerFunc) echo.HandlerFunc {
return func(c *echo.Context) *echo.HTTPError {
return func(c *echo.Context) error {
defer func() {
if err := recover(); err != nil {
trace := make([]byte, 1<<16)
n := runtime.Stack(trace, true)
c.Error(&echo.HTTPError{
Error: fmt.Errorf("echo => panic recover\n %v\n stack trace %d bytes\n %s",
err, n, trace[:n]),
})
c.Error(fmt.Errorf("echo => panic recover\n %v\n stack trace %d bytes\n %s",
err, n, trace[:n]))
}
}()
return h(c)

View File

@ -13,9 +13,9 @@ func TestRecover(t *testing.T) {
e.SetDebug(true)
req, _ := http.NewRequest(echo.GET, "/", nil)
w := httptest.NewRecorder()
res := &echo.Response{Writer: w}
res := echo.NewResponse(w)
c := echo.NewContext(req, res, e)
h := func(c *echo.Context) *echo.HTTPError {
h := func(c *echo.Context) error {
panic("test")
}

View File

@ -14,7 +14,7 @@ type (
// StripTrailingSlash returns a middleware which removes trailing slash from request
// path.
func StripTrailingSlash() echo.HandlerFunc {
return func(c *echo.Context) *echo.HTTPError {
return func(c *echo.Context) error {
p := c.Request.URL.Path
l := len(p)
if p[l-1] == '/' {
@ -35,7 +35,7 @@ func RedirectToSlash(opts ...RedirectToSlashOptions) echo.HandlerFunc {
}
}
return func(c *echo.Context) (he *echo.HTTPError) {
return func(c *echo.Context) error {
p := c.Request.URL.Path
l := len(p)
if p[l-1] != '/' {

View File

@ -10,7 +10,7 @@ import (
func TestStripTrailingSlash(t *testing.T) {
req, _ := http.NewRequest(echo.GET, "/users/", nil)
res := &echo.Response{Writer: httptest.NewRecorder()}
res := echo.NewResponse(httptest.NewRecorder())
c := echo.NewContext(req, res, echo.New())
StripTrailingSlash()(c)
p := c.Request.URL.Path
@ -21,7 +21,7 @@ func TestStripTrailingSlash(t *testing.T) {
func TestRedirectToSlash(t *testing.T) {
req, _ := http.NewRequest(echo.GET, "/users", nil)
res := &echo.Response{Writer: httptest.NewRecorder()}
res := echo.NewResponse(httptest.NewRecorder())
c := echo.NewContext(req, res, echo.New())
RedirectToSlash(RedirectToSlashOptions{Code: http.StatusTemporaryRedirect})(c)

View File

@ -11,15 +11,23 @@ import (
type (
Response struct {
Writer http.ResponseWriter
writer http.ResponseWriter
status int
size int64
committed bool
}
)
func NewResponse(w http.ResponseWriter) *Response {
return &Response{writer: w}
}
func (r *Response) Header() http.Header {
return r.Writer.Header()
return r.writer.Header()
}
func (r *Response) Writer() http.ResponseWriter {
return r.writer
}
func (r *Response) WriteHeader(code int) {
@ -29,29 +37,29 @@ func (r *Response) WriteHeader(code int) {
return
}
r.status = code
r.Writer.WriteHeader(code)
r.writer.WriteHeader(code)
r.committed = true
}
func (r *Response) Write(b []byte) (n int, err error) {
n, err = r.Writer.Write(b)
n, err = r.writer.Write(b)
r.size += int64(n)
return n, err
}
// Flush wraps response writer's Flush function.
func (r *Response) Flush() {
r.Writer.(http.Flusher).Flush()
r.writer.(http.Flusher).Flush()
}
// Hijack wraps response writer's Hijack function.
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return r.Writer.(http.Hijacker).Hijack()
return r.writer.(http.Hijacker).Hijack()
}
// CloseNotify wraps response writer's CloseNotify function.
func (r *Response) CloseNotify() <-chan bool {
return r.Writer.(http.CloseNotifier).CloseNotify()
return r.writer.(http.CloseNotifier).CloseNotify()
}
func (r *Response) Status() int {
@ -63,7 +71,7 @@ func (r *Response) Size() int64 {
}
func (r *Response) reset(w http.ResponseWriter) {
r.Writer = w
r.writer = w
r.status = http.StatusOK
r.committed = false
}

View File

@ -7,7 +7,7 @@ import (
)
func TestResponse(t *testing.T) {
r := &Response{Writer: httptest.NewRecorder()}
r := NewResponse(httptest.NewRecorder())
// Header
if r.Header() == nil {

View File

@ -283,7 +283,7 @@ func TestRouterStatic(t *testing.T) {
r := New().Router
b := new(bytes.Buffer)
path := "/folders/a/files/echo.gif"
r.Add(GET, path, func(*Context) *HTTPError {
r.Add(GET, path, func(*Context) error {
b.WriteString(path)
return nil
}, nil)
@ -300,7 +300,7 @@ func TestRouterStatic(t *testing.T) {
func TestRouterParam(t *testing.T) {
r := New().Router
r.Add(GET, "/users/:id", func(c *Context) *HTTPError {
r.Add(GET, "/users/:id", func(c *Context) error {
return nil
}, nil)
h, _ := r.Find(GET, "/users/1", context)
@ -315,7 +315,7 @@ func TestRouterParam(t *testing.T) {
func TestRouterTwoParam(t *testing.T) {
r := New().Router
r.Add(GET, "/users/:uid/files/:fid", func(*Context) *HTTPError {
r.Add(GET, "/users/:uid/files/:fid", func(*Context) error {
return nil
}, nil)
@ -339,7 +339,7 @@ func TestRouterTwoParam(t *testing.T) {
func TestRouterMatchAny(t *testing.T) {
r := New().Router
r.Add(GET, "/users/*", func(*Context) *HTTPError {
r.Add(GET, "/users/*", func(*Context) error {
return nil
}, nil)
@ -364,7 +364,7 @@ func TestRouterMatchAny(t *testing.T) {
func TestRouterMicroParam(t *testing.T) {
r := New().Router
r.Add(GET, "/:a/:b/:c", func(c *Context) *HTTPError {
r.Add(GET, "/:a/:b/:c", func(c *Context) error {
return nil
}, nil)
h, _ := r.Find(GET, "/1/2/3", context)
@ -388,11 +388,11 @@ func TestRouterMultiRoute(t *testing.T) {
b := new(bytes.Buffer)
// Routes
r.Add(GET, "/users", func(*Context) *HTTPError {
r.Add(GET, "/users", func(*Context) error {
b.WriteString("/users")
return nil
}, nil)
r.Add(GET, "/users/:id", func(c *Context) *HTTPError {
r.Add(GET, "/users/:id", func(c *Context) error {
return nil
}, nil)
@ -428,31 +428,31 @@ func TestRouterPriority(t *testing.T) {
r := New().Router
// Routes
r.Add(GET, "/users", func(c *Context) *HTTPError {
r.Add(GET, "/users", func(c *Context) error {
c.Set("a", 1)
return nil
}, nil)
r.Add(GET, "/users/new", func(c *Context) *HTTPError {
r.Add(GET, "/users/new", func(c *Context) error {
c.Set("b", 2)
return nil
}, nil)
r.Add(GET, "/users/:id", func(c *Context) *HTTPError {
r.Add(GET, "/users/:id", func(c *Context) error {
c.Set("c", 3)
return nil
}, nil)
r.Add(GET, "/users/dew", func(c *Context) *HTTPError {
r.Add(GET, "/users/dew", func(c *Context) error {
c.Set("d", 4)
return nil
}, nil)
r.Add(GET, "/users/:id/files", func(c *Context) *HTTPError {
r.Add(GET, "/users/:id/files", func(c *Context) error {
c.Set("e", 5)
return nil
}, nil)
r.Add(GET, "/users/newsee", func(c *Context) *HTTPError {
r.Add(GET, "/users/newsee", func(c *Context) error {
c.Set("f", 6)
return nil
}, nil)
r.Add(GET, "/users/*", func(c *Context) *HTTPError {
r.Add(GET, "/users/*", func(c *Context) error {
c.Set("g", 7)
return nil
}, nil)
@ -540,14 +540,14 @@ func TestRouterParamNames(t *testing.T) {
b := new(bytes.Buffer)
// Routes
r.Add(GET, "/users", func(*Context) *HTTPError {
r.Add(GET, "/users", func(*Context) error {
b.WriteString("/users")
return nil
}, nil)
r.Add(GET, "/users/:id", func(c *Context) *HTTPError {
r.Add(GET, "/users/:id", func(c *Context) error {
return nil
}, nil)
r.Add(GET, "/users/:uid/files/:fid", func(c *Context) *HTTPError {
r.Add(GET, "/users/:uid/files/:fid", func(c *Context) error {
return nil
}, nil)
@ -598,7 +598,7 @@ func TestRouterParamNames(t *testing.T) {
func TestRouterAPI(t *testing.T) {
r := New().Router
for _, route := range api {
r.Add(route.method, route.path, func(c *Context) *HTTPError {
r.Add(route.method, route.path, func(c *Context) error {
for i, n := range c.pnames {
if n != "" {
if ":"+n != c.P(uint8(i)) {
@ -619,7 +619,7 @@ func TestRouterAPI(t *testing.T) {
func TestRouterServeHTTP(t *testing.T) {
r := New().Router
r.Add(GET, "/users", func(*Context) *HTTPError {
r.Add(GET, "/users", func(*Context) error {
return nil
}, nil)

View File

@ -41,16 +41,16 @@ for many use cases. Restricting path parameters allows us to use memory efficien
Registers a custom `Echo.HTTPErrorHandler`.
Default handler sends `HTTPError.Message` HTTP response with `HTTPError.Code` status
code.
Default handler rules
- If HTTPError.Code is not set it uses `500`".
- If HTTPError.Message is not set it uses status code text.
- If debug mode is enabled, HTTPError.Message is set to `HTTPError.Error.Error()`.
- If error is of type `Echo.HTTPError` it sends HTTP response with status code `HTTPError.Code`
and message `HTTPError.Message`.
- Else it sends `500 - Internal Server Error`.
- If debug mode is enabled, it uses `error.Error()` as status message.
### Debug
`echo.SetDebug(on bool)`
`Echo.SetDebug(on bool)`
Enables debug mode.
@ -67,12 +67,12 @@ code below registers a route for method `GET`, path `/hello` and a handler which
`Hello!` HTTP response.
```go
echo.Get("/hello", func(*echo.Context) *HTTPError {
echo.Get("/hello", func(*echo.Context) error {
return c.String(http.StatusOK, "Hello!")
})
```
Echo's default handler is `func(*echo.Context) *echo.HTTPError` where `echo.Context`
Echo's default handler is `func(*echo.Context) error` where `echo.Context`
primarily holds HTTP request and response objects. Echo also has a support for other
types of handlers.
@ -89,7 +89,7 @@ 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) *HTTPError {
echo.Get("/users/:id", func(c *echo.Context) error {
// By name
id := c.Param("id")
@ -119,15 +119,15 @@ match
#### Example
```go
e.Get("/users/:id", func(c *echo.Context) *HTTPError {
e.Get("/users/:id", func(c *echo.Context) error {
return c.String(http.StatusOK, "/users/:id")
})
e.Get("/users/new", func(c *echo.Context) *HTTPError {
e.Get("/users/new", func(c *echo.Context) error {
return c.String(http.StatusOK, "/users/new")
})
e.Get("/users/1/files/*", func(c *echo.Context) *HTTPError {
e.Get("/users/1/files/*", func(c *echo.Context) error {
return c.String(http.StatusOK, "/users/1/files/*")
})
```
@ -152,7 +152,7 @@ application.
```go
// Handler
h := func(*echo.Context) *HTTPError {
h := func(*echo.Context) error {
return c.String(http.StatusOK, "OK")
}
@ -169,7 +169,7 @@ e.Get("/users/:id", h)
### JSON
```go
context.JSON(code int, v interface{}) *HTTPError
context.JSON(code int, v interface{}) error
```
Sends a JSON HTTP response with status code.
@ -177,7 +177,7 @@ Sends a JSON HTTP response with status code.
### String
```go
context.String(code int, s string) *HTTPError
context.String(code int, s string) error
```
Sends a text/plain HTTP response with status code.
@ -185,7 +185,7 @@ Sends a text/plain HTTP response with status code.
### HTML
```go
func (c *Context) HTML(code int, html string) *HTTPError
func (c *Context) HTML(code int, html string) error
```
Sends an HTML HTTP response with status code.
@ -228,8 +228,8 @@ e.Favicon("public/favicon.ico")
## Error Handling
Echo advocates centralized HTTP error handling by returning `*echo.HTTPError` from
middleware and handlers.
Echo advocates centralized HTTP error handling by returning `error` from middleware
and handlers.
It allows you to
@ -237,8 +237,8 @@ It allows you to
- Customize HTTP responses.
- Recover from panics inside middleware or handlers.
For example, when a basic auth middleware finds invalid credentials it returns 401
"Unauthorized" error, aborting the current HTTP request.
For example, when a basic auth middleware finds invalid credentials it returns
`401 - Unauthorized` error, aborting the current HTTP request.
```go
package main
@ -251,18 +251,18 @@ import (
func main() {
e := echo.New()
e.Use(func(c *echo.Context) *echo.HTTPError {
e.Use(func(c *echo.Context) error {
// Extract the credentials from HTTP request header and perform a security
// check
// For invalid credentials
return &echo.HTTPError{Code: http.StatusUnauthorized}
return echo.NewHTTPError(http.StatusUnauthorized)
})
e.Get("/welcome", welcome)
e.Run(":1323")
}
func welcome(c *echo.Context) *echo.HTTPError {
func welcome(c *echo.Context) error {
return c.String(http.StatusOK, "Welcome!")
}
```

View File

@ -16,14 +16,14 @@ Echo is a fast HTTP router (zero memory allocation) and micro web framework in G
- `echo.MiddlewareFunc`
- `func(echo.HandlerFunc) echo.HandlerFunc`
- `echo.HandlerFunc`
- `func(*echo.Context) *echo.HTTPError`
- `func(*echo.Context) error`
- `func(http.Handler) http.Handler`
- `http.Handler`
- `http.HandlerFunc`
- `func(http.ResponseWriter, *http.Request)`
- Handler
- `echo.HandlerFunc`
- `func(*echo.Context) *echo.HTTPError`
- `func(*echo.Context) error`
- `http.Handler`
- `http.HandlerFunc`
- `func(http.ResponseWriter, *http.Request)`
@ -57,7 +57,7 @@ import (
)
// Handler
func hello(c *echo.Context) *echo.HTTPError {
func hello(c *echo.Context) error {
return c.String(http.StatusOK, "Hello, World!\n")
}