diff --git a/README.md b/README.md index 6c44ab7a..edf4043f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ + +# **WORK IN PROGRESS!** + # [Echo](http://labstack.com/echo) [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/labstack/echo) [![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE) [![Build Status](http://img.shields.io/travis/labstack/echo.svg?style=flat-square)](https://travis-ci.org/labstack/echo) [![Coverage Status](http://img.shields.io/coveralls/labstack/echo.svg?style=flat-square)](https://coveralls.io/r/labstack/echo) [![Join the chat at https://gitter.im/labstack/echo](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg?style=flat-square)](https://gitter.im/labstack/echo) A fast and unfancy micro web framework for Go. diff --git a/context.go b/context.go index 46b06c8d..3792cdfd 100644 --- a/context.go +++ b/context.go @@ -7,13 +7,14 @@ import ( "path/filepath" "time" + "github.com/labstack/echo/engine" "github.com/labstack/gommon/log" "net/url" "bytes" - xcontext "golang.org/x/net/context" + netContext "golang.org/x/net/context" "golang.org/x/net/websocket" ) @@ -21,9 +22,9 @@ type ( // Context represents context for the current request. It holds request and // response objects, path parameters, data and registered handler. Context interface { - xcontext.Context - Request() *http.Request - Response() *Response + netContext.Context + Request() engine.Request + Response() engine.Response Socket() *websocket.Conn Path() string P(int) string @@ -50,8 +51,8 @@ type ( } context struct { - request *http.Request - response *Response + request engine.Request + response engine.Response socket *websocket.Conn path string pnames []string @@ -65,7 +66,7 @@ type ( ) // NewContext creates a Context object. -func NewContext(req *http.Request, res *Response, e *Echo) Context { +func NewContext(req engine.Request, res engine.Response, e *Echo) Context { return &context{ request: req, response: res, @@ -92,12 +93,12 @@ func (c *context) Value(key interface{}) interface{} { } // Request returns *http.Request. -func (c *context) Request() *http.Request { +func (c *context) Request() engine.Request { return c.request } // Response returns *Response. -func (c *context) Response() *Response { +func (c *context) Response() engine.Response { return c.response } @@ -135,14 +136,17 @@ func (c *context) Param(name string) (value string) { // Query returns query parameter by name. func (c *context) Query(name string) string { if c.query == nil { - c.query = c.request.URL.Query() + // TODO: v2 + // c.query = c.request.URL.Query() } return c.query.Get(name) } // Form returns form parameter by name. func (c *context) Form(name string) string { - return c.request.FormValue(name) + // TODO: v2 + // return c.request.FormValue(name) + return "" } // Get retrieves data from the context. @@ -294,7 +298,8 @@ func (c *context) Redirect(code int, url string) error { if code < http.StatusMultipleChoices || code > http.StatusTemporaryRedirect { return InvalidRedirectCode } - http.Redirect(c.response, c.request, url, code) + // TODO: v2 + // http.Redirect(c.response, c.request, url, code) return nil } @@ -313,9 +318,11 @@ func (c *context) X() *context { return c } -func (c *context) reset(r *http.Request, w http.ResponseWriter, e *Echo) { - c.request = r - c.response.reset(w, e) +func (c *context) reset(req engine.Request, res engine.Response, e *Echo) { + c.request = req + // TODO: v2 + // c.response.reset(res, e) + c.response = res c.query = nil c.store = nil c.echo = e diff --git a/echo.go b/echo.go index 74737099..8683bfba 100644 --- a/echo.go +++ b/echo.go @@ -16,9 +16,11 @@ import ( "encoding/xml" + "github.com/labstack/echo/engine" + "github.com/labstack/echo/engine/fasthttp" + "github.com/labstack/echo/engine/standard" "github.com/labstack/gommon/log" "golang.org/x/net/http2" - "golang.org/x/net/websocket" ) type ( @@ -34,8 +36,10 @@ type ( renderer Renderer pool sync.Pool debug bool - hook http.HandlerFunc + hook engine.HandlerFunc autoIndex bool + engineType engine.Type + engine engine.Engine router *Router logger *log.Logger } @@ -43,7 +47,7 @@ type ( Route struct { Method string Path string - Handler Handler + Handler string } HTTPError struct { @@ -51,17 +55,17 @@ type ( message string } - Middleware interface{} + // Middleware interface{} MiddlewareFunc func(HandlerFunc) HandlerFunc - Handler interface{} - HandlerFunc func(Context) error + // Handler interface{} + HandlerFunc func(Context) error // HTTPErrorHandler is a centralized HTTP error handler. HTTPErrorHandler func(error, Context) // Binder is the interface that wraps the Bind method. Binder interface { - Bind(*http.Request, interface{}) error + Bind(engine.Request, interface{}) error } binder struct { @@ -188,7 +192,8 @@ var ( func New() (e *Echo) { e = &Echo{maxParam: new(int)} e.pool.New = func() interface{} { - return NewContext(nil, new(Response), e) + // NOTE: v2 + return NewContext(nil, nil, e) } e.router = NewRouter(e) @@ -198,20 +203,21 @@ func New() (e *Echo) { e.HTTP2(true) e.defaultHTTPErrorHandler = func(err error, c Context) { - x := c.X() - code := http.StatusInternalServerError - msg := http.StatusText(code) - if he, ok := err.(*HTTPError); ok { - code = he.code - msg = he.message - } - if e.debug { - msg = err.Error() - } - if !x.response.committed { - http.Error(x.response, msg, code) - } - e.logger.Error(err) + // TODO: v2 + // x := c.X() + // code := http.StatusInternalServerError + // msg := http.StatusText(code) + // if he, ok := err.(*HTTPError); ok { + // code = he.code + // msg = he.message + // } + // if e.debug { + // msg = err.Error() + // } + // if !x.response.Committed() { + // http.Error(x.response, msg, code) + // } + // e.logger.Error(err) } e.SetHTTPErrorHandler(e.defaultHTTPErrorHandler) e.SetBinder(&binder{}) @@ -291,95 +297,80 @@ func (e *Echo) AutoIndex(on bool) { // Hook registers a callback which is invoked from `Echo#ServerHTTP` as the first // statement. Hook is useful if you want to modify response/response objects even // before it hits the router or any middleware. -func (e *Echo) Hook(h http.HandlerFunc) { +func (e *Echo) Hook(h engine.HandlerFunc) { e.hook = h } // Use adds handler to the middleware chain. -func (e *Echo) Use(m ...Middleware) { +func (e *Echo) Use(m ...MiddlewareFunc) { for _, h := range m { - e.middleware = append(e.middleware, wrapMiddleware(h)) + e.middleware = append(e.middleware, h) } } // Connect adds a CONNECT route > handler to the router. -func (e *Echo) Connect(path string, h Handler) { +func (e *Echo) Connect(path string, h HandlerFunc) { e.add(CONNECT, path, h) } // Delete adds a DELETE route > handler to the router. -func (e *Echo) Delete(path string, h Handler) { +func (e *Echo) Delete(path string, h HandlerFunc) { e.add(DELETE, path, h) } // Get adds a GET route > handler to the router. -func (e *Echo) Get(path string, h Handler) { +func (e *Echo) Get(path string, h HandlerFunc) { e.add(GET, path, h) } // Head adds a HEAD route > handler to the router. -func (e *Echo) Head(path string, h Handler) { +func (e *Echo) Head(path string, h HandlerFunc) { e.add(HEAD, path, h) } // Options adds an OPTIONS route > handler to the router. -func (e *Echo) Options(path string, h Handler) { +func (e *Echo) Options(path string, h HandlerFunc) { e.add(OPTIONS, path, h) } // Patch adds a PATCH route > handler to the router. -func (e *Echo) Patch(path string, h Handler) { +func (e *Echo) Patch(path string, h HandlerFunc) { e.add(PATCH, path, h) } // Post adds a POST route > handler to the router. -func (e *Echo) Post(path string, h Handler) { +func (e *Echo) Post(path string, h HandlerFunc) { e.add(POST, path, h) } // Put adds a PUT route > handler to the router. -func (e *Echo) Put(path string, h Handler) { +func (e *Echo) Put(path string, h HandlerFunc) { e.add(PUT, path, h) } // Trace adds a TRACE route > handler to the router. -func (e *Echo) Trace(path string, h Handler) { +func (e *Echo) Trace(path string, h HandlerFunc) { e.add(TRACE, path, h) } // Any adds a route > handler to the router for all HTTP methods. -func (e *Echo) Any(path string, h Handler) { +func (e *Echo) Any(path string, h HandlerFunc) { for _, m := range methods { e.add(m, path, h) } } // Match adds a route > handler to the router for multiple HTTP methods provided. -func (e *Echo) Match(methods []string, path string, h Handler) { +func (e *Echo) Match(methods []string, path string, h HandlerFunc) { for _, m := range methods { e.add(m, 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) (err error) { - x := c.X() - wss := websocket.Server{ - Handler: func(ws *websocket.Conn) { - x.socket = ws - x.response.status = http.StatusSwitchingProtocols - err = h(c) - }, - } - wss.ServeHTTP(x.response, x.request) - return err - }) -} - -func (e *Echo) add(method, path string, h Handler) { +// NOTE: v2 +func (e *Echo) add(method, path string, h HandlerFunc) { path = e.prefix + path - e.router.Add(method, path, wrapHandler(h), e) + e.router.Add(method, path, h, e) r := Route{ Method: method, Path: path, @@ -419,36 +410,36 @@ func (e *Echo) ServeFile(path, file string) { } func (e *Echo) serveFile(dir, file string, c Context) (err error) { - x := c.X() - fs := http.Dir(dir) - f, err := fs.Open(file) - if err != nil { - return NewHTTPError(http.StatusNotFound) - } - defer f.Close() - - fi, _ := f.Stat() - if fi.IsDir() { - /* NOTE: - Not checking the Last-Modified header as it caches the response `304` when - changing differnt directories for the same path. - */ - d := f - - // Index file - file = filepath.Join(file, indexPage) - f, err = fs.Open(file) - if err != nil { - if e.autoIndex { - // Auto index - return listDir(d, c) - } - return NewHTTPError(http.StatusForbidden) - } - fi, _ = f.Stat() // Index file stat - } - - http.ServeContent(x.response, x.request, fi.Name(), fi.ModTime(), f) + // TODO: v2 + // x := c.X() + // fs := http.Dir(dir) + // f, err := fs.Open(file) + // if err != nil { + // return NewHTTPError(http.StatusNotFound) + // } + // defer f.Close() + // + // fi, _ := f.Stat() + // if fi.IsDir() { + // /* NOTE: + // Not checking the Last-Modified header as it caches the response `304` when + // changing differnt directories for the same path. + // */ + // d := f + // + // // Index file + // file = filepath.Join(file, indexPage) + // f, err = fs.Open(file) + // if err != nil { + // if e.autoIndex { + // // Auto index + // return listDir(d, c) + // } + // return NewHTTPError(http.StatusForbidden) + // } + // fi, _ = f.Stat() // Index file stat + // } + // http.ServeContent(x.response, x.request, fi.Name(), fi.ModTime(), f) return } @@ -477,7 +468,7 @@ func listDir(d http.File, c Context) (err error) { // Group creates a new sub router with prefix. It inherits all properties from // the parent. Passing middleware overrides parent middleware. -func (e *Echo) Group(prefix string, m ...Middleware) *Group { +func (e *Echo) Group(prefix string, m ...MiddlewareFunc) *Group { g := &Group{*e} g.echo.prefix += prefix if len(m) == 0 { @@ -492,7 +483,7 @@ func (e *Echo) Group(prefix string, m ...Middleware) *Group { } // URI generates a URI from handler. -func (e *Echo) URI(h Handler, params ...interface{}) string { +func (e *Echo) URI(h HandlerFunc, params ...interface{}) string { uri := new(bytes.Buffer) pl := len(params) n := 0 @@ -517,7 +508,7 @@ func (e *Echo) URI(h Handler, params ...interface{}) string { } // URL is an alias for `URI` function. -func (e *Echo) URL(h Handler, params ...interface{}) string { +func (e *Echo) URL(h HandlerFunc, params ...interface{}) string { return e.URI(h, params...) } @@ -528,25 +519,26 @@ func (e *Echo) Routes() []Route { // ServeHTTP implements `http.Handler` interface, which serves HTTP requests. func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if e.hook != nil { - e.hook(w, r) - } - - c := e.pool.Get().(*context) - h, e := e.router.Find(r.Method, r.URL.Path, c) - c.reset(r, w, e) - - // Chain middleware with handler in the end - for i := len(e.middleware) - 1; i >= 0; i-- { - h = e.middleware[i](h) - } - - // Execute chain - if err := h(c); err != nil { - e.httpErrorHandler(err, c) - } - - e.pool.Put(c) + // TODO: v2 + // if e.hook != nil { + // e.hook(w, r) + // } + // + // c := e.pool.Get().(*context) + // h, e := e.router.Find(r.Method, r.URL.Path, c) + // c.reset(r, w, e) + // + // // Chain middleware with handler in the end + // for i := len(e.middleware) - 1; i >= 0; i-- { + // h = e.middleware[i](h) + // } + // + // // Execute chain + // if err := h(c); err != nil { + // e.httpErrorHandler(err, c) + // } + // + // e.pool.Put(c) } // Server returns the internal *http.Server. @@ -559,9 +551,44 @@ func (e *Echo) Server(addr string) *http.Server { return s } +func (e *Echo) SetEngine(t engine.Type) { + e.engineType = t +} + // Run runs a server. -func (e *Echo) Run(addr string) { - e.run(e.Server(addr)) +func (e *Echo) Run(address string) { + config := &engine.Config{Address: address} + handler := func(req engine.Request, res engine.Response) { + if e.hook != nil { + e.hook(req, res) + } + + c := e.pool.Get().(*context) + h, e := e.router.Find(req.Method(), req.URL().Path(), c) + c.reset(req, res, e) + + // Chain middleware with handler in the end + for i := len(e.middleware) - 1; i >= 0; i-- { + h = e.middleware[i](h) + } + + // Execute chain + if err := h(c); err != nil { + e.httpErrorHandler(err, c) + } + + e.pool.Put(c) + } + e.engine = standard.NewServer(config, handler) + + switch e.engineType { + case engine.FastHTTP: + e.engine = fasthttp.NewServer(config, handler) + } + + e.engine.Start() + + // e.run(e.Server(addr)) } // RunTLS runs a server with TLS configuration. @@ -618,94 +645,13 @@ func (e *HTTPError) Error() string { return e.message } -// wrapMiddleware wraps middleware. -func wrapMiddleware(m Middleware) MiddlewareFunc { - switch m := m.(type) { - case MiddlewareFunc: - return m - case func(HandlerFunc) HandlerFunc: - return m - case HandlerFunc: - return wrapHandlerFuncMW(m) - case func(Context) error: - return wrapHandlerFuncMW(m) - case func(http.Handler) http.Handler: - return func(h HandlerFunc) HandlerFunc { - return func(c Context) (err error) { - x := c.X() - m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - x.response.writer = w - x.request = r - err = h(c) - })).ServeHTTP(x.response.writer, x.request) - return - } - } - case http.Handler: - return wrapHTTPHandlerFuncMW(m.ServeHTTP) - case func(http.ResponseWriter, *http.Request): - return wrapHTTPHandlerFuncMW(m) - default: - panic("unknown middleware") - } -} - -// wrapHandlerFuncMW wraps HandlerFunc middleware. -func wrapHandlerFuncMW(m HandlerFunc) MiddlewareFunc { - return func(h HandlerFunc) HandlerFunc { - return func(c Context) error { - if err := m(c); err != nil { - return err - } - return h(c) - } - } -} - -// wrapHTTPHandlerFuncMW wraps http.HandlerFunc middleware. -func wrapHTTPHandlerFuncMW(m http.HandlerFunc) MiddlewareFunc { - return func(h HandlerFunc) HandlerFunc { - return func(c Context) error { - x := c.X() - if !x.response.committed { - m.ServeHTTP(x.response.writer, x.request) - } - return h(c) - } - } -} - -// wrapHandler wraps handler. -func wrapHandler(h Handler) HandlerFunc { - switch h := h.(type) { - case HandlerFunc: - return h - case func(Context) error: - return h - case http.Handler, http.HandlerFunc: - return func(c Context) error { - x := c.X() - h.(http.Handler).ServeHTTP(x.response, x.request) - return nil - } - case func(http.ResponseWriter, *http.Request): - return func(c Context) error { - x := c.X() - h(x.response, x.request) - return nil - } - default: - panic("unknown handler") - } -} - -func (binder) Bind(r *http.Request, i interface{}) (err error) { - ct := r.Header.Get(ContentType) +func (binder) Bind(r engine.Request, i interface{}) (err error) { + ct := r.Header().Get(ContentType) err = UnsupportedMediaType if strings.HasPrefix(ct, ApplicationJSON) { - err = json.NewDecoder(r.Body).Decode(i) + err = json.NewDecoder(r.Body()).Decode(i) } else if strings.HasPrefix(ct, ApplicationXML) { - err = xml.NewDecoder(r.Body).Decode(i) + err = xml.NewDecoder(r.Body()).Decode(i) } return } diff --git a/group.go b/group.go index 856a3365..c2bc9f1b 100644 --- a/group.go +++ b/group.go @@ -6,52 +6,48 @@ type ( } ) -func (g *Group) Use(m ...Middleware) { +func (g *Group) Use(m ...MiddlewareFunc) { for _, h := range m { - g.echo.middleware = append(g.echo.middleware, wrapMiddleware(h)) + g.echo.middleware = append(g.echo.middleware, h) } } -func (g *Group) Connect(path string, h Handler) { +func (g *Group) Connect(path string, h HandlerFunc) { g.echo.Connect(path, h) } -func (g *Group) Delete(path string, h Handler) { +func (g *Group) Delete(path string, h HandlerFunc) { g.echo.Delete(path, h) } -func (g *Group) Get(path string, h Handler) { +func (g *Group) Get(path string, h HandlerFunc) { g.echo.Get(path, h) } -func (g *Group) Head(path string, h Handler) { +func (g *Group) Head(path string, h HandlerFunc) { g.echo.Head(path, h) } -func (g *Group) Options(path string, h Handler) { +func (g *Group) Options(path string, h HandlerFunc) { g.echo.Options(path, h) } -func (g *Group) Patch(path string, h Handler) { +func (g *Group) Patch(path string, h HandlerFunc) { g.echo.Patch(path, h) } -func (g *Group) Post(path string, h Handler) { +func (g *Group) Post(path string, h HandlerFunc) { g.echo.Post(path, h) } -func (g *Group) Put(path string, h Handler) { +func (g *Group) Put(path string, h HandlerFunc) { g.echo.Put(path, h) } -func (g *Group) Trace(path string, h Handler) { +func (g *Group) Trace(path string, h HandlerFunc) { g.echo.Trace(path, h) } -func (g *Group) WebSocket(path string, h HandlerFunc) { - g.echo.WebSocket(path, h) -} - func (g *Group) Static(path, root string) { g.echo.Static(path, root) } @@ -64,6 +60,6 @@ func (g *Group) ServeFile(path, file string) { g.echo.ServeFile(path, file) } -func (g *Group) Group(prefix string, m ...Middleware) *Group { +func (g *Group) Group(prefix string, m ...MiddlewareFunc) *Group { return g.echo.Group(prefix, m...) } diff --git a/middleware/auth.go b/middleware/auth.go index f27e333e..e44a90f9 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -22,11 +22,11 @@ const ( func BasicAuth(fn BasicValidateFunc) echo.HandlerFunc { return func(c echo.Context) error { // Skip WebSocket - if (c.Request().Header.Get(echo.Upgrade)) == echo.WebSocket { + if (c.Request().Header().Get(echo.Upgrade)) == echo.WebSocket { return nil } - auth := c.Request().Header.Get(echo.Authorization) + auth := c.Request().Header().Get(echo.Authorization) l := len(Basic) if len(auth) > l+1 && auth[:l] == Basic { diff --git a/middleware/compress.go b/middleware/compress.go index 808ab3af..496a7624 100644 --- a/middleware/compress.go +++ b/middleware/compress.go @@ -53,7 +53,7 @@ func Gzip() echo.MiddlewareFunc { 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) { + if strings.Contains(c.Request().Header().Get(echo.AcceptEncoding), scheme) { w := writerPool.Get().(*gzip.Writer) w.Reset(c.Response().Writer()) defer func() { diff --git a/middleware/logger.go b/middleware/logger.go index f39224c8..912d6f35 100644 --- a/middleware/logger.go +++ b/middleware/logger.go @@ -16,9 +16,9 @@ func Logger() echo.MiddlewareFunc { logger := c.Logger() remoteAddr := req.RemoteAddr - if ip := req.Header.Get(echo.XRealIP); ip != "" { + if ip := req.Header().Get(echo.XRealIP); ip != "" { remoteAddr = ip - } else if ip = req.Header.Get(echo.XForwardedFor); ip != "" { + } else if ip = req.Header().Get(echo.XForwardedFor); ip != "" { remoteAddr = ip } else { remoteAddr, _, _ = net.SplitHostPort(remoteAddr) diff --git a/router.go b/router.go index 037b0125..0a406281 100644 --- a/router.go +++ b/router.go @@ -275,7 +275,8 @@ func (n *node) check405() HandlerFunc { return notFoundHandler } -func (r *Router) Find(method, path string, x *context) (h HandlerFunc, e *Echo) { +func (r *Router) Find(method, path string, context Context) (h HandlerFunc, e *Echo) { + x := context.X() h = notFoundHandler e = r.echo cn := r.tree // Current node as root @@ -401,11 +402,12 @@ End: } func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { - c := r.echo.pool.Get().(*context) - h, _ := r.Find(req.Method, req.URL.Path, c) - c.reset(req, w, r.echo) - if err := h(c); err != nil { - r.echo.httpErrorHandler(err, c) - } - r.echo.pool.Put(c) + // TODO: v2 + // c := r.echo.pool.Get().(*context) + // h, _ := r.Find(req.Method, req.URL.Path, c) + // c.reset(req, w, r.echo) + // if err := h(c); err != nil { + // r.echo.httpErrorHandler(err, c) + // } + // r.echo.pool.Put(c) }