2016-03-20 00:47:20 +02:00
|
|
|
/*
|
2016-11-29 06:30:42 +02:00
|
|
|
Package echo implements high performance, minimalist Go web framework.
|
2016-03-20 00:47:20 +02:00
|
|
|
|
|
|
|
Example:
|
|
|
|
|
2016-11-15 09:54:21 +02:00
|
|
|
package main
|
2016-03-20 00:47:20 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
import (
|
|
|
|
"github.com/labstack/echo/v5"
|
|
|
|
"github.com/labstack/echo/v5/middleware"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
)
|
2016-03-20 00:47:20 +02:00
|
|
|
|
2016-11-15 09:54:21 +02:00
|
|
|
// Handler
|
|
|
|
func hello(c echo.Context) error {
|
|
|
|
return c.String(http.StatusOK, "Hello, World!")
|
|
|
|
}
|
2016-03-20 00:47:20 +02:00
|
|
|
|
2016-11-15 09:54:21 +02:00
|
|
|
func main() {
|
|
|
|
// Echo instance
|
|
|
|
e := echo.New()
|
2016-03-20 00:47:20 +02:00
|
|
|
|
2016-11-15 09:54:21 +02:00
|
|
|
// Middleware
|
|
|
|
e.Use(middleware.Logger())
|
|
|
|
e.Use(middleware.Recover())
|
2016-03-20 00:47:20 +02:00
|
|
|
|
2016-11-15 09:54:21 +02:00
|
|
|
// Routes
|
|
|
|
e.GET("/", hello)
|
2016-03-20 00:47:20 +02:00
|
|
|
|
2016-11-15 09:54:21 +02:00
|
|
|
// Start server
|
2021-07-15 22:34:01 +02:00
|
|
|
if err := e.Start(":8080"); err != http.ErrServerClosed {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2016-11-15 09:54:21 +02:00
|
|
|
}
|
2016-03-20 00:47:20 +02:00
|
|
|
|
2016-05-24 23:51:26 +02:00
|
|
|
Learn more at https://echo.labstack.com
|
2016-03-20 00:47:20 +02:00
|
|
|
*/
|
2015-03-27 23:35:15 +02:00
|
|
|
package echo
|
|
|
|
|
|
|
|
import (
|
2018-04-03 13:09:21 +02:00
|
|
|
stdContext "context"
|
2015-04-03 05:18:34 +02:00
|
|
|
"errors"
|
2015-04-29 03:53:57 +02:00
|
|
|
"fmt"
|
2015-04-07 22:02:23 +02:00
|
|
|
"io"
|
2021-07-15 22:34:01 +02:00
|
|
|
"io/fs"
|
2015-03-27 23:35:15 +02:00
|
|
|
"net/http"
|
2021-07-15 22:34:01 +02:00
|
|
|
"net/url"
|
|
|
|
"os"
|
|
|
|
"os/signal"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2015-03-27 23:35:15 +02:00
|
|
|
"sync"
|
|
|
|
)
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// Echo is the top-level framework instance.
|
|
|
|
// Note: replacing/nilling public fields is not coroutine/thread-safe and can cause data-races/panics.
|
|
|
|
type Echo struct {
|
|
|
|
// premiddleware are middlewares that are run for every request before routing is done
|
|
|
|
premiddleware []MiddlewareFunc
|
|
|
|
// middleware are middlewares that are run after router found a matching route (not found and method not found are also matches)
|
|
|
|
middleware []MiddlewareFunc
|
2015-06-01 09:07:53 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
router Router
|
|
|
|
routers map[string]Router
|
|
|
|
routerCreator func(e *Echo) Router
|
2015-05-23 05:26:52 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
contextPool sync.Pool
|
|
|
|
// contextPathParamAllocSize holds maximum parameter count for all added routes. This is necessary info for context
|
|
|
|
// creation time so we can allocate path parameter values slice.
|
|
|
|
contextPathParamAllocSize int
|
2015-05-23 05:26:52 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// NewContextFunc allows using custom context implementations, instead of default *echo.context
|
|
|
|
NewContextFunc func(e *Echo, pathParamAllocSize int) ServableContext
|
|
|
|
Debug bool
|
|
|
|
HTTPErrorHandler HTTPErrorHandler
|
|
|
|
Binder Binder
|
|
|
|
JSONSerializer JSONSerializer
|
|
|
|
Validator Validator
|
|
|
|
Renderer Renderer
|
|
|
|
Logger Logger
|
|
|
|
IPExtractor IPExtractor
|
2016-02-09 03:26:00 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// Filesystem is file system used by Static and File handlers to access files.
|
|
|
|
// Defaults to os.DirFS(".")
|
|
|
|
//
|
|
|
|
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
|
|
|
|
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
|
|
|
|
// including `assets/images` as their prefix.
|
|
|
|
Filesystem fs.FS
|
|
|
|
}
|
2015-04-19 01:47:48 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// JSONSerializer is the interface that encodes and decodes JSON to and from interfaces.
|
|
|
|
type JSONSerializer interface {
|
|
|
|
Serialize(c Context, i interface{}, indent string) error
|
|
|
|
Deserialize(c Context, i interface{}) error
|
|
|
|
}
|
2015-04-19 01:47:48 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// HTTPErrorHandler is a centralized HTTP error handler.
|
|
|
|
type HTTPErrorHandler func(c Context, err error)
|
2015-08-01 04:25:03 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// HandlerFunc defines a function to serve HTTP requests.
|
|
|
|
type HandlerFunc func(c Context) error
|
2021-07-05 21:33:19 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// MiddlewareFunc defines a function to process middleware.
|
|
|
|
type MiddlewareFunc func(next HandlerFunc) HandlerFunc
|
2016-10-14 01:45:27 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// MiddlewareConfigurator defines interface for creating middleware handlers with possibility to return configuration errors instead of panicking.
|
|
|
|
type MiddlewareConfigurator interface {
|
|
|
|
ToMiddleware() (MiddlewareFunc, error)
|
|
|
|
}
|
2016-10-31 00:03:18 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// Validator is the interface that wraps the Validate function.
|
|
|
|
type Validator interface {
|
|
|
|
Validate(i interface{}) error
|
|
|
|
}
|
2015-03-27 23:35:15 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// Renderer is the interface that wraps the Render function.
|
|
|
|
type Renderer interface {
|
|
|
|
Render(io.Writer, string, interface{}, Context) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map defines a generic map of type `map[string]interface{}`.
|
|
|
|
type Map map[string]interface{}
|
2018-10-15 09:31:26 +02:00
|
|
|
|
2016-04-06 16:28:53 +02:00
|
|
|
// MIME types
|
2016-03-20 00:47:20 +02:00
|
|
|
const (
|
2016-04-06 16:28:53 +02:00
|
|
|
MIMEApplicationJSON = "application/json"
|
|
|
|
MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8
|
|
|
|
MIMEApplicationJavaScript = "application/javascript"
|
|
|
|
MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8
|
|
|
|
MIMEApplicationXML = "application/xml"
|
|
|
|
MIMEApplicationXMLCharsetUTF8 = MIMEApplicationXML + "; " + charsetUTF8
|
2017-02-28 22:04:29 +02:00
|
|
|
MIMETextXML = "text/xml"
|
|
|
|
MIMETextXMLCharsetUTF8 = MIMETextXML + "; " + charsetUTF8
|
2016-04-06 16:28:53 +02:00
|
|
|
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"
|
2016-03-20 00:47:20 +02:00
|
|
|
)
|
2015-05-11 07:34:31 +02:00
|
|
|
|
2016-03-20 00:47:20 +02:00
|
|
|
const (
|
2016-12-26 07:42:46 +02:00
|
|
|
charsetUTF8 = "charset=UTF-8"
|
2019-01-10 01:19:48 +02:00
|
|
|
// PROPFIND Method can be used on collection and property resources.
|
2019-01-27 00:39:45 +02:00
|
|
|
PROPFIND = "PROPFIND"
|
2019-06-09 18:11:18 +02:00
|
|
|
// REPORT Method can be used to get information about a resource, see rfc 3253
|
|
|
|
REPORT = "REPORT"
|
2016-03-20 00:47:20 +02:00
|
|
|
)
|
2015-07-21 04:24:33 +02:00
|
|
|
|
2016-03-20 00:47:20 +02:00
|
|
|
// Headers
|
|
|
|
const (
|
2021-12-04 20:02:11 +02:00
|
|
|
HeaderAccept = "Accept"
|
|
|
|
HeaderAcceptEncoding = "Accept-Encoding"
|
2021-12-09 21:57:20 +02:00
|
|
|
// 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
|
2017-06-04 19:39:08 +02:00
|
|
|
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"
|
2022-01-21 18:32:53 +02:00
|
|
|
HeaderRetryAfter = "Retry-After"
|
2017-06-04 19:39:08 +02:00
|
|
|
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"
|
2022-03-01 09:56:46 +02:00
|
|
|
HeaderXRealIP = "X-Real-Ip"
|
|
|
|
HeaderXRequestID = "X-Request-Id"
|
|
|
|
HeaderXCorrelationID = "X-Correlation-Id"
|
2018-03-06 11:52:28 +02:00
|
|
|
HeaderXRequestedWith = "X-Requested-With"
|
2017-06-04 19:39:08 +02:00
|
|
|
HeaderServer = "Server"
|
|
|
|
HeaderOrigin = "Origin"
|
2022-03-02 01:11:28 +02:00
|
|
|
HeaderCacheControl = "Cache-Control"
|
|
|
|
HeaderConnection = "Connection"
|
2017-06-04 19:39:08 +02:00
|
|
|
|
|
|
|
// Access control
|
2016-04-08 01:16:58 +02:00
|
|
|
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"
|
2016-05-03 07:41:07 +02:00
|
|
|
|
|
|
|
// Security
|
2019-02-27 08:32:07 +02:00
|
|
|
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"
|
2019-08-02 00:27:09 +02:00
|
|
|
HeaderReferrerPolicy = "Referrer-Policy"
|
2015-03-27 23:35:15 +02:00
|
|
|
)
|
|
|
|
|
2017-05-12 19:38:02 +02:00
|
|
|
const (
|
2019-01-10 01:19:48 +02:00
|
|
|
// Version of Echo
|
2021-07-15 22:34:01 +02:00
|
|
|
Version = "5.0.0-alpha"
|
2016-03-20 00:47:20 +02:00
|
|
|
)
|
2015-07-27 17:43:11 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
var methods = [...]string{
|
|
|
|
http.MethodConnect,
|
|
|
|
http.MethodDelete,
|
|
|
|
http.MethodGet,
|
|
|
|
http.MethodHead,
|
|
|
|
http.MethodOptions,
|
|
|
|
http.MethodPatch,
|
|
|
|
http.MethodPost,
|
|
|
|
PROPFIND,
|
|
|
|
http.MethodPut,
|
|
|
|
http.MethodTrace,
|
|
|
|
REPORT,
|
|
|
|
}
|
2015-04-01 17:05:54 +02:00
|
|
|
|
2015-07-08 02:34:59 +02:00
|
|
|
// New creates an instance of Echo.
|
2021-07-15 22:34:01 +02:00
|
|
|
func New() *Echo {
|
|
|
|
logger := newJSONLogger(os.Stdout)
|
|
|
|
e := &Echo{
|
|
|
|
Logger: logger,
|
|
|
|
Filesystem: newDefaultFS(),
|
|
|
|
Binder: &DefaultBinder{},
|
|
|
|
JSONSerializer: &DefaultJSONSerializer{},
|
|
|
|
|
|
|
|
routers: make(map[string]Router),
|
|
|
|
routerCreator: func(ec *Echo) Router {
|
|
|
|
return NewRouter(RouterConfig{})
|
2016-12-02 19:28:18 +02:00
|
|
|
},
|
2016-09-25 01:19:38 +02:00
|
|
|
}
|
2021-07-15 22:34:01 +02:00
|
|
|
|
|
|
|
e.router = NewRouter(RouterConfig{})
|
|
|
|
e.HTTPErrorHandler = DefaultHTTPErrorHandler(false)
|
|
|
|
e.contextPool.New = func() interface{} {
|
2016-04-17 00:53:27 +02:00
|
|
|
return e.NewContext(nil, nil)
|
2015-03-27 23:35:15 +02:00
|
|
|
}
|
2021-07-15 22:34:01 +02:00
|
|
|
return e
|
2015-03-27 23:35:15 +02:00
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// NewContext returns a new Context instance.
|
|
|
|
//
|
|
|
|
// Note: both request and response can be left to nil as Echo.ServeHTTP will call c.Reset(req,resp) anyway
|
|
|
|
// these arguments are useful when creating context for tests and cases like that.
|
2016-09-23 07:53:44 +02:00
|
|
|
func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) Context {
|
2021-07-15 22:34:01 +02:00
|
|
|
var c Context
|
|
|
|
if e.NewContextFunc != nil {
|
|
|
|
c = e.NewContextFunc(e, e.contextPathParamAllocSize)
|
|
|
|
} else {
|
|
|
|
c = NewDefaultContext(e, e.contextPathParamAllocSize)
|
2016-04-17 00:53:27 +02:00
|
|
|
}
|
2021-07-15 22:34:01 +02:00
|
|
|
c.SetRequest(r)
|
|
|
|
c.SetResponse(NewResponse(w, e))
|
|
|
|
return c
|
2016-04-17 00:53:27 +02:00
|
|
|
}
|
|
|
|
|
2019-04-29 07:49:18 +02:00
|
|
|
// Router returns the default router.
|
2021-07-15 22:34:01 +02:00
|
|
|
func (e *Echo) Router() Router {
|
2015-05-23 05:26:52 +02:00
|
|
|
return e.router
|
|
|
|
}
|
|
|
|
|
2019-04-29 07:49:18 +02:00
|
|
|
// Routers returns the map of host => router.
|
2021-07-15 22:34:01 +02:00
|
|
|
func (e *Echo) Routers() map[string]Router {
|
2019-04-29 07:49:18 +02:00
|
|
|
return e.routers
|
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// RouterFor returns Router for given host.
|
|
|
|
func (e *Echo) RouterFor(host string) Router {
|
|
|
|
return e.routers[host]
|
|
|
|
}
|
|
|
|
|
|
|
|
// ResetRouterCreator resets callback for creating new router instances.
|
|
|
|
// Note: current (default) router is immediately replaced with router created with creator func and vhost routers are cleared.
|
|
|
|
func (e *Echo) ResetRouterCreator(creator func(e *Echo) Router) {
|
|
|
|
e.routerCreator = creator
|
|
|
|
e.router = creator(e)
|
|
|
|
e.routers = make(map[string]Router)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DefaultHTTPErrorHandler creates new default HTTP error handler implementation. It sends a JSON response
|
|
|
|
// with status code. `exposeError` parameter decides if returned message will contain also error message or not
|
2021-09-14 19:57:47 +02:00
|
|
|
//
|
2021-07-15 22:34:01 +02:00
|
|
|
// Note: DefaultHTTPErrorHandler does not log errors. Use middleware for it if errors need to be logged (separately)
|
|
|
|
// Note: In case errors happens in middleware call-chain that is returning from handler (which did not return an error).
|
2021-09-14 19:57:47 +02:00
|
|
|
// 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.
|
2021-07-15 22:34:01 +02:00
|
|
|
func DefaultHTTPErrorHandler(exposeError bool) HTTPErrorHandler {
|
|
|
|
return func(c Context, err error) {
|
|
|
|
if c.Response().Committed {
|
|
|
|
return
|
2017-08-31 18:18:42 +02:00
|
|
|
}
|
2021-07-15 22:34:01 +02:00
|
|
|
|
|
|
|
he := &HTTPError{
|
2019-08-11 21:48:50 +02:00
|
|
|
Code: http.StatusInternalServerError,
|
2019-08-05 06:35:30 +02:00
|
|
|
Message: http.StatusText(http.StatusInternalServerError),
|
2019-08-02 07:20:33 +02:00
|
|
|
}
|
2021-07-15 22:34:01 +02:00
|
|
|
if errors.As(err, &he) {
|
|
|
|
if he.Internal != nil { // max 2 levels of checks even if internal could have also internal
|
|
|
|
errors.As(he.Internal, &he)
|
|
|
|
}
|
|
|
|
}
|
2019-10-31 04:01:23 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// Issue #1426
|
|
|
|
code := he.Code
|
|
|
|
message := he.Message
|
|
|
|
if m, ok := he.Message.(string); ok {
|
|
|
|
if exposeError {
|
|
|
|
message = Map{"message": m, "error": err.Error()}
|
|
|
|
} else {
|
|
|
|
message = Map{"message": m}
|
|
|
|
}
|
2020-11-05 04:37:15 +02:00
|
|
|
}
|
2016-12-11 08:05:41 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// Send response
|
|
|
|
var cErr error
|
|
|
|
if c.Request().Method == http.MethodHead { // Issue #608
|
|
|
|
cErr = c.NoContent(he.Code)
|
|
|
|
} else {
|
|
|
|
cErr = c.JSON(code, message)
|
|
|
|
}
|
|
|
|
if cErr != nil {
|
|
|
|
c.Echo().Logger.Error(err) // truly rare case. ala client already disconnected
|
|
|
|
}
|
2016-02-09 08:17:20 +02:00
|
|
|
}
|
2015-05-18 07:54:29 +02:00
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// Pre adds middleware to the chain which is run before router tries to find matching route.
|
|
|
|
// Meaning middleware is executed even for 404 (not found) cases.
|
2016-04-02 23:19:39 +02:00
|
|
|
func (e *Echo) Pre(middleware ...MiddlewareFunc) {
|
2016-04-09 23:00:23 +02:00
|
|
|
e.premiddleware = append(e.premiddleware, middleware...)
|
2016-03-16 23:26:34 +02:00
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// Use adds middleware to the chain which is run after router has found matching route and before route/request handler method is executed.
|
2016-04-02 23:19:39 +02:00
|
|
|
func (e *Echo) Use(middleware ...MiddlewareFunc) {
|
2016-02-16 03:12:15 +02:00
|
|
|
e.middleware = append(e.middleware, middleware...)
|
2015-03-27 23:35:15 +02:00
|
|
|
}
|
|
|
|
|
2016-04-19 01:59:58 +02:00
|
|
|
// CONNECT registers a new CONNECT route for a path with matching handler in the
|
2021-07-15 22:34:01 +02:00
|
|
|
// router with optional route-level middleware. Panics on error.
|
|
|
|
func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
|
2018-10-14 17:16:58 +02:00
|
|
|
return e.Add(http.MethodConnect, path, h, m...)
|
2016-04-19 01:59:58 +02:00
|
|
|
}
|
|
|
|
|
2016-04-20 16:32:51 +02:00
|
|
|
// DELETE registers a new DELETE route for a path with matching handler in the router
|
2021-07-15 22:34:01 +02:00
|
|
|
// with optional route-level middleware. Panics on error.
|
|
|
|
func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
|
2018-10-14 17:16:58 +02:00
|
|
|
return e.Add(http.MethodDelete, path, h, m...)
|
2016-04-20 16:32:51 +02:00
|
|
|
}
|
|
|
|
|
2016-04-19 01:59:58 +02:00
|
|
|
// GET registers a new GET route for a path with matching handler in the router
|
2021-07-15 22:34:01 +02:00
|
|
|
// with optional route-level middleware. Panics on error.
|
|
|
|
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
|
2018-10-14 17:16:58 +02:00
|
|
|
return e.Add(http.MethodGet, path, h, m...)
|
2016-04-19 01:59:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// HEAD registers a new HEAD route for a path with matching handler in the
|
2021-07-15 22:34:01 +02:00
|
|
|
// router with optional route-level middleware. Panics on error.
|
|
|
|
func (e *Echo) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
|
2018-10-14 17:16:58 +02:00
|
|
|
return e.Add(http.MethodHead, path, h, m...)
|
2016-04-19 01:59:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// OPTIONS registers a new OPTIONS route for a path with matching handler in the
|
2021-07-15 22:34:01 +02:00
|
|
|
// router with optional route-level middleware. Panics on error.
|
|
|
|
func (e *Echo) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
|
2018-10-14 17:16:58 +02:00
|
|
|
return e.Add(http.MethodOptions, path, h, m...)
|
2016-04-19 01:59:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// PATCH registers a new PATCH route for a path with matching handler in the
|
2021-07-15 22:34:01 +02:00
|
|
|
// router with optional route-level middleware. Panics on error.
|
|
|
|
func (e *Echo) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
|
2018-10-14 17:16:58 +02:00
|
|
|
return e.Add(http.MethodPatch, path, h, m...)
|
2016-04-19 01:59:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// POST registers a new POST route for a path with matching handler in the
|
2021-07-15 22:34:01 +02:00
|
|
|
// router with optional route-level middleware. Panics on error.
|
|
|
|
func (e *Echo) POST(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
|
2018-10-14 17:16:58 +02:00
|
|
|
return e.Add(http.MethodPost, path, h, m...)
|
2016-04-19 01:59:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// PUT registers a new PUT route for a path with matching handler in the
|
2021-07-15 22:34:01 +02:00
|
|
|
// router with optional route-level middleware. Panics on error.
|
|
|
|
func (e *Echo) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
|
2018-10-14 17:16:58 +02:00
|
|
|
return e.Add(http.MethodPut, path, h, m...)
|
2016-04-19 01:59:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// TRACE registers a new TRACE route for a path with matching handler in the
|
2021-07-15 22:34:01 +02:00
|
|
|
// router with optional route-level middleware. Panics on error.
|
|
|
|
func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) RouteInfo {
|
2018-10-14 17:16:58 +02:00
|
|
|
return e.Add(http.MethodTrace, path, h, m...)
|
2016-04-19 01:59:58 +02:00
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// Any registers a new route for all supported HTTP methods and path with matching handler
|
|
|
|
// in the router with optional route-level middleware. Panics on error.
|
|
|
|
func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) Routes {
|
|
|
|
errs := make([]error, 0)
|
|
|
|
ris := make(Routes, 0)
|
|
|
|
for _, m := range methods {
|
|
|
|
ri, err := e.AddRoute(Route{
|
|
|
|
Method: m,
|
|
|
|
Path: path,
|
|
|
|
Handler: handler,
|
|
|
|
Middlewares: middleware,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
errs = append(errs, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
ris = append(ris, ri)
|
2015-08-26 05:36:15 +02:00
|
|
|
}
|
2021-07-15 22:34:01 +02:00
|
|
|
if len(errs) > 0 {
|
|
|
|
panic(errs) // this is how `v4` handles errors. `v5` has methods to have panic-free usage
|
|
|
|
}
|
|
|
|
return ris
|
2015-08-26 05:36:15 +02:00
|
|
|
}
|
|
|
|
|
2016-03-20 00:47:20 +02:00
|
|
|
// Match registers a new route for multiple HTTP methods and path with matching
|
2021-07-15 22:34:01 +02:00
|
|
|
// handler in the router with optional route-level middleware. Panics on error.
|
|
|
|
func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) Routes {
|
|
|
|
errs := make([]error, 0)
|
|
|
|
ris := make(Routes, 0)
|
|
|
|
for _, m := range methods {
|
|
|
|
ri, err := e.AddRoute(Route{
|
|
|
|
Method: m,
|
|
|
|
Path: path,
|
|
|
|
Handler: handler,
|
|
|
|
Middlewares: middleware,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
errs = append(errs, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
ris = append(ris, ri)
|
|
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
|
|
panic(errs) // this is how `v4` handles errors. `v5` has methods to have panic-free usage
|
|
|
|
}
|
|
|
|
return ris
|
|
|
|
}
|
|
|
|
|
|
|
|
// Static registers a new route with path prefix to serve static files from the provided root directory.
|
|
|
|
func (e *Echo) Static(pathPrefix, fsRoot string) RouteInfo {
|
|
|
|
subFs := MustSubFS(e.Filesystem, fsRoot)
|
|
|
|
return e.Add(
|
|
|
|
http.MethodGet,
|
|
|
|
pathPrefix+"*",
|
|
|
|
StaticDirectoryHandler(subFs, false),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// StaticFS registers a new route with path prefix to serve static files from the provided file system.
|
|
|
|
//
|
|
|
|
// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
|
|
|
|
// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
|
|
|
|
// including `assets/images` as their prefix.
|
|
|
|
func (e *Echo) StaticFS(pathPrefix string, filesystem fs.FS) RouteInfo {
|
|
|
|
return e.Add(
|
|
|
|
http.MethodGet,
|
|
|
|
pathPrefix+"*",
|
|
|
|
StaticDirectoryHandler(filesystem, false),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// StaticDirectoryHandler creates handler function to serve files from provided file system
|
|
|
|
// When disablePathUnescaping is set then file name from path is not unescaped and is served as is.
|
|
|
|
func StaticDirectoryHandler(fileSystem fs.FS, disablePathUnescaping bool) HandlerFunc {
|
|
|
|
return func(c Context) error {
|
|
|
|
p := c.PathParam("*")
|
|
|
|
if !disablePathUnescaping { // when router is already unescaping we do not want to do is twice
|
|
|
|
tmpPath, err := url.PathUnescape(p)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to unescape path variable: %w", err)
|
|
|
|
}
|
|
|
|
p = tmpPath
|
|
|
|
}
|
|
|
|
|
|
|
|
// fs.FS.Open() already assumes that file names are relative to FS root path and considers name with prefix `/` as invalid
|
|
|
|
name := filepath.ToSlash(filepath.Clean(strings.TrimPrefix(p, "/")))
|
|
|
|
fi, err := fs.Stat(fileSystem, name)
|
|
|
|
if err != nil {
|
|
|
|
return ErrNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the request is for a directory and does not end with "/"
|
|
|
|
p = c.Request().URL.Path // path must not be empty.
|
|
|
|
if fi.IsDir() && len(p) > 0 && p[len(p)-1] != '/' {
|
|
|
|
// Redirect to ends with "/"
|
|
|
|
return c.Redirect(http.StatusMovedPermanently, p+"/")
|
|
|
|
}
|
|
|
|
return fsFile(c, name, fileSystem)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// FileFS registers a new route with path to serve file from the provided file system.
|
|
|
|
func (e *Echo) FileFS(path, file string, filesystem fs.FS, m ...MiddlewareFunc) RouteInfo {
|
|
|
|
return e.GET(path, StaticFileHandler(file, filesystem), m...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// StaticFileHandler creates handler function to serve file from provided file system
|
|
|
|
func StaticFileHandler(file string, filesystem fs.FS) HandlerFunc {
|
|
|
|
return func(c Context) error {
|
|
|
|
return fsFile(c, file, filesystem)
|
2015-08-26 05:36:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// File registers a new route with path to serve a static file with optional route-level middleware. Panics on error.
|
|
|
|
func (e *Echo) File(path, file string, middleware ...MiddlewareFunc) RouteInfo {
|
|
|
|
handler := func(c Context) error {
|
2016-03-12 15:14:15 +02:00
|
|
|
return c.File(file)
|
2021-07-15 22:34:01 +02:00
|
|
|
}
|
|
|
|
return e.Add(http.MethodGet, path, handler, middleware...)
|
2016-03-12 15:14:15 +02:00
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// AddRoute registers a new Route with default host Router
|
|
|
|
func (e *Echo) AddRoute(route Routable) (RouteInfo, error) {
|
|
|
|
return e.add("", route)
|
2019-04-29 22:21:11 +02:00
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func (e *Echo) add(host string, route Routable) (RouteInfo, error) {
|
2019-04-29 07:22:35 +02:00
|
|
|
router := e.findRouter(host)
|
2021-07-15 22:34:01 +02:00
|
|
|
ri, err := router.Add(route)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
paramsCount := len(ri.Params())
|
|
|
|
if paramsCount > e.contextPathParamAllocSize {
|
|
|
|
e.contextPathParamAllocSize = paramsCount
|
2015-06-01 09:07:53 +02:00
|
|
|
}
|
2021-07-15 22:34:01 +02:00
|
|
|
return ri, nil
|
2015-04-22 07:12:41 +02:00
|
|
|
}
|
|
|
|
|
2019-04-29 07:22:35 +02:00
|
|
|
// Add registers a new route for an HTTP method and path with matching handler
|
|
|
|
// in the router with optional route-level middleware.
|
2021-07-15 22:34:01 +02:00
|
|
|
func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) RouteInfo {
|
|
|
|
ri, err := e.add(
|
|
|
|
"",
|
|
|
|
Route{
|
|
|
|
Method: method,
|
|
|
|
Path: path,
|
|
|
|
Handler: handler,
|
|
|
|
Middlewares: middleware,
|
|
|
|
Name: "",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
panic(err) // this is how `v4` handles errors. `v5` has methods to have panic-free usage
|
|
|
|
}
|
|
|
|
return ri
|
2019-04-29 07:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2021-07-15 22:34:01 +02:00
|
|
|
e.routers[name] = e.routerCreator(e)
|
2019-04-29 07:22:35 +02:00
|
|
|
g = &Group{host: name, echo: e}
|
|
|
|
g.Use(m...)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-03-20 00:47:20 +02:00
|
|
|
// Group creates a new router group with prefix and optional group-level middleware.
|
2016-04-02 23:19:39 +02:00
|
|
|
func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
|
2016-02-15 18:11:29 +02:00
|
|
|
g = &Group{prefix: prefix, echo: e}
|
2016-02-16 03:12:15 +02:00
|
|
|
g.Use(m...)
|
2015-11-24 06:33:13 +02:00
|
|
|
return
|
2015-05-27 23:07:52 +02:00
|
|
|
}
|
|
|
|
|
2016-05-04 02:23:31 +02:00
|
|
|
// AcquireContext returns an empty `Context` instance from the pool.
|
2016-11-21 19:43:14 +02:00
|
|
|
// You must return the context by calling `ReleaseContext()`.
|
2016-05-04 02:23:31 +02:00
|
|
|
func (e *Echo) AcquireContext() Context {
|
2021-07-15 22:34:01 +02:00
|
|
|
return e.contextPool.Get().(Context)
|
2016-05-04 02:23:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// ReleaseContext returns the `Context` instance back to the pool.
|
|
|
|
// You must call it after `AcquireContext()`.
|
|
|
|
func (e *Echo) ReleaseContext(c Context) {
|
2021-07-15 22:34:01 +02:00
|
|
|
e.contextPool.Put(c)
|
2016-03-18 06:49:06 +02:00
|
|
|
}
|
|
|
|
|
2016-09-23 07:53:44 +02:00
|
|
|
// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.
|
|
|
|
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
2021-07-15 22:34:01 +02:00
|
|
|
var c ServableContext
|
|
|
|
if e.NewContextFunc != nil {
|
|
|
|
// NOTE: we are not casting always context to RoutableContext because casting to interface vs pointer to struct is
|
|
|
|
// "significantly" slower. Echo Context interface has way to many methods so these checks take time.
|
|
|
|
// These are benchmarks with 1.16:
|
|
|
|
// * interface extending another interface = +24% slower (3233 ns/op vs 2605 ns/op)
|
|
|
|
// * interface (not extending any, just methods)= +14% slower
|
|
|
|
//
|
|
|
|
// Quote from https://stackoverflow.com/a/31584377
|
|
|
|
// "it's even worse with interface-to-interface assertion, because you also need to ensure that the type implements the interface."
|
|
|
|
//
|
|
|
|
// So most of the time we do not need custom context type and simple IF + cast to pointer to struct is fast enough.
|
|
|
|
c = e.contextPool.Get().(ServableContext)
|
|
|
|
} else {
|
|
|
|
c = e.contextPool.Get().(*DefaultContext)
|
|
|
|
}
|
2016-09-23 07:53:44 +02:00
|
|
|
c.Reset(r, w)
|
2022-01-24 11:33:13 +02:00
|
|
|
var h func(Context) error
|
2018-03-15 08:53:32 +02:00
|
|
|
|
|
|
|
if e.premiddleware == nil {
|
2021-07-15 22:34:01 +02:00
|
|
|
h = applyMiddleware(e.findRouter(r.Host).Route(c), e.middleware...)
|
2018-03-15 08:53:32 +02:00
|
|
|
} else {
|
2021-07-15 22:34:01 +02:00
|
|
|
h = func(cc Context) error {
|
|
|
|
// NOTE: router will be executed after pre middlewares have been run. We assume here that context we receive after pre middlewares
|
|
|
|
// is the same we began with. If not - this is use-case we do not support and is probably abuse from developer.
|
|
|
|
h1 := applyMiddleware(e.findRouter(r.Host).Route(c), e.middleware...)
|
|
|
|
return h1(cc)
|
2018-03-15 08:53:32 +02:00
|
|
|
}
|
2019-03-05 18:24:59 +02:00
|
|
|
h = applyMiddleware(h, e.premiddleware...)
|
2016-04-09 23:00:23 +02:00
|
|
|
}
|
|
|
|
|
2016-01-29 09:46:11 +02:00
|
|
|
// Execute chain
|
2016-04-09 23:00:23 +02:00
|
|
|
if err := h(c); err != nil {
|
2021-07-15 22:34:01 +02:00
|
|
|
e.HTTPErrorHandler(c, err)
|
2015-11-15 23:32:21 +02:00
|
|
|
}
|
2018-03-14 23:03:59 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
e.contextPool.Put(c)
|
2015-11-15 23:32:21 +02:00
|
|
|
}
|
2015-06-27 23:34:22 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// Start stars HTTP server on given address with Echo as a handler serving requests. The server can be shutdown by
|
|
|
|
// sending os.Interrupt signal with `ctrl+c`.
|
|
|
|
//
|
|
|
|
// Note: this method is created for use in examples/demos and is deliberately simple without providing configuration
|
|
|
|
// options.
|
|
|
|
//
|
|
|
|
// In need of customization use:
|
|
|
|
// sc := echo.StartConfig{Address: ":8080"}
|
|
|
|
// if err := sc.Start(e); err != http.ErrServerClosed {
|
|
|
|
// log.Fatal(err)
|
|
|
|
// }
|
|
|
|
// // or standard library `http.Server`
|
|
|
|
// s := http.Server{Addr: ":8080", Handler: e}
|
|
|
|
// if err := s.ListenAndServe(); err != http.ErrServerClosed {
|
|
|
|
// log.Fatal(err)
|
|
|
|
// }
|
2016-11-05 03:51:17 +02:00
|
|
|
func (e *Echo) Start(address string) error {
|
2021-07-15 22:34:01 +02:00
|
|
|
sc := StartConfig{Address: address}
|
|
|
|
ctx, cancel := signal.NotifyContext(stdContext.Background(), os.Interrupt) // start shutdown process on ctrl+c
|
|
|
|
defer cancel()
|
|
|
|
sc.GracefulContext = ctx
|
2019-02-15 19:51:54 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
return sc.Start(e)
|
2020-04-29 16:13:30 +02:00
|
|
|
}
|
|
|
|
|
2016-09-23 14:31:48 +02:00
|
|
|
// 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 {
|
2016-04-02 23:19:39 +02:00
|
|
|
return func(next HandlerFunc) HandlerFunc {
|
2016-09-23 14:31:48 +02:00
|
|
|
return func(c Context) (err error) {
|
|
|
|
m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2016-10-24 17:21:36 +02:00
|
|
|
c.SetRequest(r)
|
2019-08-07 20:10:18 +02:00
|
|
|
c.SetResponse(NewResponse(w, c.Echo()))
|
2016-09-23 14:31:48 +02:00
|
|
|
err = next(c)
|
|
|
|
})).ServeHTTP(c.Response(), c.Request())
|
|
|
|
return
|
2016-04-02 23:19:39 +02:00
|
|
|
}
|
2016-03-07 17:55:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func (e *Echo) findRouter(host string) Router {
|
2019-04-29 07:22:35 +02:00
|
|
|
if len(e.routers) > 0 {
|
|
|
|
if r, ok := e.routers[host]; ok {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return e.router
|
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func applyMiddleware(h HandlerFunc, middleware ...MiddlewareFunc) HandlerFunc {
|
|
|
|
for i := len(middleware) - 1; i >= 0; i-- {
|
|
|
|
h = middleware[i](h)
|
2016-02-17 21:58:03 +02:00
|
|
|
}
|
2021-07-15 22:34:01 +02:00
|
|
|
return h
|
2016-02-09 08:17:20 +02:00
|
|
|
}
|
2017-01-14 04:40:27 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// defaultFS emulates os.Open behaviour with filesystem opened by `os.DirFs`. Difference between `os.Open` and `fs.Open`
|
|
|
|
// is that FS does not allow to open path that start with `..` or `/` etc. For example previously you could have `../images`
|
|
|
|
// in your application but `fs := os.DirFS("./")` would not allow you to use `fs.Open("../images")` and this would break
|
|
|
|
// all old applications that rely on being able to traverse up from current executable run path.
|
|
|
|
// NB: private because you really should use fs.FS implementation instances
|
|
|
|
type defaultFS struct {
|
|
|
|
prefix string
|
|
|
|
fs fs.FS
|
2017-01-14 04:40:27 +02:00
|
|
|
}
|
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func newDefaultFS() *defaultFS {
|
|
|
|
dir, _ := os.Getwd()
|
|
|
|
return &defaultFS{
|
|
|
|
prefix: dir,
|
|
|
|
fs: os.DirFS(dir),
|
2017-01-14 04:40:27 +02:00
|
|
|
}
|
|
|
|
}
|
2017-01-18 22:17:44 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
func (fs defaultFS) Open(name string) (fs.File, error) {
|
|
|
|
return fs.fs.Open(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func subFS(currentFs fs.FS, root string) (fs.FS, error) {
|
|
|
|
root = filepath.ToSlash(filepath.Clean(root)) // note: fs.FS operates only with slashes. `ToSlash` is necessary for Windows
|
|
|
|
if dFS, ok := currentFs.(*defaultFS); ok {
|
|
|
|
// we need to make exception for `defaultFS` instances as it interprets root prefix differently from fs.FS to
|
|
|
|
// allow cases when root is given as `../somepath` which is not valid for fs.FS
|
|
|
|
root = filepath.Join(dFS.prefix, root)
|
|
|
|
return &defaultFS{
|
|
|
|
prefix: root,
|
|
|
|
fs: os.DirFS(root),
|
|
|
|
}, nil
|
2017-01-18 22:17:44 +02:00
|
|
|
}
|
2021-07-15 22:34:01 +02:00
|
|
|
return fs.Sub(currentFs, root)
|
2017-01-18 22:17:44 +02:00
|
|
|
}
|
2019-03-05 18:24:59 +02:00
|
|
|
|
2021-07-15 22:34:01 +02:00
|
|
|
// MustSubFS creates sub FS from current filesystem or panic on failure.
|
|
|
|
// Panic happens when `fsRoot` contains invalid path according to `fs.ValidPath` rules.
|
|
|
|
//
|
|
|
|
// MustSubFS is helpful when dealing with `embed.FS` because for example `//go:embed assets/images` embeds files with
|
|
|
|
// paths including `assets/images` as their prefix. In that case use `fs := echo.MustSubFS(fs, "rootDirectory") to
|
|
|
|
// create sub fs which uses necessary prefix for directory path.
|
|
|
|
func MustSubFS(currentFs fs.FS, fsRoot string) fs.FS {
|
|
|
|
subFs, err := subFS(currentFs, fsRoot)
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Errorf("can not create sub FS, invalid root given, err: %w", err))
|
2019-03-05 18:24:59 +02:00
|
|
|
}
|
2021-07-15 22:34:01 +02:00
|
|
|
return subFs
|
2019-03-05 18:24:59 +02:00
|
|
|
}
|