summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/labstack/echo
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/labstack/echo')
-rw-r--r--vendor/github.com/labstack/echo/v4/CHANGELOG.md33
-rw-r--r--vendor/github.com/labstack/echo/v4/README.md1
-rw-r--r--vendor/github.com/labstack/echo/v4/bind.go2
-rw-r--r--vendor/github.com/labstack/echo/v4/binder.go16
-rw-r--r--vendor/github.com/labstack/echo/v4/context.go4
-rw-r--r--vendor/github.com/labstack/echo/v4/echo.go13
-rw-r--r--vendor/github.com/labstack/echo/v4/group.go10
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/basic_auth.go2
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/compress.go97
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/cors.go4
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/decompress.go6
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/middleware.go4
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/proxy.go213
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/rate_limiter.go21
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/recover.go28
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/request_logger.go4
-rw-r--r--vendor/github.com/labstack/echo/v4/response.go7
-rw-r--r--vendor/github.com/labstack/echo/v4/router.go9
18 files changed, 363 insertions, 111 deletions
diff --git a/vendor/github.com/labstack/echo/v4/CHANGELOG.md b/vendor/github.com/labstack/echo/v4/CHANGELOG.md
index 83184249..fef7bb98 100644
--- a/vendor/github.com/labstack/echo/v4/CHANGELOG.md
+++ b/vendor/github.com/labstack/echo/v4/CHANGELOG.md
@@ -1,5 +1,38 @@
# Changelog
+## v4.11.1 - 2023-07-16
+
+**Fixes**
+
+* Fix `Gzip` middleware not sending response code for no content responses (404, 301/302 redirects etc) [#2481](https://github.com/labstack/echo/pull/2481)
+
+
+## v4.11.0 - 2023-07-14
+
+
+**Fixes**
+
+* Fixes the proxy middleware concurrency issue of calling the Next() proxy target on Round Robin Balancer [#2409](https://github.com/labstack/echo/pull/2409)
+* Fix `group.RouteNotFound` not working when group has attached middlewares [#2411](https://github.com/labstack/echo/pull/2411)
+* Fix global error handler return error message when message is an error [#2456](https://github.com/labstack/echo/pull/2456)
+* Do not use global timeNow variables [#2477](https://github.com/labstack/echo/pull/2477)
+
+
+**Enhancements**
+
+* Added a optional config variable to disable centralized error handler in recovery middleware [#2410](https://github.com/labstack/echo/pull/2410)
+* refactor: use `strings.ReplaceAll` directly [#2424](https://github.com/labstack/echo/pull/2424)
+* Add support for Go1.20 `http.rwUnwrapper` to Response struct [#2425](https://github.com/labstack/echo/pull/2425)
+* Check whether is nil before invoking centralized error handling [#2429](https://github.com/labstack/echo/pull/2429)
+* Proper colon support in `echo.Reverse` method [#2416](https://github.com/labstack/echo/pull/2416)
+* Fix misuses of a vs an in documentation comments [#2436](https://github.com/labstack/echo/pull/2436)
+* Add link to slog.Handler library for Echo logging into README.md [#2444](https://github.com/labstack/echo/pull/2444)
+* In proxy middleware Support retries of failed proxy requests [#2414](https://github.com/labstack/echo/pull/2414)
+* gofmt fixes to comments [#2452](https://github.com/labstack/echo/pull/2452)
+* gzip response only if it exceeds a minimal length [#2267](https://github.com/labstack/echo/pull/2267)
+* Upgrade packages [#2475](https://github.com/labstack/echo/pull/2475)
+
+
## v4.10.2 - 2023-02-22
**Security**
diff --git a/vendor/github.com/labstack/echo/v4/README.md b/vendor/github.com/labstack/echo/v4/README.md
index fe78b6ed..ea8f30f6 100644
--- a/vendor/github.com/labstack/echo/v4/README.md
+++ b/vendor/github.com/labstack/echo/v4/README.md
@@ -110,6 +110,7 @@ of middlewares in this list.
| [github.com/swaggo/echo-swagger](https://github.com/swaggo/echo-swagger) | Automatically generate RESTful API documentation with [Swagger](https://swagger.io/) 2.0. |
| [github.com/ziflex/lecho](https://github.com/ziflex/lecho) | [Zerolog](https://github.com/rs/zerolog) logging library wrapper for Echo logger interface. |
| [github.com/brpaz/echozap](https://github.com/brpaz/echozap) | UberĀ“s [Zap](https://github.com/uber-go/zap) logging library wrapper for Echo logger interface. |
+| [github.com/samber/slog-echo](https://github.com/samber/slog-echo) | Go [slog](https://pkg.go.dev/golang.org/x/exp/slog) logging library wrapper for Echo logger interface. |
| [github.com/darkweak/souin/plugins/echo](https://github.com/darkweak/souin/tree/master/plugins/echo) | HTTP cache system based on [Souin](https://github.com/darkweak/souin) to automatically get your endpoints cached. It supports some distributed and non-distributed storage systems depending your needs. |
| [github.com/mikestefanello/pagoda](https://github.com/mikestefanello/pagoda) | Rapid, easy full-stack web development starter kit built with Echo. |
| [github.com/go-woo/protoc-gen-echo](https://github.com/go-woo/protoc-gen-echo) | ProtoBuf generate Echo server side code |
diff --git a/vendor/github.com/labstack/echo/v4/bind.go b/vendor/github.com/labstack/echo/v4/bind.go
index c841ca01..374a2aec 100644
--- a/vendor/github.com/labstack/echo/v4/bind.go
+++ b/vendor/github.com/labstack/echo/v4/bind.go
@@ -114,7 +114,7 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
// Only bind query parameters for GET/DELETE/HEAD to avoid unexpected behavior with destination struct binding from body.
// For example a request URL `&id=1&lang=en` with body `{"id":100,"lang":"de"}` would lead to precedence issues.
// The HTTP method check restores pre-v4.1.11 behavior to avoid these problems (see issue #1670)
- method := c.Request().Method
+ method := c.Request().Method
if method == http.MethodGet || method == http.MethodDelete || method == http.MethodHead {
if err = b.BindQueryParams(c, i); err != nil {
return err
diff --git a/vendor/github.com/labstack/echo/v4/binder.go b/vendor/github.com/labstack/echo/v4/binder.go
index 5a6cf9d9..29cceca0 100644
--- a/vendor/github.com/labstack/echo/v4/binder.go
+++ b/vendor/github.com/labstack/echo/v4/binder.go
@@ -1236,7 +1236,7 @@ func (b *ValueBinder) durations(sourceParam string, values []string, dest *[]tim
// Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00
//
// Note:
-// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
+// - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, false, time.Second)
}
@@ -1247,7 +1247,7 @@ func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder
// Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00
//
// Note:
-// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
+// - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, true, time.Second)
}
@@ -1257,7 +1257,7 @@ func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBi
// Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00
//
// Note:
-// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
+// - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
func (b *ValueBinder) UnixTimeMilli(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, false, time.Millisecond)
}
@@ -1268,7 +1268,7 @@ func (b *ValueBinder) UnixTimeMilli(sourceParam string, dest *time.Time) *ValueB
// Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00
//
// Note:
-// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
+// - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
func (b *ValueBinder) MustUnixTimeMilli(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, true, time.Millisecond)
}
@@ -1280,8 +1280,8 @@ func (b *ValueBinder) MustUnixTimeMilli(sourceParam string, dest *time.Time) *Va
// Example: 999999999 binds to 1970-01-01T00:00:00.999999999+00:00
//
// Note:
-// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
-// * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
+// - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
+// - Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, false, time.Nanosecond)
}
@@ -1294,8 +1294,8 @@ func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBi
// Example: 999999999 binds to 1970-01-01T00:00:00.999999999+00:00
//
// Note:
-// * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
-// * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
+// - time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
+// - Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
func (b *ValueBinder) MustUnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
return b.unixTime(sourceParam, dest, true, time.Nanosecond)
}
diff --git a/vendor/github.com/labstack/echo/v4/context.go b/vendor/github.com/labstack/echo/v4/context.go
index b3a7ce8d..27da28a9 100644
--- a/vendor/github.com/labstack/echo/v4/context.go
+++ b/vendor/github.com/labstack/echo/v4/context.go
@@ -100,8 +100,8 @@ type (
// Set saves data in the context.
Set(key string, val interface{})
- // Bind binds the request body into provided type `i`. The default binder
- // does it based on Content-Type header.
+ // Bind binds path params, query params and the request body into provided type `i`. The default binder
+ // binds body based on Content-Type header.
Bind(i interface{}) error
// Validate validates provided `i`. It is usually called after `Context#Bind()`.
diff --git a/vendor/github.com/labstack/echo/v4/echo.go b/vendor/github.com/labstack/echo/v4/echo.go
index 085a3a7f..22a5b7af 100644
--- a/vendor/github.com/labstack/echo/v4/echo.go
+++ b/vendor/github.com/labstack/echo/v4/echo.go
@@ -39,6 +39,7 @@ package echo
import (
stdContext "context"
"crypto/tls"
+ "encoding/json"
"errors"
"fmt"
"io"
@@ -258,7 +259,7 @@ const (
const (
// Version of Echo
- Version = "4.10.2"
+ Version = "4.11.1"
website = "https://echo.labstack.com"
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
banner = `
@@ -438,12 +439,18 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
// Issue #1426
code := he.Code
message := he.Message
- if m, ok := he.Message.(string); ok {
+
+ switch m := he.Message.(type) {
+ case string:
if e.Debug {
message = Map{"message": m, "error": err.Error()}
} else {
message = Map{"message": m}
}
+ case json.Marshaler:
+ // do nothing - this type knows how to format itself to JSON
+ case error:
+ message = Map{"message": m.Error()}
}
// Send response
@@ -614,7 +621,7 @@ func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
return e.URI(h, params...)
}
-// Reverse generates an URL from route name and provided parameters.
+// Reverse generates a URL from route name and provided parameters.
func (e *Echo) Reverse(name string, params ...interface{}) string {
return e.router.Reverse(name, params...)
}
diff --git a/vendor/github.com/labstack/echo/v4/group.go b/vendor/github.com/labstack/echo/v4/group.go
index 28ce0dd9..749a5caa 100644
--- a/vendor/github.com/labstack/echo/v4/group.go
+++ b/vendor/github.com/labstack/echo/v4/group.go
@@ -23,10 +23,12 @@ func (g *Group) Use(middleware ...MiddlewareFunc) {
if len(g.middleware) == 0 {
return
}
- // 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.
- g.Any("", NotFoundHandler)
- g.Any("/*", NotFoundHandler)
+ // group level middlewares are different from Echo `Pre` and `Use` middlewares (those are global). Group level middlewares
+ // are only executed if they are added to the Router with route.
+ // So we register catch all route (404 is a safe way to emulate route match) for this group and now during routing the
+ // Router would find route to match our request path and therefore guarantee the middleware(s) will get executed.
+ g.RouteNotFound("", NotFoundHandler)
+ g.RouteNotFound("/*", NotFoundHandler)
}
// CONNECT implements `Echo#CONNECT()` for sub-routes within the Group.
diff --git a/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go b/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go
index 52ef1042..f9e8caaf 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go
@@ -2,9 +2,9 @@ package middleware
import (
"encoding/base64"
+ "net/http"
"strconv"
"strings"
- "net/http"
"github.com/labstack/echo/v4"
)
diff --git a/vendor/github.com/labstack/echo/v4/middleware/compress.go b/vendor/github.com/labstack/echo/v4/middleware/compress.go
index 9e5f6106..3e9bd320 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/compress.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/compress.go
@@ -2,6 +2,7 @@ package middleware
import (
"bufio"
+ "bytes"
"compress/gzip"
"io"
"net"
@@ -21,12 +22,30 @@ type (
// Gzip compression level.
// Optional. Default value -1.
Level int `yaml:"level"`
+
+ // Length threshold before gzip compression is applied.
+ // Optional. Default value 0.
+ //
+ // Most of the time you will not need to change the default. Compressing
+ // a short response might increase the transmitted data because of the
+ // gzip format overhead. Compressing the response will also consume CPU
+ // and time on the server and the client (for decompressing). Depending on
+ // your use case such a threshold might be useful.
+ //
+ // See also:
+ // https://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits
+ MinLength int
}
gzipResponseWriter struct {
io.Writer
http.ResponseWriter
- wroteBody bool
+ wroteHeader bool
+ wroteBody bool
+ minLength int
+ minLengthExceeded bool
+ buffer *bytes.Buffer
+ code int
}
)
@@ -37,8 +56,9 @@ const (
var (
// DefaultGzipConfig is the default Gzip middleware config.
DefaultGzipConfig = GzipConfig{
- Skipper: DefaultSkipper,
- Level: -1,
+ Skipper: DefaultSkipper,
+ Level: -1,
+ MinLength: 0,
}
)
@@ -58,8 +78,12 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
if config.Level == 0 {
config.Level = DefaultGzipConfig.Level
}
+ if config.MinLength < 0 {
+ config.MinLength = DefaultGzipConfig.MinLength
+ }
pool := gzipCompressPool(config)
+ bpool := bufferPool()
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
@@ -70,7 +94,6 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
res := c.Response()
res.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding)
if strings.Contains(c.Request().Header.Get(echo.HeaderAcceptEncoding), gzipScheme) {
- res.Header().Set(echo.HeaderContentEncoding, gzipScheme) // Issue #806
i := pool.Get()
w, ok := i.(*gzip.Writer)
if !ok {
@@ -78,19 +101,38 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
}
rw := res.Writer
w.Reset(rw)
- grw := &gzipResponseWriter{Writer: w, ResponseWriter: rw}
+
+ buf := bpool.Get().(*bytes.Buffer)
+ buf.Reset()
+
+ grw := &gzipResponseWriter{Writer: w, ResponseWriter: rw, minLength: config.MinLength, buffer: buf}
defer func() {
+ // There are different reasons for cases when we have not yet written response to the client and now need to do so.
+ // a) handler response had only response code and no response body (ala 404 or redirects etc). Response code need to be written now.
+ // b) body is shorter than our minimum length threshold and being buffered currently and needs to be written
if !grw.wroteBody {
if res.Header().Get(echo.HeaderContentEncoding) == gzipScheme {
res.Header().Del(echo.HeaderContentEncoding)
}
+ if grw.wroteHeader {
+ rw.WriteHeader(grw.code)
+ }
// We have to reset response to it's pristine state when
// nothing is written to body or error is returned.
// See issue #424, #407.
res.Writer = rw
w.Reset(io.Discard)
+ } else if !grw.minLengthExceeded {
+ // Write uncompressed response
+ res.Writer = rw
+ if grw.wroteHeader {
+ grw.ResponseWriter.WriteHeader(grw.code)
+ }
+ grw.buffer.WriteTo(rw)
+ w.Reset(io.Discard)
}
w.Close()
+ bpool.Put(buf)
pool.Put(w)
}()
res.Writer = grw
@@ -102,7 +144,11 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
func (w *gzipResponseWriter) WriteHeader(code int) {
w.Header().Del(echo.HeaderContentLength) // Issue #444
- w.ResponseWriter.WriteHeader(code)
+
+ w.wroteHeader = true
+
+ // Delay writing of the header until we know if we'll actually compress the response
+ w.code = code
}
func (w *gzipResponseWriter) Write(b []byte) (int, error) {
@@ -110,10 +156,40 @@ func (w *gzipResponseWriter) Write(b []byte) (int, error) {
w.Header().Set(echo.HeaderContentType, http.DetectContentType(b))
}
w.wroteBody = true
+
+ if !w.minLengthExceeded {
+ n, err := w.buffer.Write(b)
+
+ if w.buffer.Len() >= w.minLength {
+ w.minLengthExceeded = true
+
+ // The minimum length is exceeded, add Content-Encoding header and write the header
+ w.Header().Set(echo.HeaderContentEncoding, gzipScheme) // Issue #806
+ if w.wroteHeader {
+ w.ResponseWriter.WriteHeader(w.code)
+ }
+
+ return w.Writer.Write(w.buffer.Bytes())
+ }
+
+ return n, err
+ }
+
return w.Writer.Write(b)
}
func (w *gzipResponseWriter) Flush() {
+ if !w.minLengthExceeded {
+ // Enforce compression because we will not know how much more data will come
+ w.minLengthExceeded = true
+ w.Header().Set(echo.HeaderContentEncoding, gzipScheme) // Issue #806
+ if w.wroteHeader {
+ w.ResponseWriter.WriteHeader(w.code)
+ }
+
+ w.Writer.Write(w.buffer.Bytes())
+ }
+
w.Writer.(*gzip.Writer).Flush()
if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
flusher.Flush()
@@ -142,3 +218,12 @@ func gzipCompressPool(config GzipConfig) sync.Pool {
},
}
}
+
+func bufferPool() sync.Pool {
+ return sync.Pool{
+ New: func() interface{} {
+ b := &bytes.Buffer{}
+ return b
+ },
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/cors.go b/vendor/github.com/labstack/echo/v4/middleware/cors.go
index 149de347..6ddb540a 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/cors.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/cors.go
@@ -150,8 +150,8 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
allowOriginPatterns := []string{}
for _, origin := range config.AllowOrigins {
pattern := regexp.QuoteMeta(origin)
- pattern = strings.Replace(pattern, "\\*", ".*", -1)
- pattern = strings.Replace(pattern, "\\?", ".", -1)
+ pattern = strings.ReplaceAll(pattern, "\\*", ".*")
+ pattern = strings.ReplaceAll(pattern, "\\?", ".")
pattern = "^" + pattern + "$"
allowOriginPatterns = append(allowOriginPatterns, pattern)
}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/decompress.go b/vendor/github.com/labstack/echo/v4/middleware/decompress.go
index 88ec7098..a73c9738 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/decompress.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/decompress.go
@@ -20,7 +20,7 @@ type (
}
)
-//GZIPEncoding content-encoding header if set to "gzip", decompress body contents.
+// GZIPEncoding content-encoding header if set to "gzip", decompress body contents.
const GZIPEncoding string = "gzip"
// Decompressor is used to get the sync.Pool used by the middleware to get Gzip readers
@@ -44,12 +44,12 @@ func (d *DefaultGzipDecompressPool) gzipDecompressPool() sync.Pool {
return sync.Pool{New: func() interface{} { return new(gzip.Reader) }}
}
-//Decompress decompresses request body based if content encoding type is set to "gzip" with default config
+// Decompress decompresses request body based if content encoding type is set to "gzip" with default config
func Decompress() echo.MiddlewareFunc {
return DecompressWithConfig(DefaultDecompressConfig)
}
-//DecompressWithConfig decompresses request body based if content encoding type is set to "gzip" with config
+// DecompressWithConfig decompresses request body based if content encoding type is set to "gzip" with config
func DecompressWithConfig(config DecompressConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
diff --git a/vendor/github.com/labstack/echo/v4/middleware/middleware.go b/vendor/github.com/labstack/echo/v4/middleware/middleware.go
index f250ca49..664f71f4 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/middleware.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/middleware.go
@@ -38,9 +38,9 @@ func rewriteRulesRegex(rewrite map[string]string) map[*regexp.Regexp]string {
rulesRegex := map[*regexp.Regexp]string{}
for k, v := range rewrite {
k = regexp.QuoteMeta(k)
- k = strings.Replace(k, `\*`, "(.*?)", -1)
+ k = strings.ReplaceAll(k, `\*`, "(.*?)")
if strings.HasPrefix(k, `\^`) {
- k = strings.Replace(k, `\^`, "^", -1)
+ k = strings.ReplaceAll(k, `\^`, "^")
}
k = k + "$"
rulesRegex[regexp.MustCompile(k)] = v
diff --git a/vendor/github.com/labstack/echo/v4/middleware/proxy.go b/vendor/github.com/labstack/echo/v4/middleware/proxy.go
index d2cd2aa6..e4f98d9e 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/proxy.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/proxy.go
@@ -12,7 +12,6 @@ import (
"regexp"
"strings"
"sync"
- "sync/atomic"
"time"
"github.com/labstack/echo/v4"
@@ -30,6 +29,33 @@ type (
// Required.
Balancer ProxyBalancer
+ // RetryCount defines the number of times a failed proxied request should be retried
+ // using the next available ProxyTarget. Defaults to 0, meaning requests are never retried.
+ RetryCount int
+
+ // RetryFilter defines a function used to determine if a failed request to a
+ // ProxyTarget should be retried. The RetryFilter will only be called when the number
+ // of previous retries is less than RetryCount. If the function returns true, the
+ // request will be retried. The provided error indicates the reason for the request
+ // failure. When the ProxyTarget is unavailable, the error will be an instance of
+ // echo.HTTPError with a Code of http.StatusBadGateway. In all other cases, the error
+ // will indicate an internal error in the Proxy middleware. When a RetryFilter is not
+ // specified, all requests that fail with http.StatusBadGateway will be retried. A custom
+ // RetryFilter can be provided to only retry specific requests. Note that RetryFilter is
+ // only called when the request to the target fails, or an internal error in the Proxy
+ // middleware has occurred. Successful requests that return a non-200 response code cannot
+ // be retried.
+ RetryFilter func(c echo.Context, e error) bool
+
+ // ErrorHandler defines a function which can be used to return custom errors from
+ // the Proxy middleware. ErrorHandler is only invoked when there has been
+ // either an internal error in the Proxy middleware or the ProxyTarget is
+ // unavailable. Due to the way requests are proxied, ErrorHandler is not invoked
+ // when a ProxyTarget returns a non-200 response. In these cases, the response
+ // is already written so errors cannot be modified. ErrorHandler is only
+ // invoked after all retry attempts have been exhausted.
+ ErrorHandler func(c echo.Context, err error) error
+
// Rewrite defines URL path rewrite rules. The values captured in asterisk can be
// retrieved by index e.g. $1, $2 and so on.
// Examples:
@@ -72,26 +98,28 @@ type (
Next(echo.Context) *ProxyTarget
}
- // TargetProvider defines an interface that gives the opportunity for balancer to return custom errors when selecting target.
+ // TargetProvider defines an interface that gives the opportunity for balancer
+ // to return custom errors when selecting target.
TargetProvider interface {
NextTarget(echo.Context) (*ProxyTarget, error)
}
commonBalancer struct {
targets []*ProxyTarget
- mutex sync.RWMutex
+ mutex sync.Mutex
}
// RandomBalancer implements a random load balancing technique.
randomBalancer struct {
- *commonBalancer
+ commonBalancer
random *rand.Rand
}
// RoundRobinBalancer implements a round-robin load balancing technique.
roundRobinBalancer struct {
- *commonBalancer
- i uint32
+ commonBalancer
+ // tracking the index on `targets` slice for the next `*ProxyTarget` to be used
+ i int
}
)
@@ -107,14 +135,14 @@ func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
in, _, err := c.Response().Hijack()
if err != nil {
- c.Set("_error", fmt.Sprintf("proxy raw, hijack error=%v, url=%s", t.URL, err))
+ c.Set("_error", fmt.Errorf("proxy raw, hijack error=%w, url=%s", err, t.URL))
return
}
defer in.Close()
out, err := net.Dial("tcp", t.URL.Host)
if err != nil {
- c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", t.URL, err)))
+ c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", err, t.URL)))
return
}
defer out.Close()
@@ -122,7 +150,7 @@ func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
// Write header
err = r.Write(out)
if err != nil {
- c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request header copy error=%v, url=%s", t.URL, err)))
+ c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request header copy error=%v, url=%s", err, t.URL)))
return
}
@@ -136,39 +164,44 @@ func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
go cp(in, out)
err = <-errCh
if err != nil && err != io.EOF {
- c.Set("_error", fmt.Errorf("proxy raw, copy body error=%v, url=%s", t.URL, err))
+ c.Set("_error", fmt.Errorf("proxy raw, copy body error=%w, url=%s", err, t.URL))
}
})
}
// NewRandomBalancer returns a random proxy balancer.
func NewRandomBalancer(targets []*ProxyTarget) ProxyBalancer {
- b := &randomBalancer{commonBalancer: new(commonBalancer)}
+ b := randomBalancer{}
b.targets = targets
- return b
+ b.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
+ return &b
}
// NewRoundRobinBalancer returns a round-robin proxy balancer.
func NewRoundRobinBalancer(targets []*ProxyTarget) ProxyBalancer {
- b := &roundRobinBalancer{commonBalancer: new(commonBalancer)}
+ b := roundRobinBalancer{}
b.targets = targets
- return b
+ return &b
}
-// AddTarget adds an upstream target to the list.
+// AddTarget adds an upstream target to the list and returns `true`.
+//
+// However, if a target with the same name already exists then the operation is aborted returning `false`.
func (b *commonBalancer) AddTarget(target *ProxyTarget) bool {
+ b.mutex.Lock()
+ defer b.mutex.Unlock()
for _, t := range b.targets {
if t.Name == target.Name {
return false
}
}
- b.mutex.Lock()
- defer b.mutex.Unlock()
b.targets = append(b.targets, target)
return true
}
-// RemoveTarget removes an upstream target from the list.
+// RemoveTarget removes an upstream target from the list by name.
+//
+// Returns `true` on success, `false` if no target with the name is found.
func (b *commonBalancer) RemoveTarget(name string) bool {
b.mutex.Lock()
defer b.mutex.Unlock()
@@ -182,21 +215,58 @@ func (b *commonBalancer) RemoveTarget(name string) bool {
}
// Next randomly returns an upstream target.
+//
+// Note: `nil` is returned in case upstream target list is empty.
func (b *randomBalancer) Next(c echo.Context) *ProxyTarget {
- if b.random == nil {
- b.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
+ b.mutex.Lock()
+ defer b.mutex.Unlock()
+ if len(b.targets) == 0 {
+ return nil
+ } else if len(b.targets) == 1 {
+ return b.targets[0]
}
- 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. In the case
+// where a previously failed request is being retried, the round-robin
+// balancer will attempt to use the next target relative to the original
+// request. If the list of targets held by the balancer is modified while a
+// failed request is being retried, it is possible that the balancer will
+// return the original failed target.
+//
+// Note: `nil` is returned in case upstream target list is empty.
func (b *roundRobinBalancer) Next(c echo.Context) *ProxyTarget {
- b.i = b.i % uint32(len(b.targets))
- t := b.targets[b.i]
- atomic.AddUint32(&b.i, 1)
- return t
+ b.mutex.Lock()
+ defer b.mutex.Unlock()
+ if len(b.targets) == 0 {
+ return nil
+ } else if len(b.targets) == 1 {
+ return b.targets[0]
+ }
+
+ var i int
+ const lastIdxKey = "_round_robin_last_index"
+ // This request is a retry, start from the index of the previous
+ // target to ensure we don't attempt to retry the request with
+ // the same failed target
+ if c.Get(lastIdxKey) != nil {
+ i = c.Get(lastIdxKey).(int)
+ i++
+ if i >= len(b.targets) {
+ i = 0
+ }
+ } else {
+ // This is a first time request, use the global index
+ if b.i >= len(b.targets) {
+ b.i = 0
+ }
+ i = b.i
+ b.i++
+ }
+
+ c.Set(lastIdxKey, i)
+ return b.targets[i]
}
// Proxy returns a Proxy middleware.
@@ -211,14 +281,26 @@ func Proxy(balancer ProxyBalancer) echo.MiddlewareFunc {
// ProxyWithConfig returns a Proxy middleware with config.
// See: `Proxy()`
func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
+ if config.Balancer == nil {
+ panic("echo: proxy middleware requires balancer")
+ }
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultProxyConfig.Skipper
}
- if config.Balancer == nil {
- panic("echo: proxy middleware requires balancer")
+ if config.RetryFilter == nil {
+ config.RetryFilter = func(c echo.Context, e error) bool {
+ if httpErr, ok := e.(*echo.HTTPError); ok {
+ return httpErr.Code == http.StatusBadGateway
+ }
+ return false
+ }
+ }
+ if config.ErrorHandler == nil {
+ config.ErrorHandler = func(c echo.Context, err error) error {
+ return err
+ }
}
-
if config.Rewrite != nil {
if config.RegexRewrite == nil {
config.RegexRewrite = make(map[*regexp.Regexp]string)
@@ -229,28 +311,17 @@ func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
}
provider, isTargetProvider := config.Balancer.(TargetProvider)
+
return func(next echo.HandlerFunc) echo.HandlerFunc {
- return func(c echo.Context) (err error) {
+ return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
res := c.Response()
-
- var tgt *ProxyTarget
- if isTargetProvider {
- tgt, err = provider.NextTarget(c)
- if err != nil {
- return err
- }
- } else {
- tgt = config.Balancer.Next(c)
- }
- c.Set(config.ContextKey, tgt)
-
if err := rewriteURL(config.RegexRewrite, req); err != nil {
- return err
+ return config.ErrorHandler(c, err)
}
// Fix header
@@ -266,19 +337,49 @@ func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
req.Header.Set(echo.HeaderXForwardedFor, c.RealIP())
}
- // Proxy
- switch {
- case c.IsWebSocket():
- proxyRaw(tgt, c).ServeHTTP(res, req)
- case req.Header.Get(echo.HeaderAccept) == "text/event-stream":
- default:
- proxyHTTP(tgt, c, config).ServeHTTP(res, req)
- }
- if e, ok := c.Get("_error").(error); ok {
- err = e
- }
+ retries := config.RetryCount
+ for {
+ var tgt *ProxyTarget
+ var err error
+ if isTargetProvider {
+ tgt, err = provider.NextTarget(c)
+ if err != nil {
+ return config.ErrorHandler(c, err)
+ }
+ } else {
+ tgt = config.Balancer.Next(c)
+ }
- return
+ c.Set(config.ContextKey, tgt)
+
+ //If retrying a failed request, clear any previous errors from
+ //context here so that balancers have the option to check for
+ //errors that occurred using previous target
+ if retries < config.RetryCount {
+ c.Set("_error", nil)
+ }
+
+ // Proxy
+ switch {
+ case c.IsWebSocket():
+ proxyRaw(tgt, c).ServeHTTP(res, req)
+ case req.Header.Get(echo.HeaderAccept) == "text/event-stream":
+ default:
+ proxyHTTP(tgt, c, config).ServeHTTP(res, req)
+ }
+
+ err, hasError := c.Get("_error").(error)
+ if !hasError {
+ return nil
+ }
+
+ retry := retries > 0 && config.RetryFilter(c, err)
+ if !retry {
+ return config.ErrorHandler(c, err)
+ }
+
+ retries--
+ }
}
}
}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/rate_limiter.go b/vendor/github.com/labstack/echo/v4/middleware/rate_limiter.go
index f7fae83c..1d24df52 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/rate_limiter.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/rate_limiter.go
@@ -160,6 +160,8 @@ type (
burst int
expiresIn time.Duration
lastCleanup time.Time
+
+ timeNow func() time.Time
}
// Visitor signifies a unique user's limiter details
Visitor struct {
@@ -219,7 +221,8 @@ func NewRateLimiterMemoryStoreWithConfig(config RateLimiterMemoryStoreConfig) (s
store.burst = int(config.Rate)
}
store.visitors = make(map[string]*Visitor)
- store.lastCleanup = now()
+ store.timeNow = time.Now
+ store.lastCleanup = store.timeNow()
return
}
@@ -244,12 +247,13 @@ func (store *RateLimiterMemoryStore) Allow(identifier string) (bool, error) {
limiter.Limiter = rate.NewLimiter(store.rate, store.burst)
store.visitors[identifier] = limiter
}
- limiter.lastSeen = now()
- if now().Sub(store.lastCleanup) > store.expiresIn {
+ now := store.timeNow()
+ limiter.lastSeen = now
+ if now.Sub(store.lastCleanup) > store.expiresIn {
store.cleanupStaleVisitors()
}
store.mutex.Unlock()
- return limiter.AllowN(now(), 1), nil
+ return limiter.AllowN(store.timeNow(), 1), nil
}
/*
@@ -258,14 +262,9 @@ of users who haven't visited again after the configured expiry time has elapsed
*/
func (store *RateLimiterMemoryStore) cleanupStaleVisitors() {
for id, visitor := range store.visitors {
- if now().Sub(visitor.lastSeen) > store.expiresIn {
+ if store.timeNow().Sub(visitor.lastSeen) > store.expiresIn {
delete(store.visitors, id)
}
}
- store.lastCleanup = now()
+ store.lastCleanup = store.timeNow()
}
-
-/*
-actual time method which is mocked in test file
-*/
-var now = time.Now
diff --git a/vendor/github.com/labstack/echo/v4/middleware/recover.go b/vendor/github.com/labstack/echo/v4/middleware/recover.go
index 7b612853..0466cfe5 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/recover.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/recover.go
@@ -37,19 +37,26 @@ type (
// LogErrorFunc defines a function for custom logging in the middleware.
// If it's set you don't need to provide LogLevel for config.
+ // If this function returns nil, the centralized HTTPErrorHandler will not be called.
LogErrorFunc LogErrorFunc
+
+ // DisableErrorHandler disables the call to centralized HTTPErrorHandler.
+ // The recovered error is then passed back to upstream middleware, instead of swallowing the error.
+ // Optional. Default value false.
+ DisableErrorHandler bool `yaml:"disable_error_handler"`
}
)
var (
// DefaultRecoverConfig is the default Recover middleware config.
DefaultRecoverConfig = RecoverConfig{
- Skipper: DefaultSkipper,
- StackSize: 4 << 10, // 4 KB
- DisableStackAll: false,
- DisablePrintStack: false,
- LogLevel: 0,
- LogErrorFunc: nil,
+ Skipper: DefaultSkipper,
+ StackSize: 4 << 10, // 4 KB
+ DisableStackAll: false,
+ DisablePrintStack: false,
+ LogLevel: 0,
+ LogErrorFunc: nil,
+ DisableErrorHandler: false,
}
)
@@ -71,7 +78,7 @@ func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc {
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
- return func(c echo.Context) error {
+ return func(c echo.Context) (returnErr error) {
if config.Skipper(c) {
return next(c)
}
@@ -113,7 +120,12 @@ func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc {
c.Logger().Print(msg)
}
}
- c.Error(err)
+
+ if err != nil && !config.DisableErrorHandler {
+ c.Error(err)
+ } else {
+ returnErr = err
+ }
}
}()
return next(c)
diff --git a/vendor/github.com/labstack/echo/v4/middleware/request_logger.go b/vendor/github.com/labstack/echo/v4/middleware/request_logger.go
index b9e36925..ce76230c 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/request_logger.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/request_logger.go
@@ -225,7 +225,7 @@ func (config RequestLoggerConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
if config.Skipper == nil {
config.Skipper = DefaultSkipper
}
- now = time.Now
+ now := time.Now
if config.timeNow != nil {
now = config.timeNow
}
@@ -257,7 +257,7 @@ func (config RequestLoggerConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
config.BeforeNextFunc(c)
}
err := next(c)
- if config.HandleError {
+ if err != nil && config.HandleError {
c.Error(err)
}
diff --git a/vendor/github.com/labstack/echo/v4/response.go b/vendor/github.com/labstack/echo/v4/response.go
index 84f7c9e7..d9c9aa6e 100644
--- a/vendor/github.com/labstack/echo/v4/response.go
+++ b/vendor/github.com/labstack/echo/v4/response.go
@@ -94,6 +94,13 @@ func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return r.Writer.(http.Hijacker).Hijack()
}
+// Unwrap returns the original http.ResponseWriter.
+// ResponseController can be used to access the original http.ResponseWriter.
+// See [https://go.dev/blog/go1.20]
+func (r *Response) Unwrap() http.ResponseWriter {
+ return r.Writer
+}
+
func (r *Response) reset(w http.ResponseWriter) {
r.beforeFuncs = nil
r.afterFuncs = nil
diff --git a/vendor/github.com/labstack/echo/v4/router.go b/vendor/github.com/labstack/echo/v4/router.go
index 597660d3..ee6f3fa4 100644
--- a/vendor/github.com/labstack/echo/v4/router.go
+++ b/vendor/github.com/labstack/echo/v4/router.go
@@ -151,7 +151,7 @@ func (r *Router) Routes() []*Route {
return routes
}
-// Reverse generates an URL from route name and provided parameters.
+// Reverse generates a URL from route name and provided parameters.
func (r *Router) Reverse(name string, params ...interface{}) string {
uri := new(bytes.Buffer)
ln := len(params)
@@ -159,7 +159,12 @@ func (r *Router) Reverse(name string, params ...interface{}) string {
for _, route := range r.routes {
if route.Name == name {
for i, l := 0, len(route.Path); i < l; i++ {
- if (route.Path[i] == ':' || route.Path[i] == '*') && n < ln {
+ hasBackslash := route.Path[i] == '\\'
+ if hasBackslash && i+1 < l && route.Path[i+1] == ':' {
+ i++ // backslash before colon escapes that colon. in that case skip backslash
+ }
+ if n < ln && (route.Path[i] == '*' || (!hasBackslash && route.Path[i] == ':')) {
+ // in case of `*` wildcard or `:` (unescaped colon) param we replace everything till next slash or end of path
for ; i < l && route.Path[i] != '/'; i++ {
}
uri.WriteString(fmt.Sprintf("%v", params[n]))