diff options
Diffstat (limited to 'teleirc/matterbridge/vendor/github.com/labstack/echo/v4/echo.go')
| -rw-r--r-- | teleirc/matterbridge/vendor/github.com/labstack/echo/v4/echo.go | 980 |
1 files changed, 980 insertions, 0 deletions
diff --git a/teleirc/matterbridge/vendor/github.com/labstack/echo/v4/echo.go b/teleirc/matterbridge/vendor/github.com/labstack/echo/v4/echo.go new file mode 100644 index 0000000..f6d89b9 --- /dev/null +++ b/teleirc/matterbridge/vendor/github.com/labstack/echo/v4/echo.go @@ -0,0 +1,980 @@ +/* +Package echo implements high performance, minimalist Go web framework. + +Example: + + package main + + import ( + "net/http" + + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/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 ( + stdContext "context" + "crypto/tls" + "errors" + "fmt" + "io" + stdLog "log" + "net" + "net/http" + "os" + "reflect" + "runtime" + "sync" + "time" + + "github.com/labstack/gommon/color" + "github.com/labstack/gommon/log" + "golang.org/x/crypto/acme" + "golang.org/x/crypto/acme/autocert" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" +) + +type ( + // Echo is the top-level framework instance. + // + // Goroutine safety: Do not mutate Echo instance fields after server has started. Accessing these + // fields from handlers/middlewares and changing field values at the same time leads to data-races. + // Adding new routes after the server has been started is also not safe! + Echo struct { + filesystem + common + // startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get + // listener address info (on which interface/port was listener binded) without having data races. + startupMutex sync.RWMutex + colorer *color.Color + + // premiddleware are middlewares that are run before routing is done. In case a pre-middleware returns + // an error the router is not executed and the request will end up in the global error handler. + premiddleware []MiddlewareFunc + middleware []MiddlewareFunc + maxParam *int + router *Router + routers map[string]*Router + pool sync.Pool + + StdLogger *stdLog.Logger + 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 + JSONSerializer JSONSerializer + Validator Validator + Renderer Renderer + Logger Logger + IPExtractor IPExtractor + ListenerNetwork string + + // OnAddRouteHandler is called when Echo adds new route to specific host router. + OnAddRouteHandler func(host string, route Route, handler HandlerFunc, middleware []MiddlewareFunc) + } + + // 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 `json:"-"` + Message interface{} `json:"message"` + Internal error `json:"-"` // Stores the error returned by an external dependency + } + + // MiddlewareFunc defines a function to process middleware. + MiddlewareFunc func(next HandlerFunc) HandlerFunc + + // HandlerFunc defines a function to serve HTTP requests. + HandlerFunc func(c Context) error + + // HTTPErrorHandler is a centralized HTTP error handler. + HTTPErrorHandler func(err error, c Context) + + // Validator is the interface that wraps the Validate function. + Validator interface { + Validate(i interface{}) error + } + + // JSONSerializer is the interface that encodes and decodes JSON to and from interfaces. + JSONSerializer interface { + Serialize(c Context, i interface{}, indent string) error + Deserialize(c Context, 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{} + + // Common struct for Echo & Group. + common struct{} +) + +// HTTP methods +// NOTE: Deprecated, please use the stdlib constants directly instead. +const ( + CONNECT = http.MethodConnect + DELETE = http.MethodDelete + GET = http.MethodGet + HEAD = http.MethodHead + OPTIONS = http.MethodOptions + PATCH = http.MethodPatch + POST = http.MethodPost + // PROPFIND = "PROPFIND" + PUT = http.MethodPut + TRACE = http.MethodTrace +) + +// 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" + // PROPFIND Method can be used on collection and property resources. + PROPFIND = "PROPFIND" + // REPORT Method can be used to get information about a resource, see rfc 3253 + REPORT = "REPORT" + // RouteNotFound is special method type for routes handling "route not found" (404) cases + RouteNotFound = "echo_route_not_found" +) + +// Headers +const ( + HeaderAccept = "Accept" + HeaderAcceptEncoding = "Accept-Encoding" + // HeaderAllow is the name of the "Allow" header field used to list the set of methods + // advertised as supported by the target resource. Returning an Allow header is mandatory + // for status 405 (method not found) and useful for the OPTIONS method in responses. + // See RFC 7231: https://datatracker.ietf.org/doc/html/rfc7231#section-7.4.1 + 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" + HeaderRetryAfter = "Retry-After" + 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" + HeaderXCorrelationID = "X-Correlation-Id" + HeaderXRequestedWith = "X-Requested-With" + HeaderServer = "Server" + HeaderOrigin = "Origin" + HeaderCacheControl = "Cache-Control" + HeaderConnection = "Connection" + + // 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" + HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" + HeaderXCSRFToken = "X-CSRF-Token" + HeaderReferrerPolicy = "Referrer-Policy" +) + +const ( + // Version of Echo + Version = "4.10.0" + 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{ + http.MethodConnect, + http.MethodDelete, + http.MethodGet, + http.MethodHead, + http.MethodOptions, + http.MethodPatch, + http.MethodPost, + PROPFIND, + http.MethodPut, + http.MethodTrace, + REPORT, + } +) + +// 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) + ErrTooManyRequests = NewHTTPError(http.StatusTooManyRequests) + ErrBadRequest = NewHTTPError(http.StatusBadRequest) + ErrBadGateway = NewHTTPError(http.StatusBadGateway) + ErrInternalServerError = NewHTTPError(http.StatusInternalServerError) + ErrRequestTimeout = NewHTTPError(http.StatusRequestTimeout) + ErrServiceUnavailable = NewHTTPError(http.StatusServiceUnavailable) + 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") + ErrInvalidCertOrKeyType = errors.New("invalid cert or key type, must be string or []byte") + ErrInvalidListenerNetwork = errors.New("invalid listener network") +) + +// Error handlers +var ( + NotFoundHandler = func(c Context) error { + return ErrNotFound + } + + MethodNotAllowedHandler = func(c Context) error { + // See RFC 7231 section 7.4.1: An origin server MUST generate an Allow field in a 405 (Method Not Allowed) + // response and MAY do so in any other response. For disabled resources an empty Allow header may be returned + routerAllowMethods, ok := c.Get(ContextKeyHeaderAllow).(string) + if ok && routerAllowMethods != "" { + c.Response().Header().Set(HeaderAllow, routerAllowMethods) + } + return ErrMethodNotAllowed + } +) + +// New creates an instance of Echo. +func New() (e *Echo) { + e = &Echo{ + filesystem: createFilesystem(), + Server: new(http.Server), + TLSServer: new(http.Server), + AutoTLSManager: autocert.Manager{ + Prompt: autocert.AcceptTOS, + }, + Logger: log.New("echo"), + colorer: color.New(), + maxParam: new(int), + ListenerNetwork: "tcp", + } + e.Server.Handler = e + e.TLSServer.Handler = e + e.HTTPErrorHandler = e.DefaultHTTPErrorHandler + e.Binder = &DefaultBinder{} + e.JSONSerializer = &DefaultJSONSerializer{} + 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) + e.routers = map[string]*Router{} + 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 the default router. +func (e *Echo) Router() *Router { + return e.router +} + +// Routers returns the map of host => router. +func (e *Echo) Routers() map[string]*Router { + return e.routers +} + +// DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response +// with status code. +// +// NOTE: In case errors happens in middleware call-chain that is returning from handler (which did not return an error). +// When handler has already sent response (ala c.JSON()) and there is error in middleware that is returning from +// handler. Then the error that global error handler received will be ignored because we have already "commited" the +// response and status code header has been sent to the client. +func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) { + + if c.Response().Committed { + return + } + + he, ok := err.(*HTTPError) + if ok { + if he.Internal != nil { + if herr, ok := he.Internal.(*HTTPError); ok { + he = herr + } + } + } else { + he = &HTTPError{ + Code: http.StatusInternalServerError, + Message: http.StatusText(http.StatusInternalServerError), + } + } + + // Issue #1426 + code := he.Code + message := he.Message + if m, ok := he.Message.(string); ok { + if e.Debug { + message = Map{"message": m, "error": err.Error()} + } else { + message = Map{"message": m} + } + } + + // Send response + if c.Request().Method == http.MethodHead { // Issue #608 + err = c.NoContent(he.Code) + } else { + err = c.JSON(code, message) + } + 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(http.MethodConnect, 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(http.MethodDelete, 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(http.MethodGet, 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(http.MethodHead, 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(http.MethodOptions, 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(http.MethodPatch, 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(http.MethodPost, 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(http.MethodPut, 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(http.MethodTrace, path, h, m...) +} + +// RouteNotFound registers a special-case route which is executed when no other route is found (i.e. HTTP 404 cases) +// for current request URL. +// Path supports static and named/any parameters just like other http method is defined. Generally path is ended with +// wildcard/match-any character (`/*`, `/download/*` etc). +// +// Example: `e.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })` +func (e *Echo) RouteNotFound(path string, h HandlerFunc, m ...MiddlewareFunc) *Route { + return e.Add(RouteNotFound, path, h, m...) +} + +// Any registers a new route for all HTTP methods (supported by Echo) and path with matching handler +// in the router with optional route-level middleware. +// +// Note: this method only adds specific set of supported HTTP methods as handler and is not true +// "catch-any-arbitrary-method" way of matching requests. +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 +} + +func (common) file(path, file string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route, + m ...MiddlewareFunc) *Route { + return get(path, func(c Context) error { + return c.File(file) + }, m...) +} + +// 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.file(path, file, e.GET, m...) +} + +func (e *Echo) add(host, method, path string, handler HandlerFunc, middlewares ...MiddlewareFunc) *Route { + router := e.findRouter(host) + //FIXME: when handler+middleware are both nil ... make it behave like handler removal + name := handlerName(handler) + route := router.add(method, path, name, func(c Context) error { + h := applyMiddleware(handler, middlewares...) + return h(c) + }) + + if e.OnAddRouteHandler != nil { + e.OnAddRouteHandler(host, *route, handler, middlewares) + } + + return route +} + +// 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 { + return e.add("", method, path, handler, middleware...) +} + +// Host creates a new router group for the provided host and optional host-level middleware. +func (e *Echo) Host(name string, m ...MiddlewareFunc) (g *Group) { + e.routers[name] = NewRouter(e) + g = &Group{host: name, echo: e} + g.Use(m...) + return +} + +// 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 an 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 { + return e.router.Reverse(name, params...) +} + +// Routes returns the registered routes for default router. +// In case when Echo serves multiple hosts/domains use `e.Routers()["domain2.site"].Routes()` to get specific host routes. +func (e *Echo) Routes() []*Route { + return e.router.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) + var h HandlerFunc + + if e.premiddleware == nil { + e.findRouter(r.Host).Find(r.Method, GetPath(r), c) + h = c.Handler() + h = applyMiddleware(h, e.middleware...) + } else { + h = func(c Context) error { + e.findRouter(r.Host).Find(r.Method, GetPath(r), c) + h := c.Handler() + h = applyMiddleware(h, e.middleware...) + return h(c) + } + h = applyMiddleware(h, e.premiddleware...) + } + + // 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.startupMutex.Lock() + e.Server.Addr = address + if err := e.configureServer(e.Server); err != nil { + e.startupMutex.Unlock() + return err + } + e.startupMutex.Unlock() + return e.Server.Serve(e.Listener) +} + +// StartTLS starts an HTTPS server. +// If `certFile` or `keyFile` is `string` the values are treated as file paths. +// If `certFile` or `keyFile` is `[]byte` the values are treated as the certificate or key as-is. +func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) (err error) { + e.startupMutex.Lock() + var cert []byte + if cert, err = filepathOrContent(certFile); err != nil { + e.startupMutex.Unlock() + return + } + + var key []byte + if key, err = filepathOrContent(keyFile); err != nil { + e.startupMutex.Unlock() + return + } + + s := e.TLSServer + s.TLSConfig = new(tls.Config) + s.TLSConfig.Certificates = make([]tls.Certificate, 1) + if s.TLSConfig.Certificates[0], err = tls.X509KeyPair(cert, key); err != nil { + e.startupMutex.Unlock() + return + } + + e.configureTLS(address) + if err := e.configureServer(s); err != nil { + e.startupMutex.Unlock() + return err + } + e.startupMutex.Unlock() + return s.Serve(e.TLSListener) +} + +func filepathOrContent(fileOrContent interface{}) (content []byte, err error) { + switch v := fileOrContent.(type) { + case string: + return os.ReadFile(v) + case []byte: + return v, nil + default: + return nil, ErrInvalidCertOrKeyType + } +} + +// StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org. +func (e *Echo) StartAutoTLS(address string) error { + e.startupMutex.Lock() + s := e.TLSServer + s.TLSConfig = new(tls.Config) + s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate + s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, acme.ALPNProto) + + e.configureTLS(address) + if err := e.configureServer(s); err != nil { + e.startupMutex.Unlock() + return err + } + e.startupMutex.Unlock() + return s.Serve(e.TLSListener) +} + +func (e *Echo) configureTLS(address string) { + s := e.TLSServer + s.Addr = address + if !e.DisableHTTP2 { + s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "h2") + } +} + +// StartServer starts a custom http server. +func (e *Echo) StartServer(s *http.Server) (err error) { + e.startupMutex.Lock() + if err := e.configureServer(s); err != nil { + e.startupMutex.Unlock() + return err + } + if s.TLSConfig != nil { + e.startupMutex.Unlock() + return s.Serve(e.TLSListener) + } + e.startupMutex.Unlock() + return s.Serve(e.Listener) +} + +func (e *Echo) configureServer(s *http.Server) 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 { + l, err := newListener(s.Addr, e.ListenerNetwork) + if err != nil { + return err + } + e.Listener = l + } + if !e.HidePort { + e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr())) + } + return nil + } + if e.TLSListener == nil { + l, err := newListener(s.Addr, e.ListenerNetwork) + 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 nil +} + +// ListenerAddr returns net.Addr for Listener +func (e *Echo) ListenerAddr() net.Addr { + e.startupMutex.RLock() + defer e.startupMutex.RUnlock() + if e.Listener == nil { + return nil + } + return e.Listener.Addr() +} + +// TLSListenerAddr returns net.Addr for TLSListener +func (e *Echo) TLSListenerAddr() net.Addr { + e.startupMutex.RLock() + defer e.startupMutex.RUnlock() + if e.TLSListener == nil { + return nil + } + return e.TLSListener.Addr() +} + +// StartH2CServer starts a custom http/2 server with h2c (HTTP/2 Cleartext). +func (e *Echo) StartH2CServer(address string, h2s *http2.Server) error { + e.startupMutex.Lock() + // Setup + s := e.Server + s.Addr = address + e.colorer.SetOutput(e.Logger.Output()) + s.ErrorLog = e.StdLogger + s.Handler = h2c.NewHandler(e, h2s) + 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 e.Listener == nil { + l, err := newListener(s.Addr, e.ListenerNetwork) + if err != nil { + e.startupMutex.Unlock() + return err + } + e.Listener = l + } + if !e.HidePort { + e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr())) + } + e.startupMutex.Unlock() + return s.Serve(e.Listener) +} + +// Close immediately stops the server. +// It internally calls `http.Server#Close()`. +func (e *Echo) Close() error { + e.startupMutex.Lock() + defer e.startupMutex.Unlock() + if err := e.TLSServer.Close(); err != nil { + return err + } + return e.Server.Close() +} + +// Shutdown stops the server gracefully. +// It internally calls `http.Server#Shutdown()`. +func (e *Echo) Shutdown(ctx stdContext.Context) error { + e.startupMutex.Lock() + defer e.startupMutex.Unlock() + 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 { + if he.Internal == nil { + return fmt.Sprintf("code=%d, message=%v", he.Code, he.Message) + } + return fmt.Sprintf("code=%d, message=%v, internal=%v", he.Code, he.Message, he.Internal) +} + +// SetInternal sets error to HTTPError.Internal +func (he *HTTPError) SetInternal(err error) *HTTPError { + he.Internal = err + return he +} + +// WithInternal returns clone of HTTPError with err set to HTTPError.Internal field +func (he *HTTPError) WithInternal(err error) *HTTPError { + return &HTTPError{ + Code: he.Code, + Message: he.Message, + Internal: err, + } +} + +// Unwrap satisfies the Go 1.13 error wrapper interface. +func (he *HTTPError) Unwrap() error { + return he.Internal +} + +// 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) + c.SetResponse(NewResponse(w, c.Echo())) + err = next(c) + })).ServeHTTP(c.Response(), c.Request()) + return + } + } +} + +// GetPath returns RawPath, if it's empty returns Path from URL +// Difference between RawPath and Path is: +// - Path is where request path is stored. Value is stored in decoded form: /%47%6f%2f becomes /Go/. +// - RawPath is an optional field which only gets set if the default encoding is different from Path. +func GetPath(r *http.Request) string { + path := r.URL.RawPath + if path == "" { + path = r.URL.Path + } + return path +} + +func (e *Echo) findRouter(host string) *Router { + if len(e.routers) > 0 { + if r, ok := e.routers[host]; ok { + return r + } + } + return e.router +} + +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) { + if c, err = ln.AcceptTCP(); err != nil { + return + } else if err = c.(*net.TCPConn).SetKeepAlive(true); err != nil { + return + } + // Ignore error from setting the KeepAlivePeriod as some systems, such as + // OpenBSD, do not support setting TCP_USER_TIMEOUT on IPPROTO_TCP + _ = c.(*net.TCPConn).SetKeepAlivePeriod(3 * time.Minute) + return +} + +func newListener(address, network string) (*tcpKeepAliveListener, error) { + if network != "tcp" && network != "tcp4" && network != "tcp6" { + return nil, ErrInvalidListenerNetwork + } + l, err := net.Listen(network, address) + if err != nil { + return nil, err + } + return &tcpKeepAliveListener{l.(*net.TCPListener)}, nil +} + +func applyMiddleware(h HandlerFunc, middleware ...MiddlewareFunc) HandlerFunc { + for i := len(middleware) - 1; i >= 0; i-- { + h = middleware[i](h) + } + return h +} |
