|
@@ -0,0 +1,770 @@
|
|
|
+/*
|
|
|
+Package echo implements high performance, minimalist Go web framework.
|
|
|
+
|
|
|
+Example:
|
|
|
+
|
|
|
+ package main
|
|
|
+
|
|
|
+ import (
|
|
|
+ "net/http"
|
|
|
+
|
|
|
+ "github.com/labstack/echo"
|
|
|
+ "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.Logger.Fatal(e.Start(":1323"))
|
|
|
+ }
|
|
|
+
|
|
|
+Learn more at https://echo.labstack.com
|
|
|
+*/
|
|
|
+package echo
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ stdContext "context"
|
|
|
+ "crypto/tls"
|
|
|
+ "errors"
|
|
|
+ "fmt"
|
|
|
+ "io"
|
|
|
+ stdLog "log"
|
|
|
+ "net"
|
|
|
+ "net/http"
|
|
|
+ "net/url"
|
|
|
+ "path"
|
|
|
+ "path/filepath"
|
|
|
+ "reflect"
|
|
|
+ "runtime"
|
|
|
+ "sync"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "github.com/labstack/gommon/color"
|
|
|
+ "github.com/labstack/gommon/log"
|
|
|
+ "golang.org/x/crypto/acme/autocert"
|
|
|
+)
|
|
|
+
|
|
|
+type (
|
|
|
+ // Echo is the top-level framework instance.
|
|
|
+ Echo struct {
|
|
|
+ stdLogger *stdLog.Logger
|
|
|
+ colorer *color.Color
|
|
|
+ premiddleware []MiddlewareFunc
|
|
|
+ middleware []MiddlewareFunc
|
|
|
+ maxParam *int
|
|
|
+ router *Router
|
|
|
+ notFoundHandler HandlerFunc
|
|
|
+ pool sync.Pool
|
|
|
+ Server *http.Server
|
|
|
+ TLSServer *http.Server
|
|
|
+ Listener net.Listener
|
|
|
+ TLSListener net.Listener
|
|
|
+ AutoTLSManager autocert.Manager
|
|
|
+ DisableHTTP2 bool
|
|
|
+ Debug bool
|
|
|
+ HideBanner bool
|
|
|
+ HidePort bool
|
|
|
+ HTTPErrorHandler HTTPErrorHandler
|
|
|
+ Binder Binder
|
|
|
+ Validator Validator
|
|
|
+ Renderer Renderer
|
|
|
+ Logger Logger
|
|
|
+ }
|
|
|
+
|
|
|
+ // Route contains a handler and information for matching against requests.
|
|
|
+ Route struct {
|
|
|
+ Method string `json:"method"`
|
|
|
+ Path string `json:"path"`
|
|
|
+ Name string `json:"name"`
|
|
|
+ }
|
|
|
+
|
|
|
+ // HTTPError represents an error that occurred while handling a request.
|
|
|
+ HTTPError struct {
|
|
|
+ Code int
|
|
|
+ Message interface{}
|
|
|
+ Internal error // Stores the error returned by an external dependency
|
|
|
+ }
|
|
|
+
|
|
|
+ // MiddlewareFunc defines a function to process middleware.
|
|
|
+ MiddlewareFunc func(HandlerFunc) HandlerFunc
|
|
|
+
|
|
|
+ // HandlerFunc defines a function to serve 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(i interface{}) error
|
|
|
+ }
|
|
|
+
|
|
|
+ // Renderer is the interface that wraps the Render function.
|
|
|
+ Renderer interface {
|
|
|
+ Render(io.Writer, string, interface{}, Context) error
|
|
|
+ }
|
|
|
+
|
|
|
+ // Map defines a generic map of type `map[string]interface{}`.
|
|
|
+ Map map[string]interface{}
|
|
|
+
|
|
|
+ // i is the interface for Echo and Group.
|
|
|
+ i interface {
|
|
|
+ GET(string, HandlerFunc, ...MiddlewareFunc) *Route
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+// HTTP methods
|
|
|
+const (
|
|
|
+ CONNECT = "CONNECT"
|
|
|
+ DELETE = "DELETE"
|
|
|
+ GET = "GET"
|
|
|
+ HEAD = "HEAD"
|
|
|
+ OPTIONS = "OPTIONS"
|
|
|
+ PATCH = "PATCH"
|
|
|
+ POST = "POST"
|
|
|
+ PROPFIND = "PROPFIND"
|
|
|
+ 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
|
|
|
+ MIMETextXML = "text/xml"
|
|
|
+ MIMETextXMLCharsetUTF8 = MIMETextXML + "; " + 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 (
|
|
|
+ HeaderAccept = "Accept"
|
|
|
+ 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"
|
|
|
+ HeaderXForwardedFor = "X-Forwarded-For"
|
|
|
+ HeaderXForwardedProto = "X-Forwarded-Proto"
|
|
|
+ HeaderXForwardedProtocol = "X-Forwarded-Protocol"
|
|
|
+ HeaderXForwardedSsl = "X-Forwarded-Ssl"
|
|
|
+ HeaderXUrlScheme = "X-Url-Scheme"
|
|
|
+ HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
|
|
|
+ HeaderXRealIP = "X-Real-IP"
|
|
|
+ HeaderXRequestID = "X-Request-ID"
|
|
|
+ HeaderXRequestedWith = "X-Requested-With"
|
|
|
+ HeaderServer = "Server"
|
|
|
+ HeaderOrigin = "Origin"
|
|
|
+
|
|
|
+ // Access control
|
|
|
+ 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"
|
|
|
+)
|
|
|
+
|
|
|
+const (
|
|
|
+ Version = "3.3.6"
|
|
|
+ website = "https://echo.labstack.com"
|
|
|
+ // http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
|
|
|
+ banner = `
|
|
|
+ ____ __
|
|
|
+ / __/___/ / ___
|
|
|
+ / _// __/ _ \/ _ \
|
|
|
+/___/\__/_//_/\___/ %s
|
|
|
+High performance, minimalist Go web framework
|
|
|
+%s
|
|
|
+____________________________________O/_______
|
|
|
+ O\
|
|
|
+`
|
|
|
+)
|
|
|
+
|
|
|
+var (
|
|
|
+ methods = [...]string{
|
|
|
+ CONNECT,
|
|
|
+ DELETE,
|
|
|
+ GET,
|
|
|
+ HEAD,
|
|
|
+ OPTIONS,
|
|
|
+ PATCH,
|
|
|
+ POST,
|
|
|
+ PROPFIND,
|
|
|
+ PUT,
|
|
|
+ TRACE,
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+// Errors
|
|
|
+var (
|
|
|
+ ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType)
|
|
|
+ ErrNotFound = NewHTTPError(http.StatusNotFound)
|
|
|
+ ErrUnauthorized = NewHTTPError(http.StatusUnauthorized)
|
|
|
+ ErrForbidden = NewHTTPError(http.StatusForbidden)
|
|
|
+ ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed)
|
|
|
+ ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
|
|
|
+ ErrValidatorNotRegistered = errors.New("validator not registered")
|
|
|
+ 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{
|
|
|
+ Server: new(http.Server),
|
|
|
+ TLSServer: new(http.Server),
|
|
|
+ AutoTLSManager: autocert.Manager{
|
|
|
+ Prompt: autocert.AcceptTOS,
|
|
|
+ },
|
|
|
+ Logger: log.New("echo"),
|
|
|
+ colorer: color.New(),
|
|
|
+ maxParam: new(int),
|
|
|
+ }
|
|
|
+ e.Server.Handler = e
|
|
|
+ e.TLSServer.Handler = e
|
|
|
+ e.HTTPErrorHandler = e.DefaultHTTPErrorHandler
|
|
|
+ e.Binder = &DefaultBinder{}
|
|
|
+ e.Logger.SetLevel(log.ERROR)
|
|
|
+ e.stdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)
|
|
|
+ e.pool.New = func() interface{} {
|
|
|
+ return e.NewContext(nil, nil)
|
|
|
+ }
|
|
|
+ e.router = NewRouter(e)
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+// NewContext returns a Context instance.
|
|
|
+func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) Context {
|
|
|
+ return &context{
|
|
|
+ request: r,
|
|
|
+ response: NewResponse(w, e),
|
|
|
+ store: make(Map),
|
|
|
+ echo: e,
|
|
|
+ pvalues: make([]string, *e.maxParam),
|
|
|
+ handler: NotFoundHandler,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Router returns router.
|
|
|
+func (e *Echo) Router() *Router {
|
|
|
+ return e.router
|
|
|
+}
|
|
|
+
|
|
|
+// DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response
|
|
|
+// with status code.
|
|
|
+func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
|
|
|
+ var (
|
|
|
+ code = http.StatusInternalServerError
|
|
|
+ msg interface{}
|
|
|
+ )
|
|
|
+
|
|
|
+ if he, ok := err.(*HTTPError); ok {
|
|
|
+ code = he.Code
|
|
|
+ msg = he.Message
|
|
|
+ if he.Internal != nil {
|
|
|
+ err = fmt.Errorf("%v, %v", err, he.Internal)
|
|
|
+ }
|
|
|
+ } else if e.Debug {
|
|
|
+ msg = err.Error()
|
|
|
+ } else {
|
|
|
+ msg = http.StatusText(code)
|
|
|
+ }
|
|
|
+ if _, ok := msg.(string); ok {
|
|
|
+ msg = Map{"message": msg}
|
|
|
+ }
|
|
|
+
|
|
|
+ // Send response
|
|
|
+ if !c.Response().Committed {
|
|
|
+ if c.Request().Method == HEAD { // Issue #608
|
|
|
+ err = c.NoContent(code)
|
|
|
+ } else {
|
|
|
+ err = c.JSON(code, msg)
|
|
|
+ }
|
|
|
+ if err != nil {
|
|
|
+ e.Logger.Error(err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 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) *Route {
|
|
|
+ return e.Add(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) *Route {
|
|
|
+ return e.Add(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) *Route {
|
|
|
+ return e.Add(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) *Route {
|
|
|
+ return e.Add(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) *Route {
|
|
|
+ return e.Add(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) *Route {
|
|
|
+ return e.Add(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) *Route {
|
|
|
+ return e.Add(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) *Route {
|
|
|
+ return e.Add(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) *Route {
|
|
|
+ return e.Add(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) []*Route {
|
|
|
+ routes := make([]*Route, len(methods))
|
|
|
+ for i, m := range methods {
|
|
|
+ routes[i] = e.Add(m, path, handler, middleware...)
|
|
|
+ }
|
|
|
+ return routes
|
|
|
+}
|
|
|
+
|
|
|
+// 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) []*Route {
|
|
|
+ routes := make([]*Route, len(methods))
|
|
|
+ for i, m := range methods {
|
|
|
+ routes[i] = e.Add(m, path, handler, middleware...)
|
|
|
+ }
|
|
|
+ return routes
|
|
|
+}
|
|
|
+
|
|
|
+// Static registers a new route with path prefix to serve static files from the
|
|
|
+// provided root directory.
|
|
|
+func (e *Echo) Static(prefix, root string) *Route {
|
|
|
+ if root == "" {
|
|
|
+ root = "." // For security we want to restrict to CWD.
|
|
|
+ }
|
|
|
+ return static(e, prefix, root)
|
|
|
+}
|
|
|
+
|
|
|
+func static(i i, prefix, root string) *Route {
|
|
|
+ h := func(c Context) error {
|
|
|
+ p, err := url.PathUnescape(c.Param("*"))
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
|
|
|
+ return c.File(name)
|
|
|
+ }
|
|
|
+ i.GET(prefix, h)
|
|
|
+ if prefix == "/" {
|
|
|
+ return i.GET(prefix+"*", h)
|
|
|
+ }
|
|
|
+
|
|
|
+ return i.GET(prefix+"/*", h)
|
|
|
+}
|
|
|
+
|
|
|
+// File registers a new route with path to serve a static file with optional route-level middleware.
|
|
|
+func (e *Echo) File(path, file string, m ...MiddlewareFunc) *Route {
|
|
|
+ return e.GET(path, func(c Context) error {
|
|
|
+ return c.File(file)
|
|
|
+ }, m...)
|
|
|
+}
|
|
|
+
|
|
|
+// Add registers a new route for an HTTP method and path with matching handler
|
|
|
+// in the router with optional route-level middleware.
|
|
|
+func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
|
|
|
+ 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)
|
|
|
+ })
|
|
|
+ r := &Route{
|
|
|
+ Method: method,
|
|
|
+ Path: path,
|
|
|
+ Name: name,
|
|
|
+ }
|
|
|
+ e.router.routes[method+path] = r
|
|
|
+ return 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 {
|
|
|
+ name := handlerName(handler)
|
|
|
+ return e.Reverse(name, params...)
|
|
|
+}
|
|
|
+
|
|
|
+// URL is an alias for `URI` function.
|
|
|
+func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
|
|
|
+ return e.URI(h, params...)
|
|
|
+}
|
|
|
+
|
|
|
+// Reverse generates an URL from route name and provided parameters.
|
|
|
+func (e *Echo) Reverse(name string, params ...interface{}) string {
|
|
|
+ uri := new(bytes.Buffer)
|
|
|
+ ln := len(params)
|
|
|
+ n := 0
|
|
|
+ for _, r := range e.router.routes {
|
|
|
+ if r.Name == 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()
|
|
|
+}
|
|
|
+
|
|
|
+// Routes returns the registered routes.
|
|
|
+func (e *Echo) Routes() []*Route {
|
|
|
+ routes := make([]*Route, 0, len(e.router.routes))
|
|
|
+ for _, v := range e.router.routes {
|
|
|
+ routes = append(routes, v)
|
|
|
+ }
|
|
|
+ return routes
|
|
|
+}
|
|
|
+
|
|
|
+// AcquireContext returns an empty `Context` instance from the pool.
|
|
|
+// You must 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)
|
|
|
+}
|
|
|
+
|
|
|
+// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.
|
|
|
+func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
+ // Acquire context
|
|
|
+ c := e.pool.Get().(*context)
|
|
|
+ c.Reset(r, w)
|
|
|
+
|
|
|
+ h := NotFoundHandler
|
|
|
+
|
|
|
+ if e.premiddleware == nil {
|
|
|
+ e.router.Find(r.Method, getPath(r), c)
|
|
|
+ h = c.Handler()
|
|
|
+ for i := len(e.middleware) - 1; i >= 0; i-- {
|
|
|
+ h = e.middleware[i](h)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ h = func(c Context) error {
|
|
|
+ e.router.Find(r.Method, getPath(r), c)
|
|
|
+ h := c.Handler()
|
|
|
+ for i := len(e.middleware) - 1; i >= 0; i-- {
|
|
|
+ h = e.middleware[i](h)
|
|
|
+ }
|
|
|
+ return h(c)
|
|
|
+ }
|
|
|
+ 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)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Release context
|
|
|
+ e.pool.Put(c)
|
|
|
+}
|
|
|
+
|
|
|
+// Start starts an HTTP server.
|
|
|
+func (e *Echo) Start(address string) error {
|
|
|
+ e.Server.Addr = address
|
|
|
+ return e.StartServer(e.Server)
|
|
|
+}
|
|
|
+
|
|
|
+// StartTLS starts an HTTPS server.
|
|
|
+func (e *Echo) StartTLS(address string, certFile, keyFile string) (err error) {
|
|
|
+ if certFile == "" || keyFile == "" {
|
|
|
+ return errors.New("invalid tls configuration")
|
|
|
+ }
|
|
|
+ s := e.TLSServer
|
|
|
+ s.TLSConfig = new(tls.Config)
|
|
|
+ s.TLSConfig.Certificates = make([]tls.Certificate, 1)
|
|
|
+ s.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ return e.startTLS(address)
|
|
|
+}
|
|
|
+
|
|
|
+// StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org.
|
|
|
+func (e *Echo) StartAutoTLS(address string) error {
|
|
|
+ if e.Listener == nil {
|
|
|
+ go http.ListenAndServe(":http", e.AutoTLSManager.HTTPHandler(nil))
|
|
|
+ }
|
|
|
+
|
|
|
+ s := e.TLSServer
|
|
|
+ s.TLSConfig = new(tls.Config)
|
|
|
+ s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate
|
|
|
+ return e.startTLS(address)
|
|
|
+}
|
|
|
+
|
|
|
+func (e *Echo) startTLS(address string) error {
|
|
|
+ s := e.TLSServer
|
|
|
+ s.Addr = address
|
|
|
+ if !e.DisableHTTP2 {
|
|
|
+ s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "h2")
|
|
|
+ }
|
|
|
+ return e.StartServer(e.TLSServer)
|
|
|
+}
|
|
|
+
|
|
|
+// StartServer starts a custom http server.
|
|
|
+func (e *Echo) StartServer(s *http.Server) (err error) {
|
|
|
+ // Setup
|
|
|
+ e.colorer.SetOutput(e.Logger.Output())
|
|
|
+ s.ErrorLog = e.stdLogger
|
|
|
+ s.Handler = e
|
|
|
+ if e.Debug {
|
|
|
+ e.Logger.SetLevel(log.DEBUG)
|
|
|
+ }
|
|
|
+
|
|
|
+ if !e.HideBanner {
|
|
|
+ e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website))
|
|
|
+ }
|
|
|
+
|
|
|
+ if s.TLSConfig == nil {
|
|
|
+ if e.Listener == nil {
|
|
|
+ e.Listener, err = newListener(s.Addr)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !e.HidePort {
|
|
|
+ e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
|
|
|
+ }
|
|
|
+ return s.Serve(e.Listener)
|
|
|
+ }
|
|
|
+ if e.TLSListener == nil {
|
|
|
+ l, err := newListener(s.Addr)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ e.TLSListener = tls.NewListener(l, s.TLSConfig)
|
|
|
+ }
|
|
|
+ if !e.HidePort {
|
|
|
+ e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
|
|
|
+ }
|
|
|
+ return s.Serve(e.TLSListener)
|
|
|
+}
|
|
|
+
|
|
|
+// Close immediately stops the server.
|
|
|
+// It internally calls `http.Server#Close()`.
|
|
|
+func (e *Echo) Close() error {
|
|
|
+ if err := e.TLSServer.Close(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return e.Server.Close()
|
|
|
+}
|
|
|
+
|
|
|
+// Shutdown stops server the gracefully.
|
|
|
+// It internally calls `http.Server#Shutdown()`.
|
|
|
+func (e *Echo) Shutdown(ctx stdContext.Context) error {
|
|
|
+ if err := e.TLSServer.Shutdown(ctx); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return e.Server.Shutdown(ctx)
|
|
|
+}
|
|
|
+
|
|
|
+// NewHTTPError creates a new HTTPError instance.
|
|
|
+func NewHTTPError(code int, message ...interface{}) *HTTPError {
|
|
|
+ he := &HTTPError{Code: code, Message: http.StatusText(code)}
|
|
|
+ if len(message) > 0 {
|
|
|
+ he.Message = message[0]
|
|
|
+ }
|
|
|
+ return he
|
|
|
+}
|
|
|
+
|
|
|
+// Error makes it compatible with `error` interface.
|
|
|
+func (he *HTTPError) Error() string {
|
|
|
+ return fmt.Sprintf("code=%d, message=%v", he.Code, he.Message)
|
|
|
+}
|
|
|
+
|
|
|
+// WrapHandler wraps `http.Handler` into `echo.HandlerFunc`.
|
|
|
+func WrapHandler(h http.Handler) HandlerFunc {
|
|
|
+ return func(c Context) error {
|
|
|
+ h.ServeHTTP(c.Response(), c.Request())
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// WrapMiddleware wraps `func(http.Handler) http.Handler` into `echo.MiddlewareFunc`
|
|
|
+func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc {
|
|
|
+ return func(next HandlerFunc) HandlerFunc {
|
|
|
+ return func(c Context) (err error) {
|
|
|
+ m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
+ c.SetRequest(r)
|
|
|
+ err = next(c)
|
|
|
+ })).ServeHTTP(c.Response(), c.Request())
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func getPath(r *http.Request) string {
|
|
|
+ path := r.URL.RawPath
|
|
|
+ if path == "" {
|
|
|
+ path = r.URL.Path
|
|
|
+ }
|
|
|
+ return path
|
|
|
+}
|
|
|
+
|
|
|
+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()
|
|
|
+}
|
|
|
+
|
|
|
+// // PathUnescape is wraps `url.PathUnescape`
|
|
|
+// func PathUnescape(s string) (string, error) {
|
|
|
+// return url.PathUnescape(s)
|
|
|
+// }
|
|
|
+
|
|
|
+// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
|
|
|
+// connections. It's used by ListenAndServe and ListenAndServeTLS so
|
|
|
+// dead TCP connections (e.g. closing laptop mid-download) eventually
|
|
|
+// go away.
|
|
|
+type tcpKeepAliveListener struct {
|
|
|
+ *net.TCPListener
|
|
|
+}
|
|
|
+
|
|
|
+func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
|
|
+ tc, err := ln.AcceptTCP()
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ tc.SetKeepAlive(true)
|
|
|
+ tc.SetKeepAlivePeriod(3 * time.Minute)
|
|
|
+ return tc, nil
|
|
|
+}
|
|
|
+
|
|
|
+func newListener(address string) (*tcpKeepAliveListener, error) {
|
|
|
+ l, err := net.Listen("tcp", address)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &tcpKeepAliveListener{l.(*net.TCPListener)}, nil
|
|
|
+}
|