Update vendor labstack/echo

This commit is contained in:
Wim 2018-02-21 00:48:10 +01:00
parent d1227b5fc9
commit 55ab0c12f1
22 changed files with 366 additions and 216 deletions

View File

@ -274,13 +274,6 @@ func (c *context) Param(name string) string {
if n == name { if n == name {
return c.pvalues[i] return c.pvalues[i]
} }
// Param name with aliases
for _, p := range strings.Split(n, ",") {
if p == name {
return c.pvalues[i]
}
}
} }
} }
return "" return ""

View File

@ -76,6 +76,7 @@ type (
DisableHTTP2 bool DisableHTTP2 bool
Debug bool Debug bool
HideBanner bool HideBanner bool
HidePort bool
HTTPErrorHandler HTTPErrorHandler HTTPErrorHandler HTTPErrorHandler
Binder Binder Binder Binder
Validator Validator Validator Validator
@ -213,7 +214,7 @@ const (
) )
const ( const (
version = "3.2.5" version = "3.2.6"
website = "https://echo.labstack.com" website = "https://echo.labstack.com"
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo // http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
banner = ` banner = `
@ -414,9 +415,9 @@ func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
// Any registers a new route for all HTTP methods and path with matching handler // Any registers a new route for all HTTP methods and path with matching handler
// in the router with optional route-level middleware. // in the router with optional route-level middleware.
func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route { func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
routes := make([]*Route, 0) routes := make([]*Route, len(methods))
for _, m := range methods { for i, m := range methods {
routes = append(routes, e.Add(m, path, handler, middleware...)) routes[i] = e.Add(m, path, handler, middleware...)
} }
return routes return routes
} }
@ -424,9 +425,9 @@ func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFun
// Match registers a new route for multiple HTTP methods and path with matching // Match registers a new route for multiple HTTP methods and path with matching
// handler in the router with optional route-level middleware. // handler in the router with optional route-level middleware.
func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route { func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
routes := make([]*Route, 0) routes := make([]*Route, len(methods))
for _, m := range methods { for i, m := range methods {
routes = append(routes, e.Add(m, path, handler, middleware...)) routes[i] = e.Add(m, path, handler, middleware...)
} }
return routes return routes
} }
@ -644,7 +645,7 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
return err return err
} }
} }
if !e.HideBanner { if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr())) e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
} }
return s.Serve(e.Listener) return s.Serve(e.Listener)
@ -656,7 +657,7 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
} }
e.TLSListener = tls.NewListener(l, s.TLSConfig) e.TLSListener = tls.NewListener(l, s.TLSConfig)
} }
if !e.HideBanner { if !e.HidePort {
e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr())) e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
} }
return s.Serve(e.TLSListener) return s.Serve(e.TLSListener)

View File

@ -20,9 +20,11 @@ func (g *Group) Use(middleware ...MiddlewareFunc) {
g.middleware = append(g.middleware, middleware...) g.middleware = append(g.middleware, middleware...)
// Allow all requests to reach the group as they might get dropped if router // Allow all requests to reach the group as they might get dropped if router
// doesn't find a match, making none of the group middleware process. // doesn't find a match, making none of the group middleware process.
g.echo.Any(path.Clean(g.prefix+"/*"), func(c Context) error { for _, p := range []string{"", "/*"} {
g.echo.Any(path.Clean(g.prefix+p), func(c Context) error {
return NotFoundHandler(c) return NotFoundHandler(c)
}, g.middleware...) }, g.middleware...)
}
} }
// CONNECT implements `Echo#CONNECT()` for sub-routes within the Group. // CONNECT implements `Echo#CONNECT()` for sub-routes within the Group.
@ -71,17 +73,21 @@ func (g *Group) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
} }
// Any implements `Echo#Any()` for sub-routes within the Group. // Any implements `Echo#Any()` for sub-routes within the Group.
func (g *Group) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) { func (g *Group) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
for _, m := range methods { routes := make([]*Route, len(methods))
g.Add(m, path, handler, middleware...) for i, m := range methods {
routes[i] = g.Add(m, path, handler, middleware...)
} }
return routes
} }
// Match implements `Echo#Match()` for sub-routes within the Group. // Match implements `Echo#Match()` for sub-routes within the Group.
func (g *Group) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) { func (g *Group) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
for _, m := range methods { routes := make([]*Route, len(methods))
g.Add(m, path, handler, middleware...) for i, m := range methods {
routes[i] = g.Add(m, path, handler, middleware...)
} }
return routes
} }
// Group creates a new sub-group with prefix and optional sub-group-level middleware. // Group creates a new sub-group with prefix and optional sub-group-level middleware.

View File

@ -88,6 +88,7 @@ func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
} else if valid { } else if valid {
return next(c) return next(c)
} }
break
} }
} }
} }

View File

@ -33,7 +33,7 @@ type (
) )
var ( var (
// DefaultBodyDumpConfig is the default Gzip middleware config. // DefaultBodyDumpConfig is the default BodyDump middleware config.
DefaultBodyDumpConfig = BodyDumpConfig{ DefaultBodyDumpConfig = BodyDumpConfig{
Skipper: DefaultSkipper, Skipper: DefaultSkipper,
} }

View File

@ -17,7 +17,7 @@ type (
// Maximum allowed size for a request body, it can be specified // Maximum allowed size for a request body, it can be specified
// as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P. // as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P.
Limit string `json:"limit"` Limit string `yaml:"limit"`
limit int64 limit int64
} }

View File

@ -20,7 +20,7 @@ type (
// Gzip compression level. // Gzip compression level.
// Optional. Default value -1. // Optional. Default value -1.
Level int `json:"level"` Level int `yaml:"level"`
} }
gzipResponseWriter struct { gzipResponseWriter struct {

View File

@ -16,34 +16,34 @@ type (
// AllowOrigin defines a list of origins that may access the resource. // AllowOrigin defines a list of origins that may access the resource.
// Optional. Default value []string{"*"}. // Optional. Default value []string{"*"}.
AllowOrigins []string `json:"allow_origins"` AllowOrigins []string `yaml:"allow_origins"`
// AllowMethods defines a list methods allowed when accessing the resource. // AllowMethods defines a list methods allowed when accessing the resource.
// This is used in response to a preflight request. // This is used in response to a preflight request.
// Optional. Default value DefaultCORSConfig.AllowMethods. // Optional. Default value DefaultCORSConfig.AllowMethods.
AllowMethods []string `json:"allow_methods"` AllowMethods []string `yaml:"allow_methods"`
// AllowHeaders defines a list of request headers that can be used when // AllowHeaders defines a list of request headers that can be used when
// making the actual request. This in response to a preflight request. // making the actual request. This in response to a preflight request.
// Optional. Default value []string{}. // Optional. Default value []string{}.
AllowHeaders []string `json:"allow_headers"` AllowHeaders []string `yaml:"allow_headers"`
// AllowCredentials indicates whether or not the response to the request // AllowCredentials indicates whether or not the response to the request
// can be exposed when the credentials flag is true. When used as part of // can be exposed when the credentials flag is true. When used as part of
// a response to a preflight request, this indicates whether or not the // a response to a preflight request, this indicates whether or not the
// actual request can be made using credentials. // actual request can be made using credentials.
// Optional. Default value false. // Optional. Default value false.
AllowCredentials bool `json:"allow_credentials"` AllowCredentials bool `yaml:"allow_credentials"`
// ExposeHeaders defines a whitelist headers that clients are allowed to // ExposeHeaders defines a whitelist headers that clients are allowed to
// access. // access.
// Optional. Default value []string{}. // Optional. Default value []string{}.
ExposeHeaders []string `json:"expose_headers"` ExposeHeaders []string `yaml:"expose_headers"`
// MaxAge indicates how long (in seconds) the results of a preflight request // MaxAge indicates how long (in seconds) the results of a preflight request
// can be cached. // can be cached.
// Optional. Default value 0. // Optional. Default value 0.
MaxAge int `json:"max_age"` MaxAge int `yaml:"max_age"`
} }
) )

View File

@ -18,7 +18,7 @@ type (
Skipper Skipper Skipper Skipper
// TokenLength is the length of the generated token. // TokenLength is the length of the generated token.
TokenLength uint8 `json:"token_length"` TokenLength uint8 `yaml:"token_length"`
// Optional. Default value 32. // Optional. Default value 32.
// TokenLookup is a string in the form of "<source>:<key>" that is used // TokenLookup is a string in the form of "<source>:<key>" that is used
@ -28,35 +28,35 @@ type (
// - "header:<name>" // - "header:<name>"
// - "form:<name>" // - "form:<name>"
// - "query:<name>" // - "query:<name>"
TokenLookup string `json:"token_lookup"` TokenLookup string `yaml:"token_lookup"`
// Context key to store generated CSRF token into context. // Context key to store generated CSRF token into context.
// Optional. Default value "csrf". // Optional. Default value "csrf".
ContextKey string `json:"context_key"` ContextKey string `yaml:"context_key"`
// Name of the CSRF cookie. This cookie will store CSRF token. // Name of the CSRF cookie. This cookie will store CSRF token.
// Optional. Default value "csrf". // Optional. Default value "csrf".
CookieName string `json:"cookie_name"` CookieName string `yaml:"cookie_name"`
// Domain of the CSRF cookie. // Domain of the CSRF cookie.
// Optional. Default value none. // Optional. Default value none.
CookieDomain string `json:"cookie_domain"` CookieDomain string `yaml:"cookie_domain"`
// Path of the CSRF cookie. // Path of the CSRF cookie.
// Optional. Default value none. // Optional. Default value none.
CookiePath string `json:"cookie_path"` CookiePath string `yaml:"cookie_path"`
// Max age (in seconds) of the CSRF cookie. // Max age (in seconds) of the CSRF cookie.
// Optional. Default value 86400 (24hr). // Optional. Default value 86400 (24hr).
CookieMaxAge int `json:"cookie_max_age"` CookieMaxAge int `yaml:"cookie_max_age"`
// Indicates if CSRF cookie is secure. // Indicates if CSRF cookie is secure.
// Optional. Default value false. // Optional. Default value false.
CookieSecure bool `json:"cookie_secure"` CookieSecure bool `yaml:"cookie_secure"`
// Indicates if CSRF cookie is HTTP only. // Indicates if CSRF cookie is HTTP only.
// Optional. Default value false. // Optional. Default value false.
CookieHTTPOnly bool `json:"cookie_http_only"` CookieHTTPOnly bool `yaml:"cookie_http_only"`
} }
// csrfTokenExtractor defines a function that takes `echo.Context` and returns // csrfTokenExtractor defines a function that takes `echo.Context` and returns

View File

@ -20,7 +20,8 @@ type (
// Possible values: // Possible values:
// - "header:<name>" // - "header:<name>"
// - "query:<name>" // - "query:<name>"
KeyLookup string `json:"key_lookup"` // - "form:<name>"
KeyLookup string `yaml:"key_lookup"`
// AuthScheme to be used in the Authorization header. // AuthScheme to be used in the Authorization header.
// Optional. Default value "Bearer". // Optional. Default value "Bearer".
@ -81,6 +82,8 @@ func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc {
switch parts[0] { switch parts[0] {
case "query": case "query":
extractor = keyFromQuery(parts[1]) extractor = keyFromQuery(parts[1])
case "form":
extractor = keyFromForm(parts[1])
} }
return func(next echo.HandlerFunc) echo.HandlerFunc { return func(next echo.HandlerFunc) echo.HandlerFunc {
@ -134,3 +137,14 @@ func keyFromQuery(param string) keyExtractor {
return key, nil return key, nil
} }
} }
// keyFromForm returns a `keyExtractor` that extracts key from the form.
func keyFromForm(param string) keyExtractor {
return func(c echo.Context) (string, error) {
key := c.FormValue(param)
if key == "" {
return "", errors.New("Missing key in the form")
}
return key, nil
}
}

View File

@ -26,6 +26,7 @@ type (
// - time_unix_nano // - time_unix_nano
// - time_rfc3339 // - time_rfc3339
// - time_rfc3339_nano // - time_rfc3339_nano
// - time_custom
// - id (Request ID) // - id (Request ID)
// - remote_ip // - remote_ip
// - uri // - uri
@ -46,7 +47,10 @@ type (
// Example "${remote_ip} ${status}" // Example "${remote_ip} ${status}"
// //
// Optional. Default value DefaultLoggerConfig.Format. // Optional. Default value DefaultLoggerConfig.Format.
Format string `json:"format"` Format string `yaml:"format"`
// Optional. Default value DefaultLoggerConfig.CustomTimeFormat.
CustomTimeFormat string `yaml:"custom_time_format"`
// Output is a writer where logs in JSON format are written. // Output is a writer where logs in JSON format are written.
// Optional. Default value os.Stdout. // Optional. Default value os.Stdout.
@ -66,6 +70,7 @@ var (
`"method":"${method}","uri":"${uri}","status":${status}, "latency":${latency},` + `"method":"${method}","uri":"${uri}","status":${status}, "latency":${latency},` +
`"latency_human":"${latency_human}","bytes_in":${bytes_in},` + `"latency_human":"${latency_human}","bytes_in":${bytes_in},` +
`"bytes_out":${bytes_out}}` + "\n", `"bytes_out":${bytes_out}}` + "\n",
CustomTimeFormat:"2006-01-02 15:04:05.00000",
Output: os.Stdout, Output: os.Stdout,
colorer: color.New(), colorer: color.New(),
} }
@ -126,6 +131,8 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
return buf.WriteString(time.Now().Format(time.RFC3339)) return buf.WriteString(time.Now().Format(time.RFC3339))
case "time_rfc3339_nano": case "time_rfc3339_nano":
return buf.WriteString(time.Now().Format(time.RFC3339Nano)) return buf.WriteString(time.Now().Format(time.RFC3339Nano))
case "time_custom":
return buf.WriteString(time.Now().Format(config.CustomTimeFormat))
case "id": case "id":
id := req.Header.Get(echo.HeaderXRequestID) id := req.Header.Get(echo.HeaderXRequestID)
if id == "" { if id == "" {

View File

@ -1,6 +1,12 @@
package middleware package middleware
import "github.com/labstack/echo" import (
"regexp"
"strconv"
"strings"
"github.com/labstack/echo"
)
type ( type (
// Skipper defines a function to skip middleware. Returning true skips processing // Skipper defines a function to skip middleware. Returning true skips processing
@ -8,6 +14,21 @@ type (
Skipper func(c echo.Context) bool Skipper func(c echo.Context) bool
) )
func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer {
groups := pattern.FindAllStringSubmatch(input, -1)
if groups == nil {
return nil
}
values := groups[0][1:]
replace := make([]string, 2*len(values))
for i, v := range values {
j := 2 * i
replace[j] = "$" + strconv.Itoa(i+1)
replace[j+1] = v
}
return strings.NewReplacer(replace...)
}
// DefaultSkipper returns false which processes the middleware. // DefaultSkipper returns false which processes the middleware.
func DefaultSkipper(echo.Context) bool { func DefaultSkipper(echo.Context) bool {
return false return false

View File

@ -8,6 +8,9 @@ import (
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
"regexp"
"strings"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -24,33 +27,49 @@ type (
// Balancer defines a load balancing technique. // Balancer defines a load balancing technique.
// Required. // Required.
// Possible values:
// - RandomBalancer
// - RoundRobinBalancer
Balancer ProxyBalancer Balancer ProxyBalancer
// Rewrite defines URL path rewrite rules. The values captured in asterisk can be
// retrieved by index e.g. $1, $2 and so on.
// Examples:
// "/old": "/new",
// "/api/*": "/$1",
// "/js/*": "/public/javascripts/$1",
// "/users/*/orders/*": "/user/$1/order/$2",
Rewrite map[string]string
rewriteRegex map[*regexp.Regexp]string
} }
// ProxyTarget defines the upstream target. // ProxyTarget defines the upstream target.
ProxyTarget struct { ProxyTarget struct {
Name string
URL *url.URL URL *url.URL
} }
// RandomBalancer implements a random load balancing technique.
RandomBalancer struct {
Targets []*ProxyTarget
random *rand.Rand
}
// RoundRobinBalancer implements a round-robin load balancing technique.
RoundRobinBalancer struct {
Targets []*ProxyTarget
i uint32
}
// ProxyBalancer defines an interface to implement a load balancing technique. // ProxyBalancer defines an interface to implement a load balancing technique.
ProxyBalancer interface { ProxyBalancer interface {
AddTarget(*ProxyTarget) bool
RemoveTarget(string) bool
Next() *ProxyTarget Next() *ProxyTarget
} }
commonBalancer struct {
targets []*ProxyTarget
mutex sync.RWMutex
}
// RandomBalancer implements a random load balancing technique.
randomBalancer struct {
*commonBalancer
random *rand.Rand
}
// RoundRobinBalancer implements a round-robin load balancing technique.
roundRobinBalancer struct {
*commonBalancer
i uint32
}
) )
var ( var (
@ -104,19 +123,61 @@ func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
}) })
} }
// Next randomly returns an upstream target. // NewRandomBalancer returns a random proxy balancer.
func (r *RandomBalancer) Next() *ProxyTarget { func NewRandomBalancer(targets []*ProxyTarget) ProxyBalancer {
if r.random == nil { b := &randomBalancer{commonBalancer: new(commonBalancer)}
r.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) b.targets = targets
return b
}
// NewRoundRobinBalancer returns a round-robin proxy balancer.
func NewRoundRobinBalancer(targets []*ProxyTarget) ProxyBalancer {
b := &roundRobinBalancer{commonBalancer: new(commonBalancer)}
b.targets = targets
return b
}
// AddTarget adds an upstream target to the list.
func (b *commonBalancer) AddTarget(target *ProxyTarget) bool {
for _, t := range b.targets {
if t.Name == target.Name {
return false
} }
return r.Targets[r.random.Intn(len(r.Targets))] }
b.mutex.Lock()
defer b.mutex.Unlock()
b.targets = append(b.targets, target)
return true
}
// RemoveTarget removes an upstream target from the list.
func (b *commonBalancer) RemoveTarget(name string) bool {
b.mutex.Lock()
defer b.mutex.Unlock()
for i, t := range b.targets {
if t.Name == name {
b.targets = append(b.targets[:i], b.targets[i+1:]...)
return true
}
}
return false
}
// Next randomly returns an upstream target.
func (b *randomBalancer) Next() *ProxyTarget {
if b.random == nil {
b.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
}
b.mutex.RLock()
defer b.mutex.RUnlock()
return b.targets[b.random.Intn(len(b.targets))]
} }
// Next returns an upstream target using round-robin technique. // Next returns an upstream target using round-robin technique.
func (r *RoundRobinBalancer) Next() *ProxyTarget { func (b *roundRobinBalancer) Next() *ProxyTarget {
r.i = r.i % uint32(len(r.Targets)) b.i = b.i % uint32(len(b.targets))
t := r.Targets[r.i] t := b.targets[b.i]
atomic.AddUint32(&r.i, 1) atomic.AddUint32(&b.i, 1)
return t return t
} }
@ -139,6 +200,13 @@ func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
if config.Balancer == nil { if config.Balancer == nil {
panic("echo: proxy middleware requires balancer") panic("echo: proxy middleware requires balancer")
} }
config.rewriteRegex = map[*regexp.Regexp]string{}
// Initialize
for k, v := range config.Rewrite {
k = strings.Replace(k, "*", "(\\S*)", -1)
config.rewriteRegex[regexp.MustCompile(k)] = v
}
return func(next echo.HandlerFunc) echo.HandlerFunc { return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) (err error) { return func(c echo.Context) (err error) {
@ -150,6 +218,14 @@ func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
res := c.Response() res := c.Response()
tgt := config.Balancer.Next() tgt := config.Balancer.Next()
// Rewrite
for k, v := range config.rewriteRegex {
replacer := captureTokens(k, req.URL.Path)
if replacer != nil {
req.URL.Path = replacer.Replace(v)
}
}
// Fix header // Fix header
if req.Header.Get(echo.HeaderXRealIP) == "" { if req.Header.Get(echo.HeaderXRealIP) == "" {
req.Header.Set(echo.HeaderXRealIP, c.RealIP()) req.Header.Set(echo.HeaderXRealIP, c.RealIP())

View File

@ -15,16 +15,16 @@ type (
// Size of the stack to be printed. // Size of the stack to be printed.
// Optional. Default value 4KB. // Optional. Default value 4KB.
StackSize int `json:"stack_size"` StackSize int `yaml:"stack_size"`
// DisableStackAll disables formatting stack traces of all other goroutines // DisableStackAll disables formatting stack traces of all other goroutines
// into buffer after the trace for the current goroutine. // into buffer after the trace for the current goroutine.
// Optional. Default value false. // Optional. Default value false.
DisableStackAll bool `json:"disable_stack_all"` DisableStackAll bool `yaml:"disable_stack_all"`
// DisablePrintStack disables printing stack trace. // DisablePrintStack disables printing stack trace.
// Optional. Default value as false. // Optional. Default value as false.
DisablePrintStack bool `json:"disable_print_stack"` DisablePrintStack bool `yaml:"disable_print_stack"`
} }
) )

View File

@ -6,29 +6,28 @@ import (
"github.com/labstack/echo" "github.com/labstack/echo"
) )
type ( // RedirectConfig defines the config for Redirect middleware.
// RedirectConfig defines the config for Redirect middleware. type RedirectConfig struct {
RedirectConfig struct {
// Skipper defines a function to skip middleware. // Skipper defines a function to skip middleware.
Skipper Skipper Skipper
// Status code to be used when redirecting the request. // Status code to be used when redirecting the request.
// Optional. Default value http.StatusMovedPermanently. // Optional. Default value http.StatusMovedPermanently.
Code int `json:"code"` Code int `yaml:"code"`
} }
)
const ( // redirectLogic represents a function that given a scheme, host and uri
www = "www" // can both: 1) determine if redirect is needed (will set ok accordingly) and
) // 2) return the appropriate redirect url.
type redirectLogic func(scheme, host, uri string) (ok bool, url string)
var ( const www = "www"
// DefaultRedirectConfig is the default Redirect middleware config.
DefaultRedirectConfig = RedirectConfig{ // DefaultRedirectConfig is the default Redirect middleware config.
var DefaultRedirectConfig = RedirectConfig{
Skipper: DefaultSkipper, Skipper: DefaultSkipper,
Code: http.StatusMovedPermanently, Code: http.StatusMovedPermanently,
} }
)
// HTTPSRedirect redirects http requests to https. // HTTPSRedirect redirects http requests to https.
// For example, http://labstack.com will be redirect to https://labstack.com. // For example, http://labstack.com will be redirect to https://labstack.com.
@ -41,29 +40,12 @@ func HTTPSRedirect() echo.MiddlewareFunc {
// HTTPSRedirectWithConfig returns an HTTPSRedirect middleware with config. // HTTPSRedirectWithConfig returns an HTTPSRedirect middleware with config.
// See `HTTPSRedirect()`. // See `HTTPSRedirect()`.
func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
// Defaults return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
if config.Skipper == nil { if ok = scheme != "https"; ok {
config.Skipper = DefaultTrailingSlashConfig.Skipper url = "https://" + host + uri
}
if config.Code == 0 {
config.Code = DefaultRedirectConfig.Code
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
host := req.Host
uri := req.RequestURI
if !c.IsTLS() {
return c.Redirect(config.Code, "https://"+host+uri)
}
return next(c)
}
} }
return
})
} }
// HTTPSWWWRedirect redirects http requests to https www. // HTTPSWWWRedirect redirects http requests to https www.
@ -77,29 +59,12 @@ func HTTPSWWWRedirect() echo.MiddlewareFunc {
// HTTPSWWWRedirectWithConfig returns an HTTPSRedirect middleware with config. // HTTPSWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
// See `HTTPSWWWRedirect()`. // See `HTTPSWWWRedirect()`.
func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
// Defaults return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
if config.Skipper == nil { if ok = scheme != "https" && host[:3] != www; ok {
config.Skipper = DefaultTrailingSlashConfig.Skipper url = "https://www." + host + uri
}
if config.Code == 0 {
config.Code = DefaultRedirectConfig.Code
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
host := req.Host
uri := req.RequestURI
if !c.IsTLS() && host[:3] != www {
return c.Redirect(config.Code, "https://www."+host+uri)
}
return next(c)
}
} }
return
})
} }
// HTTPSNonWWWRedirect redirects http requests to https non www. // HTTPSNonWWWRedirect redirects http requests to https non www.
@ -113,32 +78,15 @@ func HTTPSNonWWWRedirect() echo.MiddlewareFunc {
// HTTPSNonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config. // HTTPSNonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
// See `HTTPSNonWWWRedirect()`. // See `HTTPSNonWWWRedirect()`.
func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
// Defaults return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
if config.Skipper == nil { if ok = scheme != "https"; ok {
config.Skipper = DefaultTrailingSlashConfig.Skipper
}
if config.Code == 0 {
config.Code = DefaultRedirectConfig.Code
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
host := req.Host
uri := req.RequestURI
if !c.IsTLS() {
if host[:3] == www { if host[:3] == www {
return c.Redirect(config.Code, "https://"+host[4:]+uri) host = host[4:]
}
return c.Redirect(config.Code, "https://"+host+uri)
}
return next(c)
} }
url = "https://" + host + uri
} }
return
})
} }
// WWWRedirect redirects non www requests to www. // WWWRedirect redirects non www requests to www.
@ -152,30 +100,12 @@ func WWWRedirect() echo.MiddlewareFunc {
// WWWRedirectWithConfig returns an HTTPSRedirect middleware with config. // WWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
// See `WWWRedirect()`. // See `WWWRedirect()`.
func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
// Defaults return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
if config.Skipper == nil { if ok = host[:3] != www; ok {
config.Skipper = DefaultTrailingSlashConfig.Skipper url = scheme + "://www." + host + uri
}
if config.Code == 0 {
config.Code = DefaultRedirectConfig.Code
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
scheme := c.Scheme()
host := req.Host
if host[:3] != www {
uri := req.RequestURI
return c.Redirect(config.Code, scheme+"://www."+host+uri)
}
return next(c)
}
} }
return
})
} }
// NonWWWRedirect redirects www requests to non www. // NonWWWRedirect redirects www requests to non www.
@ -189,6 +119,15 @@ func NonWWWRedirect() echo.MiddlewareFunc {
// NonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config. // NonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
// See `NonWWWRedirect()`. // See `NonWWWRedirect()`.
func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc { func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
if ok = host[:3] == www; ok {
url = scheme + "://" + host[4:] + uri
}
return
})
}
func redirect(config RedirectConfig, cb redirectLogic) echo.MiddlewareFunc {
if config.Skipper == nil { if config.Skipper == nil {
config.Skipper = DefaultTrailingSlashConfig.Skipper config.Skipper = DefaultTrailingSlashConfig.Skipper
} }
@ -202,13 +141,12 @@ func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
return next(c) return next(c)
} }
req := c.Request() req, scheme := c.Request(), c.Scheme()
scheme := c.Scheme()
host := req.Host host := req.Host
if host[:3] == www { if ok, url := cb(scheme, host, req.RequestURI); ok {
uri := req.RequestURI return c.Redirect(config.Code, url)
return c.Redirect(config.Code, scheme+"://"+host[4:]+uri)
} }
return next(c) return next(c)
} }
} }

83
vendor/github.com/labstack/echo/middleware/rewrite.go generated vendored Normal file
View File

@ -0,0 +1,83 @@
package middleware
import (
"regexp"
"strings"
"github.com/labstack/echo"
)
type (
// RewriteConfig defines the config for Rewrite middleware.
RewriteConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Rules defines the URL path rewrite rules. The values captured in asterisk can be
// retrieved by index e.g. $1, $2 and so on.
// Example:
// "/old": "/new",
// "/api/*": "/$1",
// "/js/*": "/public/javascripts/$1",
// "/users/*/orders/*": "/user/$1/order/$2",
// Required.
Rules map[string]string `yaml:"rules"`
rulesRegex map[*regexp.Regexp]string
}
)
var (
// DefaultRewriteConfig is the default Rewrite middleware config.
DefaultRewriteConfig = RewriteConfig{
Skipper: DefaultSkipper,
}
)
// Rewrite returns a Rewrite middleware.
//
// Rewrite middleware rewrites the URL path based on the provided rules.
func Rewrite(rules map[string]string) echo.MiddlewareFunc {
c := DefaultRewriteConfig
c.Rules = rules
return RewriteWithConfig(c)
}
// RewriteWithConfig returns a Rewrite middleware with config.
// See: `Rewrite()`.
func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc {
// Defaults
if config.Rules == nil {
panic("echo: rewrite middleware requires url path rewrite rules")
}
if config.Skipper == nil {
config.Skipper = DefaultBodyDumpConfig.Skipper
}
config.rulesRegex = map[*regexp.Regexp]string{}
// Initialize
for k, v := range config.Rules {
k = strings.Replace(k, "*", "(\\S*)", -1)
config.rulesRegex[regexp.MustCompile(k)] = v
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) (err error) {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
// Rewrite
for k, v := range config.rulesRegex {
replacer := captureTokens(k, req.URL.Path)
if replacer != nil {
req.URL.Path = replacer.Replace(v)
}
}
return next(c)
}
}
}

View File

@ -15,12 +15,12 @@ type (
// XSSProtection provides protection against cross-site scripting attack (XSS) // XSSProtection provides protection against cross-site scripting attack (XSS)
// by setting the `X-XSS-Protection` header. // by setting the `X-XSS-Protection` header.
// Optional. Default value "1; mode=block". // Optional. Default value "1; mode=block".
XSSProtection string `json:"xss_protection"` XSSProtection string `yaml:"xss_protection"`
// ContentTypeNosniff provides protection against overriding Content-Type // ContentTypeNosniff provides protection against overriding Content-Type
// header by setting the `X-Content-Type-Options` header. // header by setting the `X-Content-Type-Options` header.
// Optional. Default value "nosniff". // Optional. Default value "nosniff".
ContentTypeNosniff string `json:"content_type_nosniff"` ContentTypeNosniff string `yaml:"content_type_nosniff"`
// XFrameOptions can be used to indicate whether or not a browser should // XFrameOptions can be used to indicate whether or not a browser should
// be allowed to render a page in a <frame>, <iframe> or <object> . // be allowed to render a page in a <frame>, <iframe> or <object> .
@ -32,27 +32,27 @@ type (
// - "SAMEORIGIN" - The page can only be displayed in a frame on the same origin as the page itself. // - "SAMEORIGIN" - The page can only be displayed in a frame on the same origin as the page itself.
// - "DENY" - The page cannot be displayed in a frame, regardless of the site attempting to do so. // - "DENY" - The page cannot be displayed in a frame, regardless of the site attempting to do so.
// - "ALLOW-FROM uri" - The page can only be displayed in a frame on the specified origin. // - "ALLOW-FROM uri" - The page can only be displayed in a frame on the specified origin.
XFrameOptions string `json:"x_frame_options"` XFrameOptions string `yaml:"x_frame_options"`
// HSTSMaxAge sets the `Strict-Transport-Security` header to indicate how // HSTSMaxAge sets the `Strict-Transport-Security` header to indicate how
// long (in seconds) browsers should remember that this site is only to // long (in seconds) browsers should remember that this site is only to
// be accessed using HTTPS. This reduces your exposure to some SSL-stripping // be accessed using HTTPS. This reduces your exposure to some SSL-stripping
// man-in-the-middle (MITM) attacks. // man-in-the-middle (MITM) attacks.
// Optional. Default value 0. // Optional. Default value 0.
HSTSMaxAge int `json:"hsts_max_age"` HSTSMaxAge int `yaml:"hsts_max_age"`
// HSTSExcludeSubdomains won't include subdomains tag in the `Strict Transport Security` // HSTSExcludeSubdomains won't include subdomains tag in the `Strict Transport Security`
// header, excluding all subdomains from security policy. It has no effect // header, excluding all subdomains from security policy. It has no effect
// unless HSTSMaxAge is set to a non-zero value. // unless HSTSMaxAge is set to a non-zero value.
// Optional. Default value false. // Optional. Default value false.
HSTSExcludeSubdomains bool `json:"hsts_exclude_subdomains"` HSTSExcludeSubdomains bool `yaml:"hsts_exclude_subdomains"`
// ContentSecurityPolicy sets the `Content-Security-Policy` header providing // ContentSecurityPolicy sets the `Content-Security-Policy` header providing
// security against cross-site scripting (XSS), clickjacking and other code // security against cross-site scripting (XSS), clickjacking and other code
// injection attacks resulting from execution of malicious content in the // injection attacks resulting from execution of malicious content in the
// trusted web page context. // trusted web page context.
// Optional. Default value "". // Optional. Default value "".
ContentSecurityPolicy string `json:"content_security_policy"` ContentSecurityPolicy string `yaml:"content_security_policy"`
} }
) )

View File

@ -12,7 +12,7 @@ type (
// Status code to be used when redirecting the request. // Status code to be used when redirecting the request.
// Optional, but when provided the request is redirected using this code. // Optional, but when provided the request is redirected using this code.
RedirectCode int `json:"redirect_code"` RedirectCode int `yaml:"redirect_code"`
} }
) )

View File

@ -19,20 +19,20 @@ type (
// Root directory from where the static content is served. // Root directory from where the static content is served.
// Required. // Required.
Root string `json:"root"` Root string `yaml:"root"`
// Index file for serving a directory. // Index file for serving a directory.
// Optional. Default value "index.html". // Optional. Default value "index.html".
Index string `json:"index"` Index string `yaml:"index"`
// Enable HTML5 mode by forwarding all not-found requests to root so that // Enable HTML5 mode by forwarding all not-found requests to root so that
// SPA (single-page application) can handle the routing. // SPA (single-page application) can handle the routing.
// Optional. Default value false. // Optional. Default value false.
HTML5 bool `json:"html5"` HTML5 bool `yaml:"html5"`
// Enable directory browsing. // Enable directory browsing.
// Optional. Default value false. // Optional. Default value false.
Browse bool `json:"browse"` Browse bool `yaml:"browse"`
} }
) )

View File

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"net" "net"
"net/http" "net/http"
"strconv"
) )
type ( type (
@ -12,7 +13,9 @@ type (
// See: https://golang.org/pkg/net/http/#ResponseWriter // See: https://golang.org/pkg/net/http/#ResponseWriter
Response struct { Response struct {
echo *Echo echo *Echo
contentLength int64
beforeFuncs []func() beforeFuncs []func()
afterFuncs []func()
Writer http.ResponseWriter Writer http.ResponseWriter
Status int Status int
Size int64 Size int64
@ -40,6 +43,12 @@ func (r *Response) Before(fn func()) {
r.beforeFuncs = append(r.beforeFuncs, fn) r.beforeFuncs = append(r.beforeFuncs, fn)
} }
// After registers a function which is called just after the response is written.
// If the `Content-Length` is unknown, none of the after function is executed.
func (r *Response) After(fn func()) {
r.afterFuncs = append(r.afterFuncs, fn)
}
// WriteHeader sends an HTTP response header with status code. If WriteHeader is // WriteHeader sends an HTTP response header with status code. If WriteHeader is
// not called explicitly, the first call to Write will trigger an implicit // not called explicitly, the first call to Write will trigger an implicit
// WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly // WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly
@ -55,6 +64,7 @@ func (r *Response) WriteHeader(code int) {
r.Status = code r.Status = code
r.Writer.WriteHeader(code) r.Writer.WriteHeader(code)
r.Committed = true r.Committed = true
r.contentLength, _ = strconv.ParseInt(r.Header().Get(HeaderContentLength), 10, 0)
} }
// Write writes the data to the connection as part of an HTTP reply. // Write writes the data to the connection as part of an HTTP reply.
@ -64,6 +74,11 @@ func (r *Response) Write(b []byte) (n int, err error) {
} }
n, err = r.Writer.Write(b) n, err = r.Writer.Write(b)
r.Size += int64(n) r.Size += int64(n)
if r.Size == r.contentLength {
for _, fn := range r.afterFuncs {
fn()
}
}
return return
} }
@ -91,6 +106,9 @@ func (r *Response) CloseNotify() <-chan bool {
} }
func (r *Response) reset(w http.ResponseWriter) { func (r *Response) reset(w http.ResponseWriter) {
r.contentLength = 0
r.beforeFuncs = nil
r.afterFuncs = nil
r.Writer = w r.Writer = w
r.Size = 0 r.Size = 0
r.Status = http.StatusOK r.Status = http.StatusOK

View File

@ -1,7 +1,5 @@
package echo package echo
import "strings"
type ( type (
// Router is the registry of all registered routes for an `Echo` instance for // Router is the registry of all registered routes for an `Echo` instance for
// request matching and URL path parameter parsing. // request matching and URL path parameter parsing.
@ -175,12 +173,6 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
if len(cn.pnames) == 0 { // Issue #729 if len(cn.pnames) == 0 { // Issue #729
cn.pnames = pnames cn.pnames = pnames
} }
for i, n := range pnames {
// Param name aliases
if i < len(cn.pnames) && !strings.Contains(cn.pnames[i], n) {
cn.pnames[i] += "," + n
}
}
} }
} }
return return

2
vendor/manifest vendored
View File

@ -230,7 +230,7 @@
"importpath": "github.com/labstack/echo", "importpath": "github.com/labstack/echo",
"repository": "https://github.com/labstack/echo", "repository": "https://github.com/labstack/echo",
"vcs": "git", "vcs": "git",
"revision": "0473c51f1dbd83487effce00702571d19033a6e5", "revision": "7eec915044a15b70fbb5dd0225c819550b05d596",
"branch": "master", "branch": "master",
"notests": true "notests": true
}, },