717 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			717 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| 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"
 | |
| 	"crypto/tls"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	stdLog "log"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"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
 | |
| 		DisableHTTP2     bool
 | |
| 		Debug            bool
 | |
| 		HideBanner       bool
 | |
| 		HTTPErrorHandler HTTPErrorHandler
 | |
| 		Binder           Binder
 | |
| 		Validator        Validator
 | |
| 		Renderer         Renderer
 | |
| 		AutoTLSManager   autocert.Manager
 | |
| 		// Mutex            sync.RWMutex
 | |
| 		Logger Logger
 | |
| 	}
 | |
| 
 | |
| 	// Route contains a handler and information for matching against requests.
 | |
| 	Route struct {
 | |
| 		Method  string `json:"method"`
 | |
| 		Path    string `json:"path"`
 | |
| 		Handler string `json:"handler"`
 | |
| 	}
 | |
| 
 | |
| 	// HTTPError represents an error that occurred while handling a request.
 | |
| 	HTTPError struct {
 | |
| 		Code    int
 | |
| 		Message interface{}
 | |
| 	}
 | |
| 
 | |
| 	// MiddlewareFunc defines a function to process middleware.
 | |
| 	MiddlewareFunc func(HandlerFunc) HandlerFunc
 | |
| 
 | |
| 	// HandlerFunc defines a function to server HTTP requests.
 | |
| 	HandlerFunc func(Context) error
 | |
| 
 | |
| 	// HTTPErrorHandler is a centralized HTTP error handler.
 | |
| 	HTTPErrorHandler func(error, Context)
 | |
| 
 | |
| 	// Validator is the interface that wraps the Validate function.
 | |
| 	Validator interface {
 | |
| 		Validate(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)
 | |
| 	}
 | |
| )
 | |
| 
 | |
| // HTTP methods
 | |
| const (
 | |
| 	CONNECT = "CONNECT"
 | |
| 	DELETE  = "DELETE"
 | |
| 	GET     = "GET"
 | |
| 	HEAD    = "HEAD"
 | |
| 	OPTIONS = "OPTIONS"
 | |
| 	PATCH   = "PATCH"
 | |
| 	POST    = "POST"
 | |
| 	PUT     = "PUT"
 | |
| 	TRACE   = "TRACE"
 | |
| )
 | |
| 
 | |
| // MIME types
 | |
| const (
 | |
| 	MIMEApplicationJSON                  = "application/json"
 | |
| 	MIMEApplicationJSONCharsetUTF8       = MIMEApplicationJSON + "; " + charsetUTF8
 | |
| 	MIMEApplicationJavaScript            = "application/javascript"
 | |
| 	MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8
 | |
| 	MIMEApplicationXML                   = "application/xml"
 | |
| 	MIMEApplicationXMLCharsetUTF8        = MIMEApplicationXML + "; " + charsetUTF8
 | |
| 	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"
 | |
| 	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.2.1"
 | |
| 	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,
 | |
| 		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.OFF)
 | |
| 	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
 | |
| 	} else if e.Debug {
 | |
| 		msg = err.Error()
 | |
| 	} else {
 | |
| 		msg = http.StatusText(code)
 | |
| 	}
 | |
| 	if _, ok := msg.(string); ok {
 | |
| 		msg = Map{"message": msg}
 | |
| 	}
 | |
| 
 | |
| 	if !c.Response().Committed {
 | |
| 		if c.Request().Method == HEAD { // Issue #608
 | |
| 			if err := c.NoContent(code); err != nil {
 | |
| 				goto ERROR
 | |
| 			}
 | |
| 		} else {
 | |
| 			if err := c.JSON(code, msg); err != nil {
 | |
| 				goto ERROR
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| ERROR:
 | |
| 	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) {
 | |
| 	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) {
 | |
| 	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) {
 | |
| 	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) {
 | |
| 	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) {
 | |
| 	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) {
 | |
| 	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) {
 | |
| 	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) {
 | |
| 	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) {
 | |
| 	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) {
 | |
| 	for _, m := range methods {
 | |
| 		e.add(m, path, handler, middleware...)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Match registers a new route for multiple HTTP methods and path with matching
 | |
| // handler in the router with optional route-level middleware.
 | |
| func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
 | |
| 	for _, m := range methods {
 | |
| 		e.add(m, path, handler, middleware...)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Static registers a new route with path prefix to serve static files from the
 | |
| // provided root directory.
 | |
| func (e *Echo) Static(prefix, root string) {
 | |
| 	if root == "" {
 | |
| 		root = "." // For security we want to restrict to CWD.
 | |
| 	}
 | |
| 	static(e, prefix, root)
 | |
| }
 | |
| 
 | |
| func static(i i, prefix, root string) {
 | |
| 	h := func(c Context) error {
 | |
| 		p, err := 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 == "/" {
 | |
| 		i.GET(prefix+"*", h)
 | |
| 	} else {
 | |
| 		i.GET(prefix+"/*", h)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // File registers a new route with path to serve a static file.
 | |
| func (e *Echo) File(path, file string) {
 | |
| 	e.GET(path, func(c Context) error {
 | |
| 		return c.File(file)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func (e *Echo) add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) {
 | |
| 	name := handlerName(handler)
 | |
| 	e.router.Add(method, path, func(c Context) error {
 | |
| 		h := handler
 | |
| 		// Chain middleware
 | |
| 		for i := len(middleware) - 1; i >= 0; i-- {
 | |
| 			h = middleware[i](h)
 | |
| 		}
 | |
| 		return h(c)
 | |
| 	})
 | |
| 	r := &Route{
 | |
| 		Method:  method,
 | |
| 		Path:    path,
 | |
| 		Handler: name,
 | |
| 	}
 | |
| 	e.router.routes[method+path] = r
 | |
| }
 | |
| 
 | |
| // Group creates a new router group with prefix and optional group-level middleware.
 | |
| func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
 | |
| 	g = &Group{prefix: prefix, echo: e}
 | |
| 	g.Use(m...)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // URI generates a URI from handler.
 | |
| func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string {
 | |
| 	uri := new(bytes.Buffer)
 | |
| 	ln := len(params)
 | |
| 	n := 0
 | |
| 	name := handlerName(handler)
 | |
| 	for _, r := range e.router.routes {
 | |
| 		if r.Handler == name {
 | |
| 			for i, l := 0, len(r.Path); i < l; i++ {
 | |
| 				if r.Path[i] == ':' && n < ln {
 | |
| 					for ; i < l && r.Path[i] != '/'; i++ {
 | |
| 					}
 | |
| 					uri.WriteString(fmt.Sprintf("%v", params[n]))
 | |
| 					n++
 | |
| 				}
 | |
| 				if i < l {
 | |
| 					uri.WriteByte(r.Path[i])
 | |
| 				}
 | |
| 			}
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	return uri.String()
 | |
| }
 | |
| 
 | |
| // URL is an alias for `URI` function.
 | |
| func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
 | |
| 	return e.URI(h, params...)
 | |
| }
 | |
| 
 | |
| // Routes returns the registered routes.
 | |
| func (e *Echo) Routes() []*Route {
 | |
| 	routes := []*Route{}
 | |
| 	for _, v := range e.router.routes {
 | |
| 		routes = append(routes, v)
 | |
| 	}
 | |
| 	return routes
 | |
| }
 | |
| 
 | |
| // AcquireContext returns an empty `Context` instance from the pool.
 | |
| // You must 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 lock
 | |
| 	// e.Mutex.RLock()
 | |
| 	// defer e.Mutex.RUnlock()
 | |
| 
 | |
| 	// Acquire context
 | |
| 	c := e.pool.Get().(*context)
 | |
| 	defer e.pool.Put(c)
 | |
| 	c.Reset(r, w)
 | |
| 
 | |
| 	// Middleware
 | |
| 	h := func(c Context) error {
 | |
| 		method := r.Method
 | |
| 		path := r.URL.RawPath
 | |
| 		if path == "" {
 | |
| 			path = r.URL.Path
 | |
| 		}
 | |
| 		e.router.Find(method, path, c)
 | |
| 		h := c.Handler()
 | |
| 		for i := len(e.middleware) - 1; i >= 0; i-- {
 | |
| 			h = e.middleware[i](h)
 | |
| 		}
 | |
| 		return h(c)
 | |
| 	}
 | |
| 
 | |
| 	// Premiddleware
 | |
| 	for i := len(e.premiddleware) - 1; i >= 0; i-- {
 | |
| 		h = e.premiddleware[i](h)
 | |
| 	}
 | |
| 
 | |
| 	// Execute chain
 | |
| 	if err := h(c); err != nil {
 | |
| 		e.HTTPErrorHandler(err, c)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // 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 {
 | |
| 	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.HideBanner {
 | |
| 			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.HideBanner {
 | |
| 		e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
 | |
| 	}
 | |
| 	return s.Serve(e.TLSListener)
 | |
| }
 | |
| 
 | |
| // 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 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()
 | |
| }
 | |
| 
 | |
| // 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
 | |
| }
 |