1
0
mirror of https://github.com/labstack/echo.git synced 2024-12-22 20:06:21 +02:00
echo/echo.go
Russ Egan 76125a53e5 Fix for #681
Premiddleware may embed the echo context in their own custom context.  Therefore, it’s important to pass the context flowing down from the premiddleware chain down through the rest of the chain.
2016-10-21 14:10:29 -04:00

623 lines
17 KiB
Go

/*
Package echo implements a fast and unfancy HTTP server framework for Go (Golang).
Example:
package main
import (
"net/http"
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
"github.com/labstack/echo/middleware"
)
// Handler
func hello(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
func main() {
// Echo instance
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Routes
e.GET("/", hello)
// Start server
e.Run(standard.New(":1323"))
}
Learn more at https://echo.labstack.com
*/
package echo
import (
"bytes"
"errors"
"fmt"
"io"
"net/http"
"path"
"reflect"
"runtime"
"sync"
gcontext "github.com/labstack/echo/context"
"github.com/labstack/echo/engine"
"github.com/labstack/echo/log"
glog "github.com/labstack/gommon/log"
)
type (
// Echo is the top-level framework instance.
Echo struct {
server engine.Server
premiddleware []MiddlewareFunc
middleware []MiddlewareFunc
maxParam *int
notFoundHandler HandlerFunc
httpErrorHandler HTTPErrorHandler
binder Binder
renderer Renderer
pool sync.Pool
debug bool
router *Router
logger log.Logger
}
// Route contains a handler and information for matching against requests.
Route struct {
Method string
Path string
Handler string
}
// HTTPError represents an error that occurred while handling a request.
HTTPError struct {
Code int
Message string
}
// MiddlewareFunc defines a function to process middleware.
MiddlewareFunc func(HandlerFunc) HandlerFunc
// HandlerFunc defines a function to server HTTP requests.
HandlerFunc func(Context) error
// HTTPErrorHandler is a centralized HTTP error handler.
HTTPErrorHandler func(error, Context)
// Validator is the interface that wraps the Validate function.
Validator interface {
Validate() error
}
// Renderer is the interface that wraps the Render function.
Renderer interface {
Render(io.Writer, string, interface{}, Context) error
}
)
// HTTP methods
const (
CONNECT = "CONNECT"
DELETE = "DELETE"
GET = "GET"
HEAD = "HEAD"
OPTIONS = "OPTIONS"
PATCH = "PATCH"
POST = "POST"
PUT = "PUT"
TRACE = "TRACE"
)
// MIME types
const (
MIMEApplicationJSON = "application/json"
MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8
MIMEApplicationJavaScript = "application/javascript"
MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8
MIMEApplicationXML = "application/xml"
MIMEApplicationXMLCharsetUTF8 = MIMEApplicationXML + "; " + charsetUTF8
MIMEApplicationForm = "application/x-www-form-urlencoded"
MIMEApplicationProtobuf = "application/protobuf"
MIMEApplicationMsgpack = "application/msgpack"
MIMETextHTML = "text/html"
MIMETextHTMLCharsetUTF8 = MIMETextHTML + "; " + charsetUTF8
MIMETextPlain = "text/plain"
MIMETextPlainCharsetUTF8 = MIMETextPlain + "; " + charsetUTF8
MIMEMultipartForm = "multipart/form-data"
MIMEOctetStream = "application/octet-stream"
)
const (
charsetUTF8 = "charset=utf-8"
)
// Headers
const (
HeaderAcceptEncoding = "Accept-Encoding"
HeaderAllow = "Allow"
HeaderAuthorization = "Authorization"
HeaderContentDisposition = "Content-Disposition"
HeaderContentEncoding = "Content-Encoding"
HeaderContentLength = "Content-Length"
HeaderContentType = "Content-Type"
HeaderCookie = "Cookie"
HeaderSetCookie = "Set-Cookie"
HeaderIfModifiedSince = "If-Modified-Since"
HeaderLastModified = "Last-Modified"
HeaderLocation = "Location"
HeaderUpgrade = "Upgrade"
HeaderVary = "Vary"
HeaderWWWAuthenticate = "WWW-Authenticate"
HeaderXForwardedProto = "X-Forwarded-Proto"
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
HeaderXForwardedFor = "X-Forwarded-For"
HeaderXRealIP = "X-Real-IP"
HeaderServer = "Server"
HeaderOrigin = "Origin"
HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
HeaderAccessControlMaxAge = "Access-Control-Max-Age"
// Security
HeaderStrictTransportSecurity = "Strict-Transport-Security"
HeaderXContentTypeOptions = "X-Content-Type-Options"
HeaderXXSSProtection = "X-XSS-Protection"
HeaderXFrameOptions = "X-Frame-Options"
HeaderContentSecurityPolicy = "Content-Security-Policy"
HeaderXCSRFToken = "X-CSRF-Token"
)
var (
methods = [...]string{
CONNECT,
DELETE,
GET,
HEAD,
OPTIONS,
PATCH,
POST,
PUT,
TRACE,
}
)
// Errors
var (
ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType)
ErrNotFound = NewHTTPError(http.StatusNotFound)
ErrUnauthorized = NewHTTPError(http.StatusUnauthorized)
ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed)
ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
ErrRendererNotRegistered = errors.New("renderer not registered")
ErrInvalidRedirectCode = errors.New("invalid redirect status code")
ErrCookieNotFound = errors.New("cookie not found")
)
// Error handlers
var (
NotFoundHandler = func(c Context) error {
return ErrNotFound
}
MethodNotAllowedHandler = func(c Context) error {
return ErrMethodNotAllowed
}
)
// New creates an instance of Echo.
func New() (e *Echo) {
e = &Echo{maxParam: new(int)}
e.pool.New = func() interface{} {
return e.NewContext(nil, nil)
}
e.router = NewRouter(e)
// Defaults
e.SetHTTPErrorHandler(e.DefaultHTTPErrorHandler)
e.SetBinder(&binder{})
l := glog.New("echo")
l.SetLevel(glog.OFF)
e.SetLogger(l)
return
}
// NewContext returns a Context instance.
func (e *Echo) NewContext(req engine.Request, res engine.Response) Context {
return &context{
stdContext: gcontext.Background(),
request: req,
response: res,
store: make(store),
echo: e,
pvalues: make([]string, *e.maxParam),
handler: NotFoundHandler,
}
}
// Router returns router.
func (e *Echo) Router() *Router {
return e.router
}
// Logger returns the logger instance.
func (e *Echo) Logger() log.Logger {
return e.logger
}
// SetLogger defines a custom logger.
func (e *Echo) SetLogger(l log.Logger) {
e.logger = l
}
// SetLogOutput sets the output destination for the logger. Default value is `os.Std*`
func (e *Echo) SetLogOutput(w io.Writer) {
e.logger.SetOutput(w)
}
// SetLogLevel sets the log level for the logger. Default value ERROR.
func (e *Echo) SetLogLevel(l glog.Lvl) {
e.logger.SetLevel(l)
}
// DefaultHTTPErrorHandler invokes the default HTTP error handler.
func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
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 !c.Response().Committed() {
if c.Request().Method() == HEAD { // Issue #608
c.NoContent(code)
} else {
c.String(code, msg)
}
}
e.logger.Error(err)
}
// SetHTTPErrorHandler registers a custom Echo.HTTPErrorHandler.
func (e *Echo) SetHTTPErrorHandler(h HTTPErrorHandler) {
e.httpErrorHandler = h
}
// SetBinder registers a custom binder. It's invoked by `Context#Bind()`.
func (e *Echo) SetBinder(b Binder) {
e.binder = b
}
// Binder returns the binder instance.
func (e *Echo) Binder() Binder {
return e.binder
}
// SetRenderer registers an HTML template renderer. It's invoked by `Context#Render()`.
func (e *Echo) SetRenderer(r Renderer) {
e.renderer = r
}
// SetDebug enables/disables debug mode.
func (e *Echo) SetDebug(on bool) {
e.debug = on
}
// Debug returns debug mode (enabled or disabled).
func (e *Echo) Debug() bool {
return e.debug
}
// Pre adds middleware to the chain which is run before router.
func (e *Echo) Pre(middleware ...MiddlewareFunc) {
e.premiddleware = append(e.premiddleware, middleware...)
}
// Use adds middleware to the chain which is run after router.
func (e *Echo) Use(middleware ...MiddlewareFunc) {
e.middleware = append(e.middleware, middleware...)
}
// CONNECT registers a new CONNECT route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(CONNECT, path, h, m...)
}
// Connect is deprecated, use `CONNECT()` instead.
func (e *Echo) Connect(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.CONNECT(path, h, m...)
}
// DELETE registers a new DELETE route for a path with matching handler in the router
// with optional route-level middleware.
func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(DELETE, path, h, m...)
}
// Delete is deprecated, use `DELETE()` instead.
func (e *Echo) Delete(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.DELETE(path, h, m...)
}
// GET registers a new GET route for a path with matching handler in the router
// with optional route-level middleware.
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(GET, path, h, m...)
}
// Get is deprecated, use `GET()` instead.
func (e *Echo) Get(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.GET(path, h, m...)
}
// HEAD registers a new HEAD route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(HEAD, path, h, m...)
}
// Head is deprecated, use `HEAD()` instead.
func (e *Echo) Head(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.HEAD(path, h, m...)
}
// OPTIONS registers a new OPTIONS route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(OPTIONS, path, h, m...)
}
// Options is deprecated, use `OPTIONS()` instead.
func (e *Echo) Options(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.OPTIONS(path, h, m...)
}
// PATCH registers a new PATCH route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(PATCH, path, h, m...)
}
// Patch is deprecated, use `PATCH()` instead.
func (e *Echo) Patch(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.PATCH(path, h, m...)
}
// POST registers a new POST route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) POST(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(POST, path, h, m...)
}
// Post is deprecated, use `POST()` instead.
func (e *Echo) Post(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.POST(path, h, m...)
}
// PUT registers a new PUT route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(PUT, path, h, m...)
}
// Put is deprecated, use `PUT()` instead.
func (e *Echo) Put(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.PUT(path, h, m...)
}
// TRACE registers a new TRACE route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.add(TRACE, path, h, m...)
}
// Trace is deprecated, use `TRACE()` instead.
func (e *Echo) Trace(path string, h HandlerFunc, m ...MiddlewareFunc) {
e.TRACE(path, h, m...)
}
// Any registers a new route for all HTTP methods and path with matching handler
// in the router with optional route-level middleware.
func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
for _, m := range methods {
e.add(m, path, handler, middleware...)
}
}
// Match registers a new route for multiple HTTP methods and path with matching
// handler in the router with optional route-level middleware.
func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
for _, m := range methods {
e.add(m, path, handler, middleware...)
}
}
// Static registers a new route with path prefix to serve static files from the
// provided root directory.
func (e *Echo) Static(prefix, root string) {
e.GET(prefix+"*", func(c Context) error {
return c.File(path.Join(root, c.P(0)))
})
}
// File registers a new route with path to serve a static file.
func (e *Echo) File(path, file string) {
e.GET(path, func(c Context) error {
return c.File(file)
})
}
func (e *Echo) add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
name := handlerName(handler)
e.router.Add(method, path, func(c Context) error {
h := handler
// Chain middleware
for i := len(middleware) - 1; i >= 0; i-- {
h = middleware[i](h)
}
return h(c)
}, e)
r := Route{
Method: method,
Path: path,
Handler: name,
}
e.router.routes[method+path] = r
}
// Group creates a new router group with prefix and optional group-level middleware.
func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
g = &Group{prefix: prefix, echo: e}
g.Use(m...)
return
}
// URI generates a URI from handler.
func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string {
uri := new(bytes.Buffer)
ln := len(params)
n := 0
name := handlerName(handler)
for _, r := range e.router.routes {
if r.Handler == name {
for i, l := 0, len(r.Path); i < l; i++ {
if r.Path[i] == ':' && n < ln {
for ; i < l && r.Path[i] != '/'; i++ {
}
uri.WriteString(fmt.Sprintf("%v", params[n]))
n++
}
if i < l {
uri.WriteByte(r.Path[i])
}
}
break
}
}
return uri.String()
}
// URL is an alias for `URI` function.
func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
return e.URI(h, params...)
}
// Routes returns the registered routes.
func (e *Echo) Routes() []Route {
routes := []Route{}
for _, v := range e.router.routes {
routes = append(routes, v)
}
return routes
}
// AcquireContext returns an empty `Context` instance from the pool.
// You must be return the context by calling `ReleaseContext()`.
func (e *Echo) AcquireContext() Context {
return e.pool.Get().(Context)
}
// ReleaseContext returns the `Context` instance back to the pool.
// You must call it after `AcquireContext()`.
func (e *Echo) ReleaseContext(c Context) {
e.pool.Put(c)
}
func (e *Echo) ServeHTTP(req engine.Request, res engine.Response) {
c := e.pool.Get().(*context)
c.Reset(req, res)
// Middleware
h := func(carg Context) error {
method := req.Method()
path := req.URL().Path()
e.router.Find(method, path, c)
h := c.handler
for i := len(e.middleware) - 1; i >= 0; i-- {
h = e.middleware[i](h)
}
return h(carg)
}
// Premiddleware
for i := len(e.premiddleware) - 1; i >= 0; i-- {
h = e.premiddleware[i](h)
}
// Execute chain
if err := h(c); err != nil {
e.httpErrorHandler(err, c)
}
e.pool.Put(c)
}
// Run starts the HTTP server.
func (e *Echo) Run(s engine.Server) error {
e.server = s
s.SetHandler(e)
s.SetLogger(e.logger)
if e.Debug() {
e.SetLogLevel(glog.DEBUG)
e.logger.Debug("running in debug mode")
}
return s.Start()
}
// Stop stops the HTTP server.
func (e *Echo) Stop() error {
return e.server.Stop()
}
// NewHTTPError creates a new HTTPError instance.
func NewHTTPError(code int, msg ...string) *HTTPError {
he := &HTTPError{Code: code, Message: http.StatusText(code)}
if len(msg) > 0 {
m := msg[0]
he.Message = m
}
return he
}
// Error makes it compatible with `error` interface.
func (e *HTTPError) Error() string {
return e.Message
}
// WrapMiddleware wrap `echo.HandlerFunc` into `echo.MiddlewareFunc`.
func WrapMiddleware(h HandlerFunc) MiddlewareFunc {
return func(next HandlerFunc) HandlerFunc {
return func(c Context) error {
if err := h(c); err != nil {
return err
}
return next(c)
}
}
}
func handlerName(h HandlerFunc) string {
t := reflect.ValueOf(h).Type()
if t.Kind() == reflect.Func {
return runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
}
return t.String()
}