mirror of
https://github.com/labstack/echo.git
synced 2024-12-22 20:06:21 +02:00
76125a53e5
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.
623 lines
17 KiB
Go
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()
|
|
}
|