diff --git a/app/server/rest_server.go b/app/server/rest_server.go index bc5a34f..d2bad60 100644 --- a/app/server/rest_server.go +++ b/app/server/rest_server.go @@ -7,8 +7,8 @@ import ( "net/http" "time" - "github.com/go-chi/chi" - "github.com/go-chi/chi/middleware" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/render" log "github.com/go-pkgz/lgr" "github.com/go-pkgz/rest" diff --git a/go.mod b/go.mod index 4394a2d..06d5eaa 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23 require ( github.com/fatih/color v1.18.0 github.com/fsouza/go-dockerclient v1.12.0 - github.com/go-chi/chi v4.0.2+incompatible + github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/render v1.0.3 github.com/go-pkgz/lgr v0.11.1 github.com/go-pkgz/mongo/v2 v2.2.1 @@ -56,7 +56,6 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect golang.org/x/crypto v0.29.0 // indirect - golang.org/x/net v0.31.0 // indirect golang.org/x/sync v0.9.0 // indirect golang.org/x/sys v0.27.0 // indirect golang.org/x/text v0.20.0 // indirect diff --git a/go.sum b/go.sum index 0f542d2..0482abf 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fsouza/go-dockerclient v1.12.0 h1:S2f2crEUbBNCFiF06kR/GvioEB8EMsb3Td/bpawD+aU= github.com/fsouza/go-dockerclient v1.12.0/go.mod h1:YWUtjg8japrqD/80L98nTtCoxQFp5B5wrSsnyeB5lFo= -github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= -github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-pkgz/lgr v0.11.1 h1:hXFhZcznehI6imLhEa379oMOKFz7TQUmisAqb3oLOSM= @@ -124,8 +124,6 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/vendor/github.com/go-chi/chi/.travis.yml b/vendor/github.com/go-chi/chi/.travis.yml deleted file mode 100644 index de3287e..0000000 --- a/vendor/github.com/go-chi/chi/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -language: go - -go: - - 1.10.x - - 1.11.x - - 1.12.x - -script: - - go get -d -t ./... - - go vet ./... - - go test ./... - - > - go_version=$(go version); - if [ ${go_version:13:4} = "1.12" ]; then - go get -u golang.org/x/tools/cmd/goimports; - goimports -d -e ./ | grep '.*' && { echo; echo "Aborting due to non-empty goimports output."; exit 1; } || :; - fi - diff --git a/vendor/github.com/go-chi/chi/CHANGELOG.md b/vendor/github.com/go-chi/chi/CHANGELOG.md deleted file mode 100644 index d03e40c..0000000 --- a/vendor/github.com/go-chi/chi/CHANGELOG.md +++ /dev/null @@ -1,139 +0,0 @@ -# Changelog - -## v4.0.0 (2019-01-10) - -- chi v4 requires Go 1.10.3+ (or Go 1.9.7+) - we have deprecated support for Go 1.7 and 1.8 -- router: respond with 404 on router with no routes (#362) -- router: additional check to ensure wildcard is at the end of a url pattern (#333) -- middleware: deprecate use of http.CloseNotifier (#347) -- middleware: fix RedirectSlashes to include query params on redirect (#334) -- History of changes: see https://github.com/go-chi/chi/compare/v3.3.4...v4.0.0 - - -## v3.3.4 (2019-01-07) - -- Minor middleware improvements. No changes to core library/router. Moving v3 into its -- own branch as a version of chi for Go 1.7, 1.8, 1.9, 1.10, 1.11 -- History of changes: see https://github.com/go-chi/chi/compare/v3.3.3...v3.3.4 - - -## v3.3.3 (2018-08-27) - -- Minor release -- See https://github.com/go-chi/chi/compare/v3.3.2...v3.3.3 - - -## v3.3.2 (2017-12-22) - -- Support to route trailing slashes on mounted sub-routers (#281) -- middleware: new `ContentCharset` to check matching charsets. Thank you - @csucu for your community contribution! - - -## v3.3.1 (2017-11-20) - -- middleware: new `AllowContentType` handler for explicit whitelist of accepted request Content-Types -- middleware: new `SetHeader` handler for short-hand middleware to set a response header key/value -- Minor bug fixes - - -## v3.3.0 (2017-10-10) - -- New chi.RegisterMethod(method) to add support for custom HTTP methods, see _examples/custom-method for usage -- Deprecated LINK and UNLINK methods from the default list, please use `chi.RegisterMethod("LINK")` and `chi.RegisterMethod("UNLINK")` in an `init()` function - - -## v3.2.1 (2017-08-31) - -- Add new `Match(rctx *Context, method, path string) bool` method to `Routes` interface - and `Mux`. Match searches the mux's routing tree for a handler that matches the method/path -- Add new `RouteMethod` to `*Context` -- Add new `Routes` pointer to `*Context` -- Add new `middleware.GetHead` to route missing HEAD requests to GET handler -- Updated benchmarks (see README) - - -## v3.1.5 (2017-08-02) - -- Setup golint and go vet for the project -- As per golint, we've redefined `func ServerBaseContext(h http.Handler, baseCtx context.Context) http.Handler` - to `func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler` - - -## v3.1.0 (2017-07-10) - -- Fix a few minor issues after v3 release -- Move `docgen` sub-pkg to https://github.com/go-chi/docgen -- Move `render` sub-pkg to https://github.com/go-chi/render -- Add new `URLFormat` handler to chi/middleware sub-pkg to make working with url mime - suffixes easier, ie. parsing `/articles/1.json` and `/articles/1.xml`. See comments in - https://github.com/go-chi/chi/blob/master/middleware/url_format.go for example usage. - - -## v3.0.0 (2017-06-21) - -- Major update to chi library with many exciting updates, but also some *breaking changes* -- URL parameter syntax changed from `/:id` to `/{id}` for even more flexible routing, such as - `/articles/{month}-{day}-{year}-{slug}`, `/articles/{id}`, and `/articles/{id}.{ext}` on the - same router -- Support for regexp for routing patterns, in the form of `/{paramKey:regExp}` for example: - `r.Get("/articles/{name:[a-z]+}", h)` and `chi.URLParam(r, "name")` -- Add `Method` and `MethodFunc` to `chi.Router` to allow routing definitions such as - `r.Method("GET", "/", h)` which provides a cleaner interface for custom handlers like - in `_examples/custom-handler` -- Deprecating `mux#FileServer` helper function. Instead, we encourage users to create their - own using file handler with the stdlib, see `_examples/fileserver` for an example -- Add support for LINK/UNLINK http methods via `r.Method()` and `r.MethodFunc()` -- Moved the chi project to its own organization, to allow chi-related community packages to - be easily discovered and supported, at: https://github.com/go-chi -- *NOTE:* please update your import paths to `"github.com/go-chi/chi"` -- *NOTE:* chi v2 is still available at https://github.com/go-chi/chi/tree/v2 - - -## v2.1.0 (2017-03-30) - -- Minor improvements and update to the chi core library -- Introduced a brand new `chi/render` sub-package to complete the story of building - APIs to offer a pattern for managing well-defined request / response payloads. Please - check out the updated `_examples/rest` example for how it works. -- Added `MethodNotAllowed(h http.HandlerFunc)` to chi.Router interface - - -## v2.0.0 (2017-01-06) - -- After many months of v2 being in an RC state with many companies and users running it in - production, the inclusion of some improvements to the middlewares, we are very pleased to - announce v2.0.0 of chi. - - -## v2.0.0-rc1 (2016-07-26) - -- Huge update! chi v2 is a large refactor targetting Go 1.7+. As of Go 1.7, the popular - community `"net/context"` package has been included in the standard library as `"context"` and - utilized by `"net/http"` and `http.Request` to managing deadlines, cancelation signals and other - request-scoped values. We're very excited about the new context addition and are proud to - introduce chi v2, a minimal and powerful routing package for building large HTTP services, - with zero external dependencies. Chi focuses on idiomatic design and encourages the use of - stdlib HTTP handlers and middlwares. -- chi v2 deprecates its `chi.Handler` interface and requires `http.Handler` or `http.HandlerFunc` -- chi v2 stores URL routing parameters and patterns in the standard request context: `r.Context()` -- chi v2 lower-level routing context is accessible by `chi.RouteContext(r.Context()) *chi.Context`, - which provides direct access to URL routing parameters, the routing path and the matching - routing patterns. -- Users upgrading from chi v1 to v2, need to: - 1. Update the old chi.Handler signature, `func(ctx context.Context, w http.ResponseWriter, r *http.Request)` to - the standard http.Handler: `func(w http.ResponseWriter, r *http.Request)` - 2. Use `chi.URLParam(r *http.Request, paramKey string) string` - or `URLParamFromCtx(ctx context.Context, paramKey string) string` to access a url parameter value - - -## v1.0.0 (2016-07-01) - -- Released chi v1 stable https://github.com/go-chi/chi/tree/v1.0.0 for Go 1.6 and older. - - -## v0.9.0 (2016-03-31) - -- Reuse context objects via sync.Pool for zero-allocation routing [#33](https://github.com/go-chi/chi/pull/33) -- BREAKING NOTE: due to subtle API changes, previously `chi.URLParams(ctx)["id"]` used to access url parameters - has changed to: `chi.URLParam(ctx, "id")` diff --git a/vendor/github.com/go-chi/chi/middleware/compress.go b/vendor/github.com/go-chi/chi/middleware/compress.go deleted file mode 100644 index d2876d4..0000000 --- a/vendor/github.com/go-chi/chi/middleware/compress.go +++ /dev/null @@ -1,275 +0,0 @@ -package middleware - -import ( - "bufio" - "compress/flate" - "compress/gzip" - "errors" - "io" - "net" - "net/http" - "strings" -) - -var encoders = map[string]EncoderFunc{} - -var encodingPrecedence = []string{"br", "gzip", "deflate"} - -func init() { - // TODO: - // lzma: Opera. - // sdch: Chrome, Android. Gzip output + dictionary header. - // br: Brotli, see https://github.com/go-chi/chi/pull/326 - - // TODO: Exception for old MSIE browsers that can't handle non-HTML? - // https://zoompf.com/blog/2012/02/lose-the-wait-http-compression - SetEncoder("gzip", encoderGzip) - - // HTTP 1.1 "deflate" (RFC 2616) stands for DEFLATE data (RFC 1951) - // wrapped with zlib (RFC 1950). The zlib wrapper uses Adler-32 - // checksum compared to CRC-32 used in "gzip" and thus is faster. - // - // But.. some old browsers (MSIE, Safari 5.1) incorrectly expect - // raw DEFLATE data only, without the mentioned zlib wrapper. - // Because of this major confusion, most modern browsers try it - // both ways, first looking for zlib headers. - // Quote by Mark Adler: http://stackoverflow.com/a/9186091/385548 - // - // The list of browsers having problems is quite big, see: - // http://zoompf.com/blog/2012/02/lose-the-wait-http-compression - // https://web.archive.org/web/20120321182910/http://www.vervestudios.co/projects/compression-tests/results - // - // That's why we prefer gzip over deflate. It's just more reliable - // and not significantly slower than gzip. - SetEncoder("deflate", encoderDeflate) - - // NOTE: Not implemented, intentionally: - // case "compress": // LZW. Deprecated. - // case "bzip2": // Too slow on-the-fly. - // case "zopfli": // Too slow on-the-fly. - // case "xz": // Too slow on-the-fly. -} - -// An EncoderFunc is a function that wraps the provided ResponseWriter with a -// streaming compression algorithm and returns it. -// -// In case of failure, the function should return nil. -type EncoderFunc func(w http.ResponseWriter, level int) io.Writer - -// SetEncoder can be used to set the implementation of a compression algorithm. -// -// The encoding should be a standardised identifier. See: -// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding -// -// For example, add the Brotli algortithm: -// -// import brotli_enc "gopkg.in/kothar/brotli-go.v0/enc" -// -// middleware.SetEncoder("br", func(w http.ResponseWriter, level int) io.Writer { -// params := brotli_enc.NewBrotliParams() -// params.SetQuality(level) -// return brotli_enc.NewBrotliWriter(params, w) -// }) -func SetEncoder(encoding string, fn EncoderFunc) { - encoding = strings.ToLower(encoding) - if encoding == "" { - panic("the encoding can not be empty") - } - if fn == nil { - panic("attempted to set a nil encoder function") - } - encoders[encoding] = fn - - var e string - for _, v := range encodingPrecedence { - if v == encoding { - e = v - } - } - - if e == "" { - encodingPrecedence = append([]string{e}, encodingPrecedence...) - } -} - -var defaultContentTypes = map[string]struct{}{ - "text/html": {}, - "text/css": {}, - "text/plain": {}, - "text/javascript": {}, - "application/javascript": {}, - "application/x-javascript": {}, - "application/json": {}, - "application/atom+xml": {}, - "application/rss+xml": {}, - "image/svg+xml": {}, -} - -// DefaultCompress is a middleware that compresses response -// body of predefined content types to a data format based -// on Accept-Encoding request header. It uses a default -// compression level. -func DefaultCompress(next http.Handler) http.Handler { - return Compress(flate.DefaultCompression)(next) -} - -// Compress is a middleware that compresses response -// body of a given content types to a data format based -// on Accept-Encoding request header. It uses a given -// compression level. -// -// NOTE: make sure to set the Content-Type header on your response -// otherwise this middleware will not compress the response body. For ex, in -// your handler you should set w.Header().Set("Content-Type", http.DetectContentType(yourBody)) -// or set it manually. -func Compress(level int, types ...string) func(next http.Handler) http.Handler { - contentTypes := defaultContentTypes - if len(types) > 0 { - contentTypes = make(map[string]struct{}, len(types)) - for _, t := range types { - contentTypes[t] = struct{}{} - } - } - - return func(next http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - encoder, encoding := selectEncoder(r.Header) - - cw := &compressResponseWriter{ - ResponseWriter: w, - w: w, - contentTypes: contentTypes, - encoder: encoder, - encoding: encoding, - level: level, - } - defer cw.Close() - - next.ServeHTTP(cw, r) - } - - return http.HandlerFunc(fn) - } -} - -func selectEncoder(h http.Header) (EncoderFunc, string) { - header := h.Get("Accept-Encoding") - - // Parse the names of all accepted algorithms from the header. - accepted := strings.Split(strings.ToLower(header), ",") - - // Find supported encoder by accepted list by precedence - for _, name := range encodingPrecedence { - if fn, ok := encoders[name]; ok && matchAcceptEncoding(accepted, name) { - return fn, name - } - } - - // No encoder found to match the accepted encoding - return nil, "" -} - -func matchAcceptEncoding(accepted []string, encoding string) bool { - for _, v := range accepted { - if strings.Index(v, encoding) >= 0 { - return true - } - } - return false -} - -type compressResponseWriter struct { - http.ResponseWriter - w io.Writer - encoder EncoderFunc - encoding string - contentTypes map[string]struct{} - level int - wroteHeader bool -} - -func (cw *compressResponseWriter) WriteHeader(code int) { - if cw.wroteHeader { - return - } - cw.wroteHeader = true - defer cw.ResponseWriter.WriteHeader(code) - - // Already compressed data? - if cw.Header().Get("Content-Encoding") != "" { - return - } - - // Parse the first part of the Content-Type response header. - contentType := "" - parts := strings.Split(cw.Header().Get("Content-Type"), ";") - if len(parts) > 0 { - contentType = parts[0] - } - - // Is the content type compressable? - if _, ok := cw.contentTypes[contentType]; !ok { - return - } - - if cw.encoder != nil && cw.encoding != "" { - if wr := cw.encoder(cw.ResponseWriter, cw.level); wr != nil { - cw.w = wr - cw.Header().Set("Content-Encoding", cw.encoding) - - // The content-length after compression is unknown - cw.Header().Del("Content-Length") - } - } -} - -func (cw *compressResponseWriter) Write(p []byte) (int, error) { - if !cw.wroteHeader { - cw.WriteHeader(http.StatusOK) - } - - return cw.w.Write(p) -} - -func (cw *compressResponseWriter) Flush() { - if f, ok := cw.w.(http.Flusher); ok { - f.Flush() - } -} - -func (cw *compressResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { - if hj, ok := cw.w.(http.Hijacker); ok { - return hj.Hijack() - } - return nil, nil, errors.New("chi/middleware: http.Hijacker is unavailable on the writer") -} - -func (cw *compressResponseWriter) Push(target string, opts *http.PushOptions) error { - if ps, ok := cw.w.(http.Pusher); ok { - return ps.Push(target, opts) - } - return errors.New("chi/middleware: http.Pusher is unavailable on the writer") -} - -func (cw *compressResponseWriter) Close() error { - if c, ok := cw.w.(io.WriteCloser); ok { - return c.Close() - } - return errors.New("chi/middleware: io.WriteCloser is unavailable on the writer") -} - -func encoderGzip(w http.ResponseWriter, level int) io.Writer { - gw, err := gzip.NewWriterLevel(w, level) - if err != nil { - return nil - } - return gw -} - -func encoderDeflate(w http.ResponseWriter, level int) io.Writer { - dw, err := flate.NewWriter(w, level) - if err != nil { - return nil - } - return dw -} diff --git a/vendor/github.com/go-chi/chi/middleware/profiler.go b/vendor/github.com/go-chi/chi/middleware/profiler.go deleted file mode 100644 index 1d44b82..0000000 --- a/vendor/github.com/go-chi/chi/middleware/profiler.go +++ /dev/null @@ -1,55 +0,0 @@ -package middleware - -import ( - "expvar" - "fmt" - "net/http" - "net/http/pprof" - - "github.com/go-chi/chi" -) - -// Profiler is a convenient subrouter used for mounting net/http/pprof. ie. -// -// func MyService() http.Handler { -// r := chi.NewRouter() -// // ..middlewares -// r.Mount("/debug", middleware.Profiler()) -// // ..routes -// return r -// } -func Profiler() http.Handler { - r := chi.NewRouter() - r.Use(NoCache) - - r.Get("/", func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, r.RequestURI+"/pprof/", 301) - }) - r.HandleFunc("/pprof", func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, r.RequestURI+"/", 301) - }) - - r.HandleFunc("/pprof/*", pprof.Index) - r.HandleFunc("/pprof/cmdline", pprof.Cmdline) - r.HandleFunc("/pprof/profile", pprof.Profile) - r.HandleFunc("/pprof/symbol", pprof.Symbol) - r.HandleFunc("/pprof/trace", pprof.Trace) - r.HandleFunc("/vars", expVars) - - return r -} - -// Replicated from expvar.go as not public. -func expVars(w http.ResponseWriter, r *http.Request) { - first := true - w.Header().Set("Content-Type", "application/json") - fmt.Fprintf(w, "{\n") - expvar.Do(func(kv expvar.KeyValue) { - if !first { - fmt.Fprintf(w, ",\n") - } - first = false - fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) - }) - fmt.Fprintf(w, "\n}\n") -} diff --git a/vendor/github.com/go-chi/chi/middleware/recoverer.go b/vendor/github.com/go-chi/chi/middleware/recoverer.go deleted file mode 100644 index 57fc3eb..0000000 --- a/vendor/github.com/go-chi/chi/middleware/recoverer.go +++ /dev/null @@ -1,39 +0,0 @@ -package middleware - -// The original work was derived from Goji's middleware, source: -// https://github.com/zenazn/goji/tree/master/web/middleware - -import ( - "fmt" - "net/http" - "os" - "runtime/debug" -) - -// Recoverer is a middleware that recovers from panics, logs the panic (and a -// backtrace), and returns a HTTP 500 (Internal Server Error) status if -// possible. Recoverer prints a request ID if one is provided. -// -// Alternatively, look at https://github.com/pressly/lg middleware pkgs. -func Recoverer(next http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - defer func() { - if rvr := recover(); rvr != nil { - - logEntry := GetLogEntry(r) - if logEntry != nil { - logEntry.Panic(rvr, debug.Stack()) - } else { - fmt.Fprintf(os.Stderr, "Panic: %+v\n", rvr) - debug.PrintStack() - } - - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - } - }() - - next.ServeHTTP(w, r) - } - - return http.HandlerFunc(fn) -} diff --git a/vendor/github.com/go-chi/chi/middleware/throttle.go b/vendor/github.com/go-chi/chi/middleware/throttle.go deleted file mode 100644 index d935e2c..0000000 --- a/vendor/github.com/go-chi/chi/middleware/throttle.go +++ /dev/null @@ -1,101 +0,0 @@ -package middleware - -import ( - "net/http" - "time" -) - -const ( - errCapacityExceeded = "Server capacity exceeded." - errTimedOut = "Timed out while waiting for a pending request to complete." - errContextCanceled = "Context was canceled." -) - -var ( - defaultBacklogTimeout = time.Second * 60 -) - -// Throttle is a middleware that limits number of currently processed requests -// at a time. -func Throttle(limit int) func(http.Handler) http.Handler { - return ThrottleBacklog(limit, 0, defaultBacklogTimeout) -} - -// ThrottleBacklog is a middleware that limits number of currently processed -// requests at a time and provides a backlog for holding a finite number of -// pending requests. -func ThrottleBacklog(limit int, backlogLimit int, backlogTimeout time.Duration) func(http.Handler) http.Handler { - if limit < 1 { - panic("chi/middleware: Throttle expects limit > 0") - } - - if backlogLimit < 0 { - panic("chi/middleware: Throttle expects backlogLimit to be positive") - } - - t := throttler{ - tokens: make(chan token, limit), - backlogTokens: make(chan token, limit+backlogLimit), - backlogTimeout: backlogTimeout, - } - - // Filling tokens. - for i := 0; i < limit+backlogLimit; i++ { - if i < limit { - t.tokens <- token{} - } - t.backlogTokens <- token{} - } - - fn := func(h http.Handler) http.Handler { - t.h = h - return &t - } - - return fn -} - -// token represents a request that is being processed. -type token struct{} - -// throttler limits number of currently processed requests at a time. -type throttler struct { - h http.Handler - tokens chan token - backlogTokens chan token - backlogTimeout time.Duration -} - -// ServeHTTP is the primary throttler request handler -func (t *throttler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - select { - case <-ctx.Done(): - http.Error(w, errContextCanceled, http.StatusServiceUnavailable) - return - case btok := <-t.backlogTokens: - timer := time.NewTimer(t.backlogTimeout) - - defer func() { - t.backlogTokens <- btok - }() - - select { - case <-timer.C: - http.Error(w, errTimedOut, http.StatusServiceUnavailable) - return - case <-ctx.Done(): - http.Error(w, errContextCanceled, http.StatusServiceUnavailable) - return - case tok := <-t.tokens: - defer func() { - t.tokens <- tok - }() - t.h.ServeHTTP(w, r) - } - return - default: - http.Error(w, errCapacityExceeded, http.StatusServiceUnavailable) - return - } -} diff --git a/vendor/github.com/go-chi/chi/.gitignore b/vendor/github.com/go-chi/chi/v5/.gitignore similarity index 100% rename from vendor/github.com/go-chi/chi/.gitignore rename to vendor/github.com/go-chi/chi/v5/.gitignore diff --git a/vendor/github.com/go-chi/chi/v5/CHANGELOG.md b/vendor/github.com/go-chi/chi/v5/CHANGELOG.md new file mode 100644 index 0000000..25b45b9 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/CHANGELOG.md @@ -0,0 +1,341 @@ +# Changelog + +## v5.0.12 (2024-02-16) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.11...v5.0.12 + + +## v5.0.11 (2023-12-19) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.10...v5.0.11 + + +## v5.0.10 (2023-07-13) + +- Fixed small edge case in tests of v5.0.9 for older Go versions +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.9...v5.0.10 + + +## v5.0.9 (2023-07-13) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.8...v5.0.9 + + +## v5.0.8 (2022-12-07) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.7...v5.0.8 + + +## v5.0.7 (2021-11-18) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.6...v5.0.7 + + +## v5.0.6 (2021-11-15) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.5...v5.0.6 + + +## v5.0.5 (2021-10-27) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.4...v5.0.5 + + +## v5.0.4 (2021-08-29) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.3...v5.0.4 + + +## v5.0.3 (2021-04-29) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.2...v5.0.3 + + +## v5.0.2 (2021-03-25) + +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.1...v5.0.2 + + +## v5.0.1 (2021-03-10) + +- Small improvements +- History of changes: see https://github.com/go-chi/chi/compare/v5.0.0...v5.0.1 + + +## v5.0.0 (2021-02-27) + +- chi v5, `github.com/go-chi/chi/v5` introduces the adoption of Go's SIV to adhere to the current state-of-the-tools in Go. +- chi v1.5.x did not work out as planned, as the Go tooling is too powerful and chi's adoption is too wide. + The most responsible thing to do for everyone's benefit is to just release v5 with SIV, so I present to you all, + chi v5 at `github.com/go-chi/chi/v5`. I hope someday the developer experience and ergonomics I've been seeking + will still come to fruition in some form, see https://github.com/golang/go/issues/44550 +- History of changes: see https://github.com/go-chi/chi/compare/v1.5.4...v5.0.0 + + +## v1.5.4 (2021-02-27) + +- Undo prior retraction in v1.5.3 as we prepare for v5.0.0 release +- History of changes: see https://github.com/go-chi/chi/compare/v1.5.3...v1.5.4 + + +## v1.5.3 (2021-02-21) + +- Update go.mod to go 1.16 with new retract directive marking all versions without prior go.mod support +- History of changes: see https://github.com/go-chi/chi/compare/v1.5.2...v1.5.3 + + +## v1.5.2 (2021-02-10) + +- Reverting allocation optimization as a precaution as go test -race fails. +- Minor improvements, see history below +- History of changes: see https://github.com/go-chi/chi/compare/v1.5.1...v1.5.2 + + +## v1.5.1 (2020-12-06) + +- Performance improvement: removing 1 allocation by foregoing context.WithValue, thank you @bouk for + your contribution (https://github.com/go-chi/chi/pull/555). Note: new benchmarks posted in README. +- `middleware.CleanPath`: new middleware that clean's request path of double slashes +- deprecate & remove `chi.ServerBaseContext` in favour of stdlib `http.Server#BaseContext` +- plus other tiny improvements, see full commit history below +- History of changes: see https://github.com/go-chi/chi/compare/v4.1.2...v1.5.1 + + +## v1.5.0 (2020-11-12) - now with go.mod support + +`chi` dates back to 2016 with it's original implementation as one of the first routers to adopt the newly introduced +context.Context api to the stdlib -- set out to design a router that is faster, more modular and simpler than anything +else out there -- while not introducing any custom handler types or dependencies. Today, `chi` still has zero dependencies, +and in many ways is future proofed from changes, given it's minimal nature. Between versions, chi's iterations have been very +incremental, with the architecture and api being the same today as it was originally designed in 2016. For this reason it +makes chi a pretty easy project to maintain, as well thanks to the many amazing community contributions over the years +to who all help make chi better (total of 86 contributors to date -- thanks all!). + +Chi has been a labour of love, art and engineering, with the goals to offer beautiful ergonomics, flexibility, performance +and simplicity when building HTTP services with Go. I've strived to keep the router very minimal in surface area / code size, +and always improving the code wherever possible -- and as of today the `chi` package is just 1082 lines of code (not counting +middlewares, which are all optional). As well, I don't have the exact metrics, but from my analysis and email exchanges from +companies and developers, chi is used by thousands of projects around the world -- thank you all as there is no better form of +joy for me than to have art I had started be helpful and enjoyed by others. And of course I use chi in all of my own projects too :) + +For me, the aesthetics of chi's code and usage are very important. With the introduction of Go's module support +(which I'm a big fan of), chi's past versioning scheme choice to v2, v3 and v4 would mean I'd require the import path +of "github.com/go-chi/chi/v4", leading to the lengthy discussion at https://github.com/go-chi/chi/issues/462. +Haha, to some, you may be scratching your head why I've spent > 1 year stalling to adopt "/vXX" convention in the import +path -- which isn't horrible in general -- but for chi, I'm unable to accept it as I strive for perfection in it's API design, +aesthetics and simplicity. It just doesn't feel good to me given chi's simple nature -- I do not foresee a "v5" or "v6", +and upgrading between versions in the future will also be just incremental. + +I do understand versioning is a part of the API design as well, which is why the solution for a while has been to "do nothing", +as Go supports both old and new import paths with/out go.mod. However, now that Go module support has had time to iron out kinks and +is adopted everywhere, it's time for chi to get with the times. Luckily, I've discovered a path forward that will make me happy, +while also not breaking anyone's app who adopted a prior versioning from tags in v2/v3/v4. I've made an experimental release of +v1.5.0 with go.mod silently, and tested it with new and old projects, to ensure the developer experience is preserved, and it's +largely unnoticed. Fortunately, Go's toolchain will check the tags of a repo and consider the "latest" tag the one with go.mod. +However, you can still request a specific older tag such as v4.1.2, and everything will "just work". But new users can just +`go get github.com/go-chi/chi` or `go get github.com/go-chi/chi@latest` and they will get the latest version which contains +go.mod support, which is v1.5.0+. `chi` will not change very much over the years, just like it hasn't changed much from 4 years ago. +Therefore, we will stay on v1.x from here on, starting from v1.5.0. Any breaking changes will bump a "minor" release and +backwards-compatible improvements/fixes will bump a "tiny" release. + +For existing projects who want to upgrade to the latest go.mod version, run: `go get -u github.com/go-chi/chi@v1.5.0`, +which will get you on the go.mod version line (as Go's mod cache may still remember v4.x). Brand new systems can run +`go get -u github.com/go-chi/chi` or `go get -u github.com/go-chi/chi@latest` to install chi, which will install v1.5.0+ +built with go.mod support. + +My apologies to the developers who will disagree with the decisions above, but, hope you'll try it and see it's a very +minor request which is backwards compatible and won't break your existing installations. + +Cheers all, happy coding! + + +--- + + +## v4.1.2 (2020-06-02) + +- fix that handles MethodNotAllowed with path variables, thank you @caseyhadden for your contribution +- fix to replace nested wildcards correctly in RoutePattern, thank you @@unmultimedio for your contribution +- History of changes: see https://github.com/go-chi/chi/compare/v4.1.1...v4.1.2 + + +## v4.1.1 (2020-04-16) + +- fix for issue https://github.com/go-chi/chi/issues/411 which allows for overlapping regexp + route to the correct handler through a recursive tree search, thanks to @Jahaja for the PR/fix! +- new middleware.RouteHeaders as a simple router for request headers with wildcard support +- History of changes: see https://github.com/go-chi/chi/compare/v4.1.0...v4.1.1 + + +## v4.1.0 (2020-04-1) + +- middleware.LogEntry: Write method on interface now passes the response header + and an extra interface type useful for custom logger implementations. +- middleware.WrapResponseWriter: minor fix +- middleware.Recoverer: a bit prettier +- History of changes: see https://github.com/go-chi/chi/compare/v4.0.4...v4.1.0 + +## v4.0.4 (2020-03-24) + +- middleware.Recoverer: new pretty stack trace printing (https://github.com/go-chi/chi/pull/496) +- a few minor improvements and fixes +- History of changes: see https://github.com/go-chi/chi/compare/v4.0.3...v4.0.4 + + +## v4.0.3 (2020-01-09) + +- core: fix regexp routing to include default value when param is not matched +- middleware: rewrite of middleware.Compress +- middleware: suppress http.ErrAbortHandler in middleware.Recoverer +- History of changes: see https://github.com/go-chi/chi/compare/v4.0.2...v4.0.3 + + +## v4.0.2 (2019-02-26) + +- Minor fixes +- History of changes: see https://github.com/go-chi/chi/compare/v4.0.1...v4.0.2 + + +## v4.0.1 (2019-01-21) + +- Fixes issue with compress middleware: #382 #385 +- History of changes: see https://github.com/go-chi/chi/compare/v4.0.0...v4.0.1 + + +## v4.0.0 (2019-01-10) + +- chi v4 requires Go 1.10.3+ (or Go 1.9.7+) - we have deprecated support for Go 1.7 and 1.8 +- router: respond with 404 on router with no routes (#362) +- router: additional check to ensure wildcard is at the end of a url pattern (#333) +- middleware: deprecate use of http.CloseNotifier (#347) +- middleware: fix RedirectSlashes to include query params on redirect (#334) +- History of changes: see https://github.com/go-chi/chi/compare/v3.3.4...v4.0.0 + + +## v3.3.4 (2019-01-07) + +- Minor middleware improvements. No changes to core library/router. Moving v3 into its +- own branch as a version of chi for Go 1.7, 1.8, 1.9, 1.10, 1.11 +- History of changes: see https://github.com/go-chi/chi/compare/v3.3.3...v3.3.4 + + +## v3.3.3 (2018-08-27) + +- Minor release +- See https://github.com/go-chi/chi/compare/v3.3.2...v3.3.3 + + +## v3.3.2 (2017-12-22) + +- Support to route trailing slashes on mounted sub-routers (#281) +- middleware: new `ContentCharset` to check matching charsets. Thank you + @csucu for your community contribution! + + +## v3.3.1 (2017-11-20) + +- middleware: new `AllowContentType` handler for explicit whitelist of accepted request Content-Types +- middleware: new `SetHeader` handler for short-hand middleware to set a response header key/value +- Minor bug fixes + + +## v3.3.0 (2017-10-10) + +- New chi.RegisterMethod(method) to add support for custom HTTP methods, see _examples/custom-method for usage +- Deprecated LINK and UNLINK methods from the default list, please use `chi.RegisterMethod("LINK")` and `chi.RegisterMethod("UNLINK")` in an `init()` function + + +## v3.2.1 (2017-08-31) + +- Add new `Match(rctx *Context, method, path string) bool` method to `Routes` interface + and `Mux`. Match searches the mux's routing tree for a handler that matches the method/path +- Add new `RouteMethod` to `*Context` +- Add new `Routes` pointer to `*Context` +- Add new `middleware.GetHead` to route missing HEAD requests to GET handler +- Updated benchmarks (see README) + + +## v3.1.5 (2017-08-02) + +- Setup golint and go vet for the project +- As per golint, we've redefined `func ServerBaseContext(h http.Handler, baseCtx context.Context) http.Handler` + to `func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler` + + +## v3.1.0 (2017-07-10) + +- Fix a few minor issues after v3 release +- Move `docgen` sub-pkg to https://github.com/go-chi/docgen +- Move `render` sub-pkg to https://github.com/go-chi/render +- Add new `URLFormat` handler to chi/middleware sub-pkg to make working with url mime + suffixes easier, ie. parsing `/articles/1.json` and `/articles/1.xml`. See comments in + https://github.com/go-chi/chi/blob/master/middleware/url_format.go for example usage. + + +## v3.0.0 (2017-06-21) + +- Major update to chi library with many exciting updates, but also some *breaking changes* +- URL parameter syntax changed from `/:id` to `/{id}` for even more flexible routing, such as + `/articles/{month}-{day}-{year}-{slug}`, `/articles/{id}`, and `/articles/{id}.{ext}` on the + same router +- Support for regexp for routing patterns, in the form of `/{paramKey:regExp}` for example: + `r.Get("/articles/{name:[a-z]+}", h)` and `chi.URLParam(r, "name")` +- Add `Method` and `MethodFunc` to `chi.Router` to allow routing definitions such as + `r.Method("GET", "/", h)` which provides a cleaner interface for custom handlers like + in `_examples/custom-handler` +- Deprecating `mux#FileServer` helper function. Instead, we encourage users to create their + own using file handler with the stdlib, see `_examples/fileserver` for an example +- Add support for LINK/UNLINK http methods via `r.Method()` and `r.MethodFunc()` +- Moved the chi project to its own organization, to allow chi-related community packages to + be easily discovered and supported, at: https://github.com/go-chi +- *NOTE:* please update your import paths to `"github.com/go-chi/chi"` +- *NOTE:* chi v2 is still available at https://github.com/go-chi/chi/tree/v2 + + +## v2.1.0 (2017-03-30) + +- Minor improvements and update to the chi core library +- Introduced a brand new `chi/render` sub-package to complete the story of building + APIs to offer a pattern for managing well-defined request / response payloads. Please + check out the updated `_examples/rest` example for how it works. +- Added `MethodNotAllowed(h http.HandlerFunc)` to chi.Router interface + + +## v2.0.0 (2017-01-06) + +- After many months of v2 being in an RC state with many companies and users running it in + production, the inclusion of some improvements to the middlewares, we are very pleased to + announce v2.0.0 of chi. + + +## v2.0.0-rc1 (2016-07-26) + +- Huge update! chi v2 is a large refactor targeting Go 1.7+. As of Go 1.7, the popular + community `"net/context"` package has been included in the standard library as `"context"` and + utilized by `"net/http"` and `http.Request` to managing deadlines, cancelation signals and other + request-scoped values. We're very excited about the new context addition and are proud to + introduce chi v2, a minimal and powerful routing package for building large HTTP services, + with zero external dependencies. Chi focuses on idiomatic design and encourages the use of + stdlib HTTP handlers and middlewares. +- chi v2 deprecates its `chi.Handler` interface and requires `http.Handler` or `http.HandlerFunc` +- chi v2 stores URL routing parameters and patterns in the standard request context: `r.Context()` +- chi v2 lower-level routing context is accessible by `chi.RouteContext(r.Context()) *chi.Context`, + which provides direct access to URL routing parameters, the routing path and the matching + routing patterns. +- Users upgrading from chi v1 to v2, need to: + 1. Update the old chi.Handler signature, `func(ctx context.Context, w http.ResponseWriter, r *http.Request)` to + the standard http.Handler: `func(w http.ResponseWriter, r *http.Request)` + 2. Use `chi.URLParam(r *http.Request, paramKey string) string` + or `URLParamFromCtx(ctx context.Context, paramKey string) string` to access a url parameter value + + +## v1.0.0 (2016-07-01) + +- Released chi v1 stable https://github.com/go-chi/chi/tree/v1.0.0 for Go 1.6 and older. + + +## v0.9.0 (2016-03-31) + +- Reuse context objects via sync.Pool for zero-allocation routing [#33](https://github.com/go-chi/chi/pull/33) +- BREAKING NOTE: due to subtle API changes, previously `chi.URLParams(ctx)["id"]` used to access url parameters + has changed to: `chi.URLParam(ctx, "id")` diff --git a/vendor/github.com/go-chi/chi/CONTRIBUTING.md b/vendor/github.com/go-chi/chi/v5/CONTRIBUTING.md similarity index 58% rename from vendor/github.com/go-chi/chi/CONTRIBUTING.md rename to vendor/github.com/go-chi/chi/v5/CONTRIBUTING.md index c0ac2df..b4a6268 100644 --- a/vendor/github.com/go-chi/chi/CONTRIBUTING.md +++ b/vendor/github.com/go-chi/chi/v5/CONTRIBUTING.md @@ -14,7 +14,7 @@ A typical workflow is: -1. [Fork the repository.][fork] [This tip maybe also helpful.][go-fork-tip] +1. [Fork the repository.][fork] 2. [Create a topic branch.][branch] 3. Add tests for your change. 4. Run `go test`. If your tests pass, return to the step 3. @@ -24,8 +24,8 @@ A typical workflow is: 8. [Submit a pull request.][pull-req] [go-install]: https://golang.org/doc/install -[go-fork-tip]: http://blog.campoy.cat/2014/03/github-and-go-forking-pull-requests-and.html -[fork]: https://help.github.com/articles/fork-a-repo -[branch]: http://learn.github.com/p/branching.html -[git-help]: https://guides.github.com -[pull-req]: https://help.github.com/articles/using-pull-requests +[fork]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo +[branch]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-branches +[git-help]: https://docs.github.com/en +[pull-req]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests + diff --git a/vendor/github.com/go-chi/chi/LICENSE b/vendor/github.com/go-chi/chi/v5/LICENSE similarity index 100% rename from vendor/github.com/go-chi/chi/LICENSE rename to vendor/github.com/go-chi/chi/v5/LICENSE diff --git a/vendor/github.com/go-chi/chi/v5/Makefile b/vendor/github.com/go-chi/chi/v5/Makefile new file mode 100644 index 0000000..e0f18c7 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/Makefile @@ -0,0 +1,22 @@ +.PHONY: all +all: + @echo "**********************************************************" + @echo "** chi build tool **" + @echo "**********************************************************" + + +.PHONY: test +test: + go clean -testcache && $(MAKE) test-router && $(MAKE) test-middleware + +.PHONY: test-router +test-router: + go test -race -v . + +.PHONY: test-middleware +test-middleware: + go test -race -v ./middleware + +.PHONY: docs +docs: + npx docsify-cli serve ./docs diff --git a/vendor/github.com/go-chi/chi/README.md b/vendor/github.com/go-chi/chi/v5/README.md similarity index 57% rename from vendor/github.com/go-chi/chi/README.md rename to vendor/github.com/go-chi/chi/v5/README.md index d36d4db..21cbb0f 100644 --- a/vendor/github.com/go-chi/chi/README.md +++ b/vendor/github.com/go-chi/chi/v5/README.md @@ -1,7 +1,7 @@ # chi -[![GoDoc Widget]][GoDoc] [![Travis Widget]][Travis] +[![GoDoc Widget]][GoDoc] `chi` is a lightweight, idiomatic and composable router for building Go HTTP services. It's especially good at helping you write large REST API services that are kept maintainable as your @@ -15,11 +15,12 @@ public API service, which in turn powers all of our client-side applications. The key considerations of chi's design are: project structure, maintainability, standard http handlers (stdlib-only), developer productivity, and deconstructing a large system into many small parts. The core router `github.com/go-chi/chi` is quite small (less than 1000 LOC), but we've also -included some useful/optional subpackages: [middleware](/middleware), [render](https://github.com/go-chi/render) and [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too! +included some useful/optional subpackages: [middleware](/middleware), [render](https://github.com/go-chi/render) +and [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too! ## Install -`go get -u github.com/go-chi/chi` +`go get -u github.com/go-chi/chi/v5` ## Features @@ -27,10 +28,11 @@ included some useful/optional subpackages: [middleware](/middleware), [render](h * **Lightweight** - cloc'd in ~1000 LOC for the chi router * **Fast** - yes, see [benchmarks](#benchmarks) * **100% compatible with net/http** - use any http or middleware pkg in the ecosystem that is also compatible with `net/http` -* **Designed for modular/composable APIs** - middlewares, inline middlewares, route groups and subrouter mounting -* **Context control** - built on new `context` package, providing value chaining, cancelations and timeouts -* **Robust** - in production at Pressly, CloudFlare, Heroku, 99Designs, and many others (see [discussion](https://github.com/go-chi/chi/issues/91)) +* **Designed for modular/composable APIs** - middlewares, inline middlewares, route groups and sub-router mounting +* **Context control** - built on new `context` package, providing value chaining, cancellations and timeouts +* **Robust** - in production at Pressly, Cloudflare, Heroku, 99Designs, and many others (see [discussion](https://github.com/go-chi/chi/issues/91)) * **Doc generation** - `docgen` auto-generates routing documentation from your source to JSON or Markdown +* **Go.mod support** - as of v5, go.mod support (see [CHANGELOG](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)) * **No external dependencies** - plain ol' Go stdlib + net/http @@ -46,11 +48,14 @@ package main import ( "net/http" - "github.com/go-chi/chi" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" ) func main() { r := chi.NewRouter() + r.Use(middleware.Logger) r.Get("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("welcome")) }) @@ -71,8 +76,8 @@ above, they will show you all the features of chi and serve as a good form of do import ( //... "context" - "github.com/go-chi/chi" - "github.com/go-chi/chi/middleware" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" ) func main() { @@ -165,7 +170,7 @@ func AdminOnly(next http.Handler) http.Handler { ``` -## Router design +## Router interface chi's router is based on a kind of [Patricia Radix trie](https://en.wikipedia.org/wiki/Radix_tree). The router is fully compatible with `net/http`. @@ -179,7 +184,7 @@ type Router interface { http.Handler Routes - // Use appends one of more middlewares onto the Router stack. + // Use appends one or more middlewares onto the Router stack. Use(middlewares ...func(http.Handler) http.Handler) // With adds inline middlewares for an endpoint handler. @@ -254,15 +259,24 @@ about them, which means the router and all the tooling is designed to be compati friendly with any middleware in the community. This offers much better extensibility and reuse of packages and is at the heart of chi's purpose. -Here is an example of a standard net/http middleware handler using the new request context -available in Go. This middleware sets a hypothetical user identifier on the request +Here is an example of a standard net/http middleware where we assign a context key `"user"` +the value of `"123"`. This middleware sets a hypothetical user identifier on the request context and calls the next handler in the chain. ```go // HTTP middleware setting a value on the request context func MyMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // create new context from `r` request context, and assign key `"user"` + // to value of `"123"` ctx := context.WithValue(r.Context(), "user", "123") + + // call the next handler in the chain, passing the response writer and + // the updated request object with the new context value. + // + // note: context.Context values are nested, so any previously set + // values will be accessible as well, and the new `"user"` key + // will be accessible from this point forward. next.ServeHTTP(w, r.WithContext(ctx)) }) } @@ -278,7 +292,11 @@ the user sending an authenticated request, validated+set by a previous middlewar ```go // HTTP handler accessing data from the request context. func MyRequestHandler(w http.ResponseWriter, r *http.Request) { + // here we read from the request context and fetch out `"user"` key set in + // the MyMiddleware example above. user := r.Context().Value("user").(string) + + // respond to the client w.Write([]byte(fmt.Sprintf("hi %s", user))) } ``` @@ -293,11 +311,15 @@ are able to access the same information. ```go // HTTP handler accessing the url routing parameters. func MyRequestHandler(w http.ResponseWriter, r *http.Request) { - userID := chi.URLParam(r, "userID") // from a route like /users/{userID} + // fetch the url parameter `"userID"` from the request of a matching + // routing pattern. An example routing pattern could be: /users/{userID} + userID := chi.URLParam(r, "userID") + // fetch `"key"` from the request context ctx := r.Context() key := ctx.Value("key").(string) + // respond to the client w.Write([]byte(fmt.Sprintf("hi %v, %v", userID, key))) } ``` @@ -311,29 +333,74 @@ with `net/http` can be used with chi's mux. ### Core middlewares ------------------------------------------------------------------------------------------------------------ -| chi/middleware Handler | description | -|:----------------------|:--------------------------------------------------------------------------------- -| AllowContentType | Explicit whitelist of accepted request Content-Types | -| Compress | Gzip compression for clients that accept compressed responses | -| GetHead | Automatically route undefined HEAD requests to GET handlers | -| Heartbeat | Monitoring endpoint to check the servers pulse | -| Logger | Logs the start and end of each request with the elapsed processing time | -| NoCache | Sets response headers to prevent clients from caching | -| Profiler | Easily attach net/http/pprof to your routers | -| RealIP | Sets a http.Request's RemoteAddr to either X-Forwarded-For or X-Real-IP | -| Recoverer | Gracefully absorb panics and prints the stack trace | -| RequestID | Injects a request ID into the context of each request | -| RedirectSlashes | Redirect slashes on routing paths | -| SetHeader | Short-hand middleware to set a response header key/value | -| StripSlashes | Strip slashes on routing paths | -| Throttle | Puts a ceiling on the number of concurrent requests | -| Timeout | Signals to the request context when the timeout deadline is reached | -| URLFormat | Parse extension from url and put it on request context | -| WithValue | Short-hand middleware to set a key/value on the request context | ------------------------------------------------------------------------------------------------------------ - -### Auxiliary middlewares & packages +---------------------------------------------------------------------------------------------------- +| chi/middleware Handler | description | +| :--------------------- | :---------------------------------------------------------------------- | +| [AllowContentEncoding] | Enforces a whitelist of request Content-Encoding headers | +| [AllowContentType] | Explicit whitelist of accepted request Content-Types | +| [BasicAuth] | Basic HTTP authentication | +| [Compress] | Gzip compression for clients that accept compressed responses | +| [ContentCharset] | Ensure charset for Content-Type request headers | +| [CleanPath] | Clean double slashes from request path | +| [GetHead] | Automatically route undefined HEAD requests to GET handlers | +| [Heartbeat] | Monitoring endpoint to check the servers pulse | +| [Logger] | Logs the start and end of each request with the elapsed processing time | +| [NoCache] | Sets response headers to prevent clients from caching | +| [Profiler] | Easily attach net/http/pprof to your routers | +| [RealIP] | Sets a http.Request's RemoteAddr to either X-Real-IP or X-Forwarded-For | +| [Recoverer] | Gracefully absorb panics and prints the stack trace | +| [RequestID] | Injects a request ID into the context of each request | +| [RedirectSlashes] | Redirect slashes on routing paths | +| [RouteHeaders] | Route handling for request headers | +| [SetHeader] | Short-hand middleware to set a response header key/value | +| [StripSlashes] | Strip slashes on routing paths | +| [Sunset] | Sunset set Deprecation/Sunset header to response | +| [Throttle] | Puts a ceiling on the number of concurrent requests | +| [Timeout] | Signals to the request context when the timeout deadline is reached | +| [URLFormat] | Parse extension from url and put it on request context | +| [WithValue] | Short-hand middleware to set a key/value on the request context | +---------------------------------------------------------------------------------------------------- + +[AllowContentEncoding]: https://pkg.go.dev/github.com/go-chi/chi/middleware#AllowContentEncoding +[AllowContentType]: https://pkg.go.dev/github.com/go-chi/chi/middleware#AllowContentType +[BasicAuth]: https://pkg.go.dev/github.com/go-chi/chi/middleware#BasicAuth +[Compress]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Compress +[ContentCharset]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ContentCharset +[CleanPath]: https://pkg.go.dev/github.com/go-chi/chi/middleware#CleanPath +[GetHead]: https://pkg.go.dev/github.com/go-chi/chi/middleware#GetHead +[GetReqID]: https://pkg.go.dev/github.com/go-chi/chi/middleware#GetReqID +[Heartbeat]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Heartbeat +[Logger]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Logger +[NoCache]: https://pkg.go.dev/github.com/go-chi/chi/middleware#NoCache +[Profiler]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Profiler +[RealIP]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RealIP +[Recoverer]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Recoverer +[RedirectSlashes]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RedirectSlashes +[RequestLogger]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RequestLogger +[RequestID]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RequestID +[RouteHeaders]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RouteHeaders +[SetHeader]: https://pkg.go.dev/github.com/go-chi/chi/middleware#SetHeader +[StripSlashes]: https://pkg.go.dev/github.com/go-chi/chi/middleware#StripSlashes +[Sunset]: https://pkg.go.dev/github.com/go-chi/chi/v5/middleware#Sunset +[Throttle]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Throttle +[ThrottleBacklog]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleBacklog +[ThrottleWithOpts]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleWithOpts +[Timeout]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Timeout +[URLFormat]: https://pkg.go.dev/github.com/go-chi/chi/middleware#URLFormat +[WithLogEntry]: https://pkg.go.dev/github.com/go-chi/chi/middleware#WithLogEntry +[WithValue]: https://pkg.go.dev/github.com/go-chi/chi/middleware#WithValue +[Compressor]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Compressor +[DefaultLogFormatter]: https://pkg.go.dev/github.com/go-chi/chi/middleware#DefaultLogFormatter +[EncoderFunc]: https://pkg.go.dev/github.com/go-chi/chi/middleware#EncoderFunc +[HeaderRoute]: https://pkg.go.dev/github.com/go-chi/chi/middleware#HeaderRoute +[HeaderRouter]: https://pkg.go.dev/github.com/go-chi/chi/middleware#HeaderRouter +[LogEntry]: https://pkg.go.dev/github.com/go-chi/chi/middleware#LogEntry +[LogFormatter]: https://pkg.go.dev/github.com/go-chi/chi/middleware#LogFormatter +[LoggerInterface]: https://pkg.go.dev/github.com/go-chi/chi/middleware#LoggerInterface +[ThrottleOpts]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleOpts +[WrapResponseWriter]: https://pkg.go.dev/github.com/go-chi/chi/middleware#WrapResponseWriter + +### Extra middlewares & packages Please see https://github.com/go-chi for additional packages. @@ -344,13 +411,13 @@ Please see https://github.com/go-chi for additional packages. | [docgen](https://github.com/go-chi/docgen) | Print chi.Router routes at runtime | | [jwtauth](https://github.com/go-chi/jwtauth) | JWT authentication | | [hostrouter](https://github.com/go-chi/hostrouter) | Domain/host based request routing | -| [httpcoala](https://github.com/go-chi/httpcoala) | HTTP request coalescer | -| [chi-authz](https://github.com/casbin/chi-authz) | Request ACL via https://github.com/hsluoyz/casbin | -| [phi](https://github.com/fate-lovely/phi) | Port chi to [fasthttp](https://github.com/valyala/fasthttp) | +| [httplog](https://github.com/go-chi/httplog) | Small but powerful structured HTTP request logging | +| [httprate](https://github.com/go-chi/httprate) | HTTP request rate limiter | +| [httptracer](https://github.com/go-chi/httptracer) | HTTP request performance tracing library | +| [httpvcr](https://github.com/go-chi/httpvcr) | Write deterministic tests for external sources | +| [stampede](https://github.com/go-chi/stampede) | HTTP request coalescer | -------------------------------------------------------------------------------------------------------------------- -please [submit a PR](./CONTRIBUTING.md) if you'd like to include a link to a chi-compatible middleware - ## context? @@ -369,25 +436,25 @@ and.. The benchmark suite: https://github.com/pkieltyka/go-http-routing-benchmark -Results as of Jan 9, 2019 with Go 1.11.4 on Linux X1 Carbon laptop +Results as of Nov 29, 2020 with Go 1.15.5 on Linux AMD 3950x ```shell -BenchmarkChi_Param 3000000 475 ns/op 432 B/op 3 allocs/op -BenchmarkChi_Param5 2000000 696 ns/op 432 B/op 3 allocs/op -BenchmarkChi_Param20 1000000 1275 ns/op 432 B/op 3 allocs/op -BenchmarkChi_ParamWrite 3000000 505 ns/op 432 B/op 3 allocs/op -BenchmarkChi_GithubStatic 3000000 508 ns/op 432 B/op 3 allocs/op -BenchmarkChi_GithubParam 2000000 669 ns/op 432 B/op 3 allocs/op -BenchmarkChi_GithubAll 10000 134627 ns/op 87699 B/op 609 allocs/op -BenchmarkChi_GPlusStatic 3000000 402 ns/op 432 B/op 3 allocs/op -BenchmarkChi_GPlusParam 3000000 500 ns/op 432 B/op 3 allocs/op -BenchmarkChi_GPlus2Params 3000000 586 ns/op 432 B/op 3 allocs/op -BenchmarkChi_GPlusAll 200000 7237 ns/op 5616 B/op 39 allocs/op -BenchmarkChi_ParseStatic 3000000 408 ns/op 432 B/op 3 allocs/op -BenchmarkChi_ParseParam 3000000 488 ns/op 432 B/op 3 allocs/op -BenchmarkChi_Parse2Params 3000000 551 ns/op 432 B/op 3 allocs/op -BenchmarkChi_ParseAll 100000 13508 ns/op 11232 B/op 78 allocs/op -BenchmarkChi_StaticAll 20000 81933 ns/op 67826 B/op 471 allocs/op +BenchmarkChi_Param 3075895 384 ns/op 400 B/op 2 allocs/op +BenchmarkChi_Param5 2116603 566 ns/op 400 B/op 2 allocs/op +BenchmarkChi_Param20 964117 1227 ns/op 400 B/op 2 allocs/op +BenchmarkChi_ParamWrite 2863413 420 ns/op 400 B/op 2 allocs/op +BenchmarkChi_GithubStatic 3045488 395 ns/op 400 B/op 2 allocs/op +BenchmarkChi_GithubParam 2204115 540 ns/op 400 B/op 2 allocs/op +BenchmarkChi_GithubAll 10000 113811 ns/op 81203 B/op 406 allocs/op +BenchmarkChi_GPlusStatic 3337485 359 ns/op 400 B/op 2 allocs/op +BenchmarkChi_GPlusParam 2825853 423 ns/op 400 B/op 2 allocs/op +BenchmarkChi_GPlus2Params 2471697 483 ns/op 400 B/op 2 allocs/op +BenchmarkChi_GPlusAll 194220 5950 ns/op 5200 B/op 26 allocs/op +BenchmarkChi_ParseStatic 3365324 356 ns/op 400 B/op 2 allocs/op +BenchmarkChi_ParseParam 2976614 404 ns/op 400 B/op 2 allocs/op +BenchmarkChi_Parse2Params 2638084 439 ns/op 400 B/op 2 allocs/op +BenchmarkChi_ParseAll 109567 11295 ns/op 10400 B/op 52 allocs/op +BenchmarkChi_StaticAll 16846 71308 ns/op 62802 B/op 314 allocs/op ``` Comparison with other routers: https://gist.github.com/pkieltyka/123032f12052520aaccab752bd3e78cc @@ -412,18 +479,15 @@ We'll be more than happy to see [your contributions](./CONTRIBUTING.md)! ## Beyond REST chi is just a http router that lets you decompose request handling into many smaller layers. -Many companies including Pressly.com (of course) use chi to write REST services for their public -APIs. But, REST is just a convention for managing state via HTTP, and there's a lot of other pieces -required to write a complete client-server system or network of microservices. +Many companies use chi to write REST services for their public APIs. But, REST is just a convention +for managing state via HTTP, and there's a lot of other pieces required to write a complete client-server +system or network of microservices. -Looking ahead beyond REST, I also recommend some newer works in the field coming from -[gRPC](https://github.com/grpc/grpc-go), [NATS](https://nats.io), [go-kit](https://github.com/go-kit/kit) -and even [graphql](https://github.com/graphql-go/graphql). They're all pretty cool with their -own unique approaches and benefits. Specifically, I'd look at gRPC since it makes client-server -communication feel like a single program on a single computer, no need to hand-write a client library -and the request/response payloads are typed contracts. NATS is pretty amazing too as a super -fast and lightweight pub-sub transport that can speak protobufs, with nice service discovery - -an excellent combination with gRPC. +Looking beyond REST, I also recommend some newer works in the field: +* [webrpc](https://github.com/webrpc/webrpc) - Web-focused RPC client+server framework with code-gen +* [gRPC](https://github.com/grpc/grpc-go) - Google's RPC framework via protobufs +* [graphql](https://github.com/99designs/gqlgen) - Declarative query language +* [NATS](https://nats.io) - lightweight pub-sub ## License @@ -432,7 +496,7 @@ Copyright (c) 2015-present [Peter Kieltyka](https://github.com/pkieltyka) Licensed under [MIT License](./LICENSE) -[GoDoc]: https://godoc.org/github.com/go-chi/chi +[GoDoc]: https://pkg.go.dev/github.com/go-chi/chi/v5 [GoDoc Widget]: https://godoc.org/github.com/go-chi/chi?status.svg [Travis]: https://travis-ci.org/go-chi/chi [Travis Widget]: https://travis-ci.org/go-chi/chi.svg?branch=master diff --git a/vendor/github.com/go-chi/chi/v5/SECURITY.md b/vendor/github.com/go-chi/chi/v5/SECURITY.md new file mode 100644 index 0000000..7e937f8 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/SECURITY.md @@ -0,0 +1,5 @@ +# Reporting Security Issues + +We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. + +To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/go-chi/chi/security/advisories/new) tab. diff --git a/vendor/github.com/go-chi/chi/chain.go b/vendor/github.com/go-chi/chi/v5/chain.go similarity index 94% rename from vendor/github.com/go-chi/chi/chain.go rename to vendor/github.com/go-chi/chi/v5/chain.go index 88e6846..a227841 100644 --- a/vendor/github.com/go-chi/chi/chain.go +++ b/vendor/github.com/go-chi/chi/v5/chain.go @@ -10,21 +10,21 @@ func Chain(middlewares ...func(http.Handler) http.Handler) Middlewares { // Handler builds and returns a http.Handler from the chain of middlewares, // with `h http.Handler` as the final handler. func (mws Middlewares) Handler(h http.Handler) http.Handler { - return &ChainHandler{mws, h, chain(mws, h)} + return &ChainHandler{h, chain(mws, h), mws} } // HandlerFunc builds and returns a http.Handler from the chain of middlewares, // with `h http.Handler` as the final handler. func (mws Middlewares) HandlerFunc(h http.HandlerFunc) http.Handler { - return &ChainHandler{mws, h, chain(mws, h)} + return &ChainHandler{h, chain(mws, h), mws} } // ChainHandler is a http.Handler with support for handler composition and // execution. type ChainHandler struct { - Middlewares Middlewares Endpoint http.Handler chain http.Handler + Middlewares Middlewares } func (c *ChainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { diff --git a/vendor/github.com/go-chi/chi/chi.go b/vendor/github.com/go-chi/chi/v5/chi.go similarity index 82% rename from vendor/github.com/go-chi/chi/chi.go rename to vendor/github.com/go-chi/chi/v5/chi.go index 9962229..a1691bb 100644 --- a/vendor/github.com/go-chi/chi/chi.go +++ b/vendor/github.com/go-chi/chi/v5/chi.go @@ -1,29 +1,29 @@ -// // Package chi is a small, idiomatic and composable router for building HTTP services. // -// chi requires Go 1.7 or newer. +// chi requires Go 1.14 or newer. // // Example: -// package main // -// import ( -// "net/http" +// package main +// +// import ( +// "net/http" // -// "github.com/go-chi/chi" -// "github.com/go-chi/chi/middleware" -// ) +// "github.com/go-chi/chi/v5" +// "github.com/go-chi/chi/v5/middleware" +// ) // -// func main() { -// r := chi.NewRouter() -// r.Use(middleware.Logger) -// r.Use(middleware.Recoverer) +// func main() { +// r := chi.NewRouter() +// r.Use(middleware.Logger) +// r.Use(middleware.Recoverer) // -// r.Get("/", func(w http.ResponseWriter, r *http.Request) { -// w.Write([]byte("root.")) -// }) +// r.Get("/", func(w http.ResponseWriter, r *http.Request) { +// w.Write([]byte("root.")) +// }) // -// http.ListenAndServe(":3333", r) -// } +// http.ListenAndServe(":3333", r) +// } // // See github.com/go-chi/chi/_examples/ for more in-depth examples. // @@ -47,12 +47,12 @@ // placeholder which will match / characters. // // Examples: -// "/user/{name}" matches "/user/jsmith" but not "/user/jsmith/info" or "/user/jsmith/" -// "/user/{name}/info" matches "/user/jsmith/info" -// "/page/*" matches "/page/intro/latest" -// "/page/*/index" also matches "/page/intro/latest" -// "/date/{yyyy:\\d\\d\\d\\d}/{mm:\\d\\d}/{dd:\\d\\d}" matches "/date/2017/04/01" // +// "/user/{name}" matches "/user/jsmith" but not "/user/jsmith/info" or "/user/jsmith/" +// "/user/{name}/info" matches "/user/jsmith/info" +// "/page/*" matches "/page/intro/latest" +// "/page/{other}/index" also matches "/page/intro/latest" +// "/date/{yyyy:\\d\\d\\d\\d}/{mm:\\d\\d}/{dd:\\d\\d}" matches "/date/2017/04/01" package chi import "net/http" @@ -68,7 +68,7 @@ type Router interface { http.Handler Routes - // Use appends one of more middlewares onto the Router stack. + // Use appends one or more middlewares onto the Router stack. Use(middlewares ...func(http.Handler) http.Handler) // With adds inline middlewares for an endpoint handler. diff --git a/vendor/github.com/go-chi/chi/context.go b/vendor/github.com/go-chi/chi/v5/context.go similarity index 74% rename from vendor/github.com/go-chi/chi/context.go rename to vendor/github.com/go-chi/chi/v5/context.go index 229c9cb..e2cd908 100644 --- a/vendor/github.com/go-chi/chi/context.go +++ b/vendor/github.com/go-chi/chi/v5/context.go @@ -2,11 +2,38 @@ package chi import ( "context" - "net" "net/http" "strings" ) +// URLParam returns the url parameter from a http.Request object. +func URLParam(r *http.Request, key string) string { + if rctx := RouteContext(r.Context()); rctx != nil { + return rctx.URLParam(key) + } + return "" +} + +// URLParamFromCtx returns the url parameter from a http.Request Context. +func URLParamFromCtx(ctx context.Context, key string) string { + if rctx := RouteContext(ctx); rctx != nil { + return rctx.URLParam(key) + } + return "" +} + +// RouteContext returns chi's routing Context object from a +// http.Request Context. +func RouteContext(ctx context.Context) *Context { + val, _ := ctx.Value(RouteCtxKey).(*Context) + return val +} + +// NewRouteContext returns a new routing Context object. +func NewRouteContext() *Context { + return &Context{} +} + var ( // RouteCtxKey is the context.Context key to store the request context. RouteCtxKey = &contextKey{"RouteContext"} @@ -18,39 +45,39 @@ var ( type Context struct { Routes Routes + // parentCtx is the parent of this one, for using Context as a + // context.Context directly. This is an optimization that saves + // 1 allocation. + parentCtx context.Context + // Routing path/method override used during the route search. // See Mux#routeHTTP method. RoutePath string RouteMethod string - // Routing pattern stack throughout the lifecycle of the request, - // across all connected routers. It is a record of all matching - // patterns across a stack of sub-routers. - RoutePatterns []string - // URLParams are the stack of routeParams captured during the // routing lifecycle across a stack of sub-routers. URLParams RouteParams + // Route parameters matched for the current sub-router. It is + // intentionally unexported so it can't be tampered. + routeParams RouteParams + // The endpoint routing pattern that matched the request URI path // or `RoutePath` of the current sub-router. This value will update // during the lifecycle of a request passing through a stack of // sub-routers. routePattern string - // Route parameters matched for the current sub-router. It is - // intentionally unexported so it cant be tampered. - routeParams RouteParams + // Routing pattern stack throughout the lifecycle of the request, + // across all connected routers. It is a record of all matching + // patterns across a stack of sub-routers. + RoutePatterns []string - // methodNotAllowed hint + methodsAllowed []methodTyp // allowed methods in case of a 405 methodNotAllowed bool } -// NewRouteContext returns a new routing Context object. -func NewRouteContext() *Context { - return &Context{} -} - // Reset a routing context to its initial state. func (x *Context) Reset() { x.Routes = nil @@ -64,6 +91,8 @@ func (x *Context) Reset() { x.routeParams.Keys = x.routeParams.Keys[:0] x.routeParams.Values = x.routeParams.Values[:0] x.methodNotAllowed = false + x.methodsAllowed = x.methodsAllowed[:0] + x.parentCtx = nil } // URLParam returns the corresponding URL parameter value from the request @@ -84,38 +113,30 @@ func (x *Context) URLParam(key string) string { // // For example, // -// func Instrument(next http.Handler) http.Handler { -// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { -// next.ServeHTTP(w, r) -// routePattern := chi.RouteContext(r.Context()).RoutePattern() -// measure(w, r, routePattern) -// }) -// } +// func Instrument(next http.Handler) http.Handler { +// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { +// next.ServeHTTP(w, r) +// routePattern := chi.RouteContext(r.Context()).RoutePattern() +// measure(w, r, routePattern) +// }) +// } func (x *Context) RoutePattern() string { routePattern := strings.Join(x.RoutePatterns, "") - return strings.Replace(routePattern, "/*/", "/", -1) -} - -// RouteContext returns chi's routing Context object from a -// http.Request Context. -func RouteContext(ctx context.Context) *Context { - return ctx.Value(RouteCtxKey).(*Context) -} - -// URLParam returns the url parameter from a http.Request object. -func URLParam(r *http.Request, key string) string { - if rctx := RouteContext(r.Context()); rctx != nil { - return rctx.URLParam(key) + routePattern = replaceWildcards(routePattern) + if routePattern != "/" { + routePattern = strings.TrimSuffix(routePattern, "//") + routePattern = strings.TrimSuffix(routePattern, "/") } - return "" + return routePattern } -// URLParamFromCtx returns the url parameter from a http.Request Context. -func URLParamFromCtx(ctx context.Context, key string) string { - if rctx := RouteContext(ctx); rctx != nil { - return rctx.URLParam(key) +// replaceWildcards takes a route pattern and recursively replaces all +// occurrences of "/*/" to "/". +func replaceWildcards(p string) string { + if strings.Contains(p, "/*/") { + return replaceWildcards(strings.Replace(p, "/*/", "/", -1)) } - return "" + return p } // RouteParams is a structure to track URL routing parameters efficiently. @@ -125,28 +146,8 @@ type RouteParams struct { // Add will append a URL parameter to the end of the route param func (s *RouteParams) Add(key, value string) { - (*s).Keys = append((*s).Keys, key) - (*s).Values = append((*s).Values, value) -} - -// ServerBaseContext wraps an http.Handler to set the request context to the -// `baseCtx`. -func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler { - fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - baseCtx := baseCtx - - // Copy over default net/http server context keys - if v, ok := ctx.Value(http.ServerContextKey).(*http.Server); ok { - baseCtx = context.WithValue(baseCtx, http.ServerContextKey, v) - } - if v, ok := ctx.Value(http.LocalAddrContextKey).(net.Addr); ok { - baseCtx = context.WithValue(baseCtx, http.LocalAddrContextKey, v) - } - - h.ServeHTTP(w, r.WithContext(baseCtx)) - }) - return fn + s.Keys = append(s.Keys, key) + s.Values = append(s.Values, value) } // contextKey is a value for use with context.WithValue. It's used as diff --git a/vendor/github.com/go-chi/chi/v5/middleware/basic_auth.go b/vendor/github.com/go-chi/chi/v5/middleware/basic_auth.go new file mode 100644 index 0000000..a546c9e --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/middleware/basic_auth.go @@ -0,0 +1,33 @@ +package middleware + +import ( + "crypto/subtle" + "fmt" + "net/http" +) + +// BasicAuth implements a simple middleware handler for adding basic http auth to a route. +func BasicAuth(realm string, creds map[string]string) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + user, pass, ok := r.BasicAuth() + if !ok { + basicAuthFailed(w, realm) + return + } + + credPass, credUserOk := creds[user] + if !credUserOk || subtle.ConstantTimeCompare([]byte(pass), []byte(credPass)) != 1 { + basicAuthFailed(w, realm) + return + } + + next.ServeHTTP(w, r) + }) + } +} + +func basicAuthFailed(w http.ResponseWriter, realm string) { + w.Header().Add("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm)) + w.WriteHeader(http.StatusUnauthorized) +} diff --git a/vendor/github.com/go-chi/chi/v5/middleware/clean_path.go b/vendor/github.com/go-chi/chi/v5/middleware/clean_path.go new file mode 100644 index 0000000..adeba42 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/middleware/clean_path.go @@ -0,0 +1,28 @@ +package middleware + +import ( + "net/http" + "path" + + "github.com/go-chi/chi/v5" +) + +// CleanPath middleware will clean out double slash mistakes from a user's request path. +// For example, if a user requests /users//1 or //users////1 will both be treated as: /users/1 +func CleanPath(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + rctx := chi.RouteContext(r.Context()) + + routePath := rctx.RoutePath + if routePath == "" { + if r.URL.RawPath != "" { + routePath = r.URL.RawPath + } else { + routePath = r.URL.Path + } + rctx.RoutePath = path.Clean(routePath) + } + + next.ServeHTTP(w, r) + }) +} diff --git a/vendor/github.com/go-chi/chi/v5/middleware/compress.go b/vendor/github.com/go-chi/chi/v5/middleware/compress.go new file mode 100644 index 0000000..28240c4 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/middleware/compress.go @@ -0,0 +1,398 @@ +package middleware + +import ( + "bufio" + "compress/flate" + "compress/gzip" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "strings" + "sync" +) + +var defaultCompressibleContentTypes = []string{ + "text/html", + "text/css", + "text/plain", + "text/javascript", + "application/javascript", + "application/x-javascript", + "application/json", + "application/atom+xml", + "application/rss+xml", + "image/svg+xml", +} + +// Compress is a middleware that compresses response +// body of a given content types to a data format based +// on Accept-Encoding request header. It uses a given +// compression level. +// +// NOTE: make sure to set the Content-Type header on your response +// otherwise this middleware will not compress the response body. For ex, in +// your handler you should set w.Header().Set("Content-Type", http.DetectContentType(yourBody)) +// or set it manually. +// +// Passing a compression level of 5 is sensible value +func Compress(level int, types ...string) func(next http.Handler) http.Handler { + compressor := NewCompressor(level, types...) + return compressor.Handler +} + +// Compressor represents a set of encoding configurations. +type Compressor struct { + // The mapping of encoder names to encoder functions. + encoders map[string]EncoderFunc + // The mapping of pooled encoders to pools. + pooledEncoders map[string]*sync.Pool + // The set of content types allowed to be compressed. + allowedTypes map[string]struct{} + allowedWildcards map[string]struct{} + // The list of encoders in order of decreasing precedence. + encodingPrecedence []string + level int // The compression level. +} + +// NewCompressor creates a new Compressor that will handle encoding responses. +// +// The level should be one of the ones defined in the flate package. +// The types are the content types that are allowed to be compressed. +func NewCompressor(level int, types ...string) *Compressor { + // If types are provided, set those as the allowed types. If none are + // provided, use the default list. + allowedTypes := make(map[string]struct{}) + allowedWildcards := make(map[string]struct{}) + if len(types) > 0 { + for _, t := range types { + if strings.Contains(strings.TrimSuffix(t, "/*"), "*") { + panic(fmt.Sprintf("middleware/compress: Unsupported content-type wildcard pattern '%s'. Only '/*' supported", t)) + } + if strings.HasSuffix(t, "/*") { + allowedWildcards[strings.TrimSuffix(t, "/*")] = struct{}{} + } else { + allowedTypes[t] = struct{}{} + } + } + } else { + for _, t := range defaultCompressibleContentTypes { + allowedTypes[t] = struct{}{} + } + } + + c := &Compressor{ + level: level, + encoders: make(map[string]EncoderFunc), + pooledEncoders: make(map[string]*sync.Pool), + allowedTypes: allowedTypes, + allowedWildcards: allowedWildcards, + } + + // Set the default encoders. The precedence order uses the reverse + // ordering that the encoders were added. This means adding new encoders + // will move them to the front of the order. + // + // TODO: + // lzma: Opera. + // sdch: Chrome, Android. Gzip output + dictionary header. + // br: Brotli, see https://github.com/go-chi/chi/pull/326 + + // HTTP 1.1 "deflate" (RFC 2616) stands for DEFLATE data (RFC 1951) + // wrapped with zlib (RFC 1950). The zlib wrapper uses Adler-32 + // checksum compared to CRC-32 used in "gzip" and thus is faster. + // + // But.. some old browsers (MSIE, Safari 5.1) incorrectly expect + // raw DEFLATE data only, without the mentioned zlib wrapper. + // Because of this major confusion, most modern browsers try it + // both ways, first looking for zlib headers. + // Quote by Mark Adler: http://stackoverflow.com/a/9186091/385548 + // + // The list of browsers having problems is quite big, see: + // http://zoompf.com/blog/2012/02/lose-the-wait-http-compression + // https://web.archive.org/web/20120321182910/http://www.vervestudios.co/projects/compression-tests/results + // + // That's why we prefer gzip over deflate. It's just more reliable + // and not significantly slower than deflate. + c.SetEncoder("deflate", encoderDeflate) + + // TODO: Exception for old MSIE browsers that can't handle non-HTML? + // https://zoompf.com/blog/2012/02/lose-the-wait-http-compression + c.SetEncoder("gzip", encoderGzip) + + // NOTE: Not implemented, intentionally: + // case "compress": // LZW. Deprecated. + // case "bzip2": // Too slow on-the-fly. + // case "zopfli": // Too slow on-the-fly. + // case "xz": // Too slow on-the-fly. + return c +} + +// SetEncoder can be used to set the implementation of a compression algorithm. +// +// The encoding should be a standardised identifier. See: +// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding +// +// For example, add the Brotli algorithm: +// +// import brotli_enc "gopkg.in/kothar/brotli-go.v0/enc" +// +// compressor := middleware.NewCompressor(5, "text/html") +// compressor.SetEncoder("br", func(w io.Writer, level int) io.Writer { +// params := brotli_enc.NewBrotliParams() +// params.SetQuality(level) +// return brotli_enc.NewBrotliWriter(params, w) +// }) +func (c *Compressor) SetEncoder(encoding string, fn EncoderFunc) { + encoding = strings.ToLower(encoding) + if encoding == "" { + panic("the encoding can not be empty") + } + if fn == nil { + panic("attempted to set a nil encoder function") + } + + // If we are adding a new encoder that is already registered, we have to + // clear that one out first. + delete(c.pooledEncoders, encoding) + delete(c.encoders, encoding) + + // If the encoder supports Resetting (IoReseterWriter), then it can be pooled. + encoder := fn(ioutil.Discard, c.level) + if encoder != nil { + if _, ok := encoder.(ioResetterWriter); ok { + pool := &sync.Pool{ + New: func() interface{} { + return fn(ioutil.Discard, c.level) + }, + } + c.pooledEncoders[encoding] = pool + } + } + // If the encoder is not in the pooledEncoders, add it to the normal encoders. + if _, ok := c.pooledEncoders[encoding]; !ok { + c.encoders[encoding] = fn + } + + for i, v := range c.encodingPrecedence { + if v == encoding { + c.encodingPrecedence = append(c.encodingPrecedence[:i], c.encodingPrecedence[i+1:]...) + } + } + + c.encodingPrecedence = append([]string{encoding}, c.encodingPrecedence...) +} + +// Handler returns a new middleware that will compress the response based on the +// current Compressor. +func (c *Compressor) Handler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + encoder, encoding, cleanup := c.selectEncoder(r.Header, w) + + cw := &compressResponseWriter{ + ResponseWriter: w, + w: w, + contentTypes: c.allowedTypes, + contentWildcards: c.allowedWildcards, + encoding: encoding, + compressible: false, // determined in post-handler + } + if encoder != nil { + cw.w = encoder + } + // Re-add the encoder to the pool if applicable. + defer cleanup() + defer cw.Close() + + next.ServeHTTP(cw, r) + }) +} + +// selectEncoder returns the encoder, the name of the encoder, and a closer function. +func (c *Compressor) selectEncoder(h http.Header, w io.Writer) (io.Writer, string, func()) { + header := h.Get("Accept-Encoding") + + // Parse the names of all accepted algorithms from the header. + accepted := strings.Split(strings.ToLower(header), ",") + + // Find supported encoder by accepted list by precedence + for _, name := range c.encodingPrecedence { + if matchAcceptEncoding(accepted, name) { + if pool, ok := c.pooledEncoders[name]; ok { + encoder := pool.Get().(ioResetterWriter) + cleanup := func() { + pool.Put(encoder) + } + encoder.Reset(w) + return encoder, name, cleanup + + } + if fn, ok := c.encoders[name]; ok { + return fn(w, c.level), name, func() {} + } + } + + } + + // No encoder found to match the accepted encoding + return nil, "", func() {} +} + +func matchAcceptEncoding(accepted []string, encoding string) bool { + for _, v := range accepted { + if strings.Contains(v, encoding) { + return true + } + } + return false +} + +// An EncoderFunc is a function that wraps the provided io.Writer with a +// streaming compression algorithm and returns it. +// +// In case of failure, the function should return nil. +type EncoderFunc func(w io.Writer, level int) io.Writer + +// Interface for types that allow resetting io.Writers. +type ioResetterWriter interface { + io.Writer + Reset(w io.Writer) +} + +type compressResponseWriter struct { + http.ResponseWriter + + // The streaming encoder writer to be used if there is one. Otherwise, + // this is just the normal writer. + w io.Writer + contentTypes map[string]struct{} + contentWildcards map[string]struct{} + encoding string + wroteHeader bool + compressible bool +} + +func (cw *compressResponseWriter) isCompressible() bool { + // Parse the first part of the Content-Type response header. + contentType := cw.Header().Get("Content-Type") + if idx := strings.Index(contentType, ";"); idx >= 0 { + contentType = contentType[0:idx] + } + + // Is the content type compressible? + if _, ok := cw.contentTypes[contentType]; ok { + return true + } + if idx := strings.Index(contentType, "/"); idx > 0 { + contentType = contentType[0:idx] + _, ok := cw.contentWildcards[contentType] + return ok + } + return false +} + +func (cw *compressResponseWriter) WriteHeader(code int) { + if cw.wroteHeader { + cw.ResponseWriter.WriteHeader(code) // Allow multiple calls to propagate. + return + } + cw.wroteHeader = true + defer cw.ResponseWriter.WriteHeader(code) + + // Already compressed data? + if cw.Header().Get("Content-Encoding") != "" { + return + } + + if !cw.isCompressible() { + cw.compressible = false + return + } + + if cw.encoding != "" { + cw.compressible = true + cw.Header().Set("Content-Encoding", cw.encoding) + cw.Header().Add("Vary", "Accept-Encoding") + + // The content-length after compression is unknown + cw.Header().Del("Content-Length") + } +} + +func (cw *compressResponseWriter) Write(p []byte) (int, error) { + if !cw.wroteHeader { + cw.WriteHeader(http.StatusOK) + } + + return cw.writer().Write(p) +} + +func (cw *compressResponseWriter) writer() io.Writer { + if cw.compressible { + return cw.w + } + return cw.ResponseWriter +} + +type compressFlusher interface { + Flush() error +} + +func (cw *compressResponseWriter) Flush() { + if f, ok := cw.writer().(http.Flusher); ok { + f.Flush() + } + // If the underlying writer has a compression flush signature, + // call this Flush() method instead + if f, ok := cw.writer().(compressFlusher); ok { + f.Flush() + + // Also flush the underlying response writer + if f, ok := cw.ResponseWriter.(http.Flusher); ok { + f.Flush() + } + } +} + +func (cw *compressResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + if hj, ok := cw.writer().(http.Hijacker); ok { + return hj.Hijack() + } + return nil, nil, errors.New("chi/middleware: http.Hijacker is unavailable on the writer") +} + +func (cw *compressResponseWriter) Push(target string, opts *http.PushOptions) error { + if ps, ok := cw.writer().(http.Pusher); ok { + return ps.Push(target, opts) + } + return errors.New("chi/middleware: http.Pusher is unavailable on the writer") +} + +func (cw *compressResponseWriter) Close() error { + if c, ok := cw.writer().(io.WriteCloser); ok { + return c.Close() + } + return errors.New("chi/middleware: io.WriteCloser is unavailable on the writer") +} + +func (cw *compressResponseWriter) Unwrap() http.ResponseWriter { + return cw.ResponseWriter +} + +func encoderGzip(w io.Writer, level int) io.Writer { + gw, err := gzip.NewWriterLevel(w, level) + if err != nil { + return nil + } + return gw +} + +func encoderDeflate(w io.Writer, level int) io.Writer { + dw, err := flate.NewWriter(w, level) + if err != nil { + return nil + } + return dw +} diff --git a/vendor/github.com/go-chi/chi/middleware/content_charset.go b/vendor/github.com/go-chi/chi/v5/middleware/content_charset.go similarity index 100% rename from vendor/github.com/go-chi/chi/middleware/content_charset.go rename to vendor/github.com/go-chi/chi/v5/middleware/content_charset.go diff --git a/vendor/github.com/go-chi/chi/v5/middleware/content_encoding.go b/vendor/github.com/go-chi/chi/v5/middleware/content_encoding.go new file mode 100644 index 0000000..e0b9ccc --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/middleware/content_encoding.go @@ -0,0 +1,34 @@ +package middleware + +import ( + "net/http" + "strings" +) + +// AllowContentEncoding enforces a whitelist of request Content-Encoding otherwise responds +// with a 415 Unsupported Media Type status. +func AllowContentEncoding(contentEncoding ...string) func(next http.Handler) http.Handler { + allowedEncodings := make(map[string]struct{}, len(contentEncoding)) + for _, encoding := range contentEncoding { + allowedEncodings[strings.TrimSpace(strings.ToLower(encoding))] = struct{}{} + } + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + requestEncodings := r.Header["Content-Encoding"] + // skip check for empty content body or no Content-Encoding + if r.ContentLength == 0 { + next.ServeHTTP(w, r) + return + } + // All encodings in the request must be allowed + for _, encoding := range requestEncodings { + if _, ok := allowedEncodings[strings.TrimSpace(strings.ToLower(encoding))]; !ok { + w.WriteHeader(http.StatusUnsupportedMediaType) + return + } + } + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) + } +} diff --git a/vendor/github.com/go-chi/chi/middleware/content_type.go b/vendor/github.com/go-chi/chi/v5/middleware/content_type.go similarity index 80% rename from vendor/github.com/go-chi/chi/middleware/content_type.go rename to vendor/github.com/go-chi/chi/v5/middleware/content_type.go index ee49578..023978f 100644 --- a/vendor/github.com/go-chi/chi/middleware/content_type.go +++ b/vendor/github.com/go-chi/chi/v5/middleware/content_type.go @@ -19,9 +19,9 @@ func SetHeader(key, value string) func(next http.Handler) http.Handler { // AllowContentType enforces a whitelist of request Content-Types otherwise responds // with a 415 Unsupported Media Type status. func AllowContentType(contentTypes ...string) func(next http.Handler) http.Handler { - cT := []string{} - for _, t := range contentTypes { - cT = append(cT, strings.ToLower(t)) + allowedContentTypes := make(map[string]struct{}, len(contentTypes)) + for _, ctype := range contentTypes { + allowedContentTypes[strings.TrimSpace(strings.ToLower(ctype))] = struct{}{} } return func(next http.Handler) http.Handler { @@ -37,11 +37,9 @@ func AllowContentType(contentTypes ...string) func(next http.Handler) http.Handl s = s[0:i] } - for _, t := range cT { - if t == s { - next.ServeHTTP(w, r) - return - } + if _, ok := allowedContentTypes[s]; ok { + next.ServeHTTP(w, r) + return } w.WriteHeader(http.StatusUnsupportedMediaType) diff --git a/vendor/github.com/go-chi/chi/middleware/get_head.go b/vendor/github.com/go-chi/chi/v5/middleware/get_head.go similarity index 97% rename from vendor/github.com/go-chi/chi/middleware/get_head.go rename to vendor/github.com/go-chi/chi/v5/middleware/get_head.go index 86068a9..d4606d8 100644 --- a/vendor/github.com/go-chi/chi/middleware/get_head.go +++ b/vendor/github.com/go-chi/chi/v5/middleware/get_head.go @@ -3,7 +3,7 @@ package middleware import ( "net/http" - "github.com/go-chi/chi" + "github.com/go-chi/chi/v5" ) // GetHead automatically route undefined HEAD requests to GET handlers. diff --git a/vendor/github.com/go-chi/chi/middleware/heartbeat.go b/vendor/github.com/go-chi/chi/v5/middleware/heartbeat.go similarity index 87% rename from vendor/github.com/go-chi/chi/middleware/heartbeat.go rename to vendor/github.com/go-chi/chi/v5/middleware/heartbeat.go index fe822fb..f36e8cc 100644 --- a/vendor/github.com/go-chi/chi/middleware/heartbeat.go +++ b/vendor/github.com/go-chi/chi/v5/middleware/heartbeat.go @@ -12,7 +12,7 @@ import ( func Heartbeat(endpoint string) func(http.Handler) http.Handler { f := func(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { - if r.Method == "GET" && strings.EqualFold(r.URL.Path, endpoint) { + if (r.Method == "GET" || r.Method == "HEAD") && strings.EqualFold(r.URL.Path, endpoint) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) w.Write([]byte(".")) diff --git a/vendor/github.com/go-chi/chi/middleware/logger.go b/vendor/github.com/go-chi/chi/v5/middleware/logger.go similarity index 81% rename from vendor/github.com/go-chi/chi/middleware/logger.go rename to vendor/github.com/go-chi/chi/v5/middleware/logger.go index 9f119d5..cff9bd2 100644 --- a/vendor/github.com/go-chi/chi/middleware/logger.go +++ b/vendor/github.com/go-chi/chi/v5/middleware/logger.go @@ -6,6 +6,7 @@ import ( "log" "net/http" "os" + "runtime" "time" ) @@ -16,7 +17,7 @@ var ( // DefaultLogger is called by the Logger middleware handler to log each request. // Its made a package-level variable so that it can be reconfigured for custom // logging configurations. - DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags), NoColor: false}) + DefaultLogger func(next http.Handler) http.Handler ) // Logger is a middleware that logs the start and end of each request, along @@ -25,8 +26,16 @@ var ( // print in color, otherwise it will print in black and white. Logger prints a // request ID if one is provided. // -// Alternatively, look at https://github.com/pressly/lg and the `lg.RequestLogger` -// middleware pkg. +// Alternatively, look at https://github.com/goware/httplog for a more in-depth +// http logger with structured logging support. +// +// IMPORTANT NOTE: Logger should go before any other middleware that may change +// the response, such as middleware.Recoverer. Example: +// +// r := chi.NewRouter() +// r.Use(middleware.Logger) // <--<< Logger should come before Recoverer +// r.Use(middleware.Recoverer) +// r.Get("/", handler) func Logger(next http.Handler) http.Handler { return DefaultLogger(next) } @@ -40,7 +49,7 @@ func RequestLogger(f LogFormatter) func(next http.Handler) http.Handler { t1 := time.Now() defer func() { - entry.Write(ww.Status(), ww.BytesWritten(), time.Since(t1)) + entry.Write(ww.Status(), ww.BytesWritten(), ww.Header(), time.Since(t1), nil) }() next.ServeHTTP(ww, WithLogEntry(r, entry)) @@ -58,7 +67,7 @@ type LogFormatter interface { // LogEntry records the final log when a request completes. // See defaultLogEntry for an example implementation. type LogEntry interface { - Write(status, bytes int, elapsed time.Duration) + Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) Panic(v interface{}, stack []byte) } @@ -122,7 +131,7 @@ type defaultLogEntry struct { useColor bool } -func (l *defaultLogEntry) Write(status, bytes int, elapsed time.Duration) { +func (l *defaultLogEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) { switch { case status < 200: cW(l.buf, l.useColor, bBlue, "%03d", status) @@ -151,8 +160,13 @@ func (l *defaultLogEntry) Write(status, bytes int, elapsed time.Duration) { } func (l *defaultLogEntry) Panic(v interface{}, stack []byte) { - panicEntry := l.NewLogEntry(l.request).(*defaultLogEntry) - cW(panicEntry.buf, l.useColor, bRed, "panic: %+v", v) - l.Logger.Print(panicEntry.buf.String()) - l.Logger.Print(string(stack)) + PrintPrettyStack(v) +} + +func init() { + color := true + if runtime.GOOS == "windows" { + color = false + } + DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags), NoColor: !color}) } diff --git a/vendor/github.com/go-chi/chi/v5/middleware/maybe.go b/vendor/github.com/go-chi/chi/v5/middleware/maybe.go new file mode 100644 index 0000000..eabca00 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/middleware/maybe.go @@ -0,0 +1,18 @@ +package middleware + +import "net/http" + +// Maybe middleware will allow you to change the flow of the middleware stack execution depending on return +// value of maybeFn(request). This is useful for example if you'd like to skip a middleware handler if +// a request does not satisfy the maybeFn logic. +func Maybe(mw func(http.Handler) http.Handler, maybeFn func(r *http.Request) bool) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if maybeFn(r) { + mw(next).ServeHTTP(w, r) + } else { + next.ServeHTTP(w, r) + } + }) + } +} diff --git a/vendor/github.com/go-chi/chi/middleware/middleware.go b/vendor/github.com/go-chi/chi/v5/middleware/middleware.go similarity index 56% rename from vendor/github.com/go-chi/chi/middleware/middleware.go rename to vendor/github.com/go-chi/chi/v5/middleware/middleware.go index be6a44f..cc371e0 100644 --- a/vendor/github.com/go-chi/chi/middleware/middleware.go +++ b/vendor/github.com/go-chi/chi/v5/middleware/middleware.go @@ -1,5 +1,16 @@ package middleware +import "net/http" + +// New will create a new middleware handler from a http.Handler. +func New(h http.Handler) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + }) + } +} + // contextKey is a value for use with context.WithValue. It's used as // a pointer so it fits in an interface{} without allocation. This technique // for defining context keys was copied from Go 1.7's new use of context in net/http. diff --git a/vendor/github.com/go-chi/chi/middleware/nocache.go b/vendor/github.com/go-chi/chi/v5/middleware/nocache.go similarity index 83% rename from vendor/github.com/go-chi/chi/middleware/nocache.go rename to vendor/github.com/go-chi/chi/v5/middleware/nocache.go index 2412829..9308d40 100644 --- a/vendor/github.com/go-chi/chi/middleware/nocache.go +++ b/vendor/github.com/go-chi/chi/v5/middleware/nocache.go @@ -9,7 +9,7 @@ import ( ) // Unix epoch time -var epoch = time.Unix(0, 0).Format(time.RFC1123) +var epoch = time.Unix(0, 0).UTC().Format(http.TimeFormat) // Taken from https://github.com/mytrile/nocache var noCacheHeaders = map[string]string{ @@ -32,10 +32,11 @@ var etagHeaders = []string{ // a router (or subrouter) from being cached by an upstream proxy and/or client. // // As per http://wiki.nginx.org/HttpProxyModule - NoCache sets: -// Expires: Thu, 01 Jan 1970 00:00:00 UTC -// Cache-Control: no-cache, private, max-age=0 -// X-Accel-Expires: 0 -// Pragma: no-cache (for HTTP/1.0 proxies/clients) +// +// Expires: Thu, 01 Jan 1970 00:00:00 UTC +// Cache-Control: no-cache, private, max-age=0 +// X-Accel-Expires: 0 +// Pragma: no-cache (for HTTP/1.0 proxies/clients) func NoCache(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { diff --git a/vendor/github.com/go-chi/chi/v5/middleware/page_route.go b/vendor/github.com/go-chi/chi/v5/middleware/page_route.go new file mode 100644 index 0000000..32871b7 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/middleware/page_route.go @@ -0,0 +1,20 @@ +package middleware + +import ( + "net/http" + "strings" +) + +// PageRoute is a simple middleware which allows you to route a static GET request +// at the middleware stack level. +func PageRoute(path string, handler http.Handler) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" && strings.EqualFold(r.URL.Path, path) { + handler.ServeHTTP(w, r) + return + } + next.ServeHTTP(w, r) + }) + } +} diff --git a/vendor/github.com/go-chi/chi/v5/middleware/path_rewrite.go b/vendor/github.com/go-chi/chi/v5/middleware/path_rewrite.go new file mode 100644 index 0000000..99af62c --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/middleware/path_rewrite.go @@ -0,0 +1,16 @@ +package middleware + +import ( + "net/http" + "strings" +) + +// PathRewrite is a simple middleware which allows you to rewrite the request URL path. +func PathRewrite(old, new string) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + r.URL.Path = strings.Replace(r.URL.Path, old, new, 1) + next.ServeHTTP(w, r) + }) + } +} diff --git a/vendor/github.com/go-chi/chi/v5/middleware/profiler.go b/vendor/github.com/go-chi/chi/v5/middleware/profiler.go new file mode 100644 index 0000000..ebd81ee --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/middleware/profiler.go @@ -0,0 +1,46 @@ +package middleware + +import ( + "expvar" + "net/http" + "net/http/pprof" + + "github.com/go-chi/chi/v5" +) + +// Profiler is a convenient subrouter used for mounting net/http/pprof. ie. +// +// func MyService() http.Handler { +// r := chi.NewRouter() +// // ..middlewares +// r.Mount("/debug", middleware.Profiler()) +// // ..routes +// return r +// } +func Profiler() http.Handler { + r := chi.NewRouter() + r.Use(NoCache) + + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, r.RequestURI+"/pprof/", http.StatusMovedPermanently) + }) + r.HandleFunc("/pprof", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, r.RequestURI+"/", http.StatusMovedPermanently) + }) + + r.HandleFunc("/pprof/*", pprof.Index) + r.HandleFunc("/pprof/cmdline", pprof.Cmdline) + r.HandleFunc("/pprof/profile", pprof.Profile) + r.HandleFunc("/pprof/symbol", pprof.Symbol) + r.HandleFunc("/pprof/trace", pprof.Trace) + r.Handle("/vars", expvar.Handler()) + + r.Handle("/pprof/goroutine", pprof.Handler("goroutine")) + r.Handle("/pprof/threadcreate", pprof.Handler("threadcreate")) + r.Handle("/pprof/mutex", pprof.Handler("mutex")) + r.Handle("/pprof/heap", pprof.Handler("heap")) + r.Handle("/pprof/block", pprof.Handler("block")) + r.Handle("/pprof/allocs", pprof.Handler("allocs")) + + return r +} diff --git a/vendor/github.com/go-chi/chi/middleware/realip.go b/vendor/github.com/go-chi/chi/v5/middleware/realip.go similarity index 75% rename from vendor/github.com/go-chi/chi/middleware/realip.go rename to vendor/github.com/go-chi/chi/v5/middleware/realip.go index 146c2b0..55c95a8 100644 --- a/vendor/github.com/go-chi/chi/middleware/realip.go +++ b/vendor/github.com/go-chi/chi/v5/middleware/realip.go @@ -4,23 +4,25 @@ package middleware // https://github.com/zenazn/goji/tree/master/web/middleware import ( + "net" "net/http" "strings" ) +var trueClientIP = http.CanonicalHeaderKey("True-Client-IP") var xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") var xRealIP = http.CanonicalHeaderKey("X-Real-IP") // RealIP is a middleware that sets a http.Request's RemoteAddr to the results -// of parsing either the X-Forwarded-For header or the X-Real-IP header (in that -// order). +// of parsing either the True-Client-IP, X-Real-IP or the X-Forwarded-For headers +// (in that order). // // This middleware should be inserted fairly early in the middleware stack to // ensure that subsequent layers (e.g., request loggers) which examine the // RemoteAddr will see the intended value. // // You should only use this middleware if you can trust the headers passed to -// you (in particular, the two headers this middleware uses), for example +// you (in particular, the three headers this middleware uses), for example // because you have placed a reverse proxy like HAProxy or nginx in front of // chi. If your reverse proxies are configured to pass along arbitrary header // values from the client, or if you use this middleware without a reverse @@ -40,15 +42,19 @@ func RealIP(h http.Handler) http.Handler { func realIP(r *http.Request) string { var ip string - if xff := r.Header.Get(xForwardedFor); xff != "" { - i := strings.Index(xff, ", ") + if tcip := r.Header.Get(trueClientIP); tcip != "" { + ip = tcip + } else if xrip := r.Header.Get(xRealIP); xrip != "" { + ip = xrip + } else if xff := r.Header.Get(xForwardedFor); xff != "" { + i := strings.Index(xff, ",") if i == -1 { i = len(xff) } ip = xff[:i] - } else if xrip := r.Header.Get(xRealIP); xrip != "" { - ip = xrip } - + if ip == "" || net.ParseIP(ip) == nil { + return "" + } return ip } diff --git a/vendor/github.com/go-chi/chi/v5/middleware/recoverer.go b/vendor/github.com/go-chi/chi/v5/middleware/recoverer.go new file mode 100644 index 0000000..81342df --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/middleware/recoverer.go @@ -0,0 +1,203 @@ +package middleware + +// The original work was derived from Goji's middleware, source: +// https://github.com/zenazn/goji/tree/master/web/middleware + +import ( + "bytes" + "errors" + "fmt" + "io" + "net/http" + "os" + "runtime/debug" + "strings" +) + +// Recoverer is a middleware that recovers from panics, logs the panic (and a +// backtrace), and returns a HTTP 500 (Internal Server Error) status if +// possible. Recoverer prints a request ID if one is provided. +// +// Alternatively, look at https://github.com/go-chi/httplog middleware pkgs. +func Recoverer(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + defer func() { + if rvr := recover(); rvr != nil { + if rvr == http.ErrAbortHandler { + // we don't recover http.ErrAbortHandler so the response + // to the client is aborted, this should not be logged + panic(rvr) + } + + logEntry := GetLogEntry(r) + if logEntry != nil { + logEntry.Panic(rvr, debug.Stack()) + } else { + PrintPrettyStack(rvr) + } + + if r.Header.Get("Connection") != "Upgrade" { + w.WriteHeader(http.StatusInternalServerError) + } + } + }() + + next.ServeHTTP(w, r) + } + + return http.HandlerFunc(fn) +} + +// for ability to test the PrintPrettyStack function +var recovererErrorWriter io.Writer = os.Stderr + +func PrintPrettyStack(rvr interface{}) { + debugStack := debug.Stack() + s := prettyStack{} + out, err := s.parse(debugStack, rvr) + if err == nil { + recovererErrorWriter.Write(out) + } else { + // print stdlib output as a fallback + os.Stderr.Write(debugStack) + } +} + +type prettyStack struct { +} + +func (s prettyStack) parse(debugStack []byte, rvr interface{}) ([]byte, error) { + var err error + useColor := true + buf := &bytes.Buffer{} + + cW(buf, false, bRed, "\n") + cW(buf, useColor, bCyan, " panic: ") + cW(buf, useColor, bBlue, "%v", rvr) + cW(buf, false, bWhite, "\n \n") + + // process debug stack info + stack := strings.Split(string(debugStack), "\n") + lines := []string{} + + // locate panic line, as we may have nested panics + for i := len(stack) - 1; i > 0; i-- { + lines = append(lines, stack[i]) + if strings.HasPrefix(stack[i], "panic(") { + lines = lines[0 : len(lines)-2] // remove boilerplate + break + } + } + + // reverse + for i := len(lines)/2 - 1; i >= 0; i-- { + opp := len(lines) - 1 - i + lines[i], lines[opp] = lines[opp], lines[i] + } + + // decorate + for i, line := range lines { + lines[i], err = s.decorateLine(line, useColor, i) + if err != nil { + return nil, err + } + } + + for _, l := range lines { + fmt.Fprintf(buf, "%s", l) + } + return buf.Bytes(), nil +} + +func (s prettyStack) decorateLine(line string, useColor bool, num int) (string, error) { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "\t") || strings.Contains(line, ".go:") { + return s.decorateSourceLine(line, useColor, num) + } + if strings.HasSuffix(line, ")") { + return s.decorateFuncCallLine(line, useColor, num) + } + if strings.HasPrefix(line, "\t") { + return strings.Replace(line, "\t", " ", 1), nil + } + return fmt.Sprintf(" %s\n", line), nil +} + +func (s prettyStack) decorateFuncCallLine(line string, useColor bool, num int) (string, error) { + idx := strings.LastIndex(line, "(") + if idx < 0 { + return "", errors.New("not a func call line") + } + + buf := &bytes.Buffer{} + pkg := line[0:idx] + // addr := line[idx:] + method := "" + + if idx := strings.LastIndex(pkg, string(os.PathSeparator)); idx < 0 { + if idx := strings.Index(pkg, "."); idx > 0 { + method = pkg[idx:] + pkg = pkg[0:idx] + } + } else { + method = pkg[idx+1:] + pkg = pkg[0 : idx+1] + if idx := strings.Index(method, "."); idx > 0 { + pkg += method[0:idx] + method = method[idx:] + } + } + pkgColor := nYellow + methodColor := bGreen + + if num == 0 { + cW(buf, useColor, bRed, " -> ") + pkgColor = bMagenta + methodColor = bRed + } else { + cW(buf, useColor, bWhite, " ") + } + cW(buf, useColor, pkgColor, "%s", pkg) + cW(buf, useColor, methodColor, "%s\n", method) + // cW(buf, useColor, nBlack, "%s", addr) + return buf.String(), nil +} + +func (s prettyStack) decorateSourceLine(line string, useColor bool, num int) (string, error) { + idx := strings.LastIndex(line, ".go:") + if idx < 0 { + return "", errors.New("not a source line") + } + + buf := &bytes.Buffer{} + path := line[0 : idx+3] + lineno := line[idx+3:] + + idx = strings.LastIndex(path, string(os.PathSeparator)) + dir := path[0 : idx+1] + file := path[idx+1:] + + idx = strings.Index(lineno, " ") + if idx > 0 { + lineno = lineno[0:idx] + } + fileColor := bCyan + lineColor := bGreen + + if num == 1 { + cW(buf, useColor, bRed, " -> ") + fileColor = bRed + lineColor = bMagenta + } else { + cW(buf, false, bWhite, " ") + } + cW(buf, useColor, bWhite, "%s", dir) + cW(buf, useColor, fileColor, "%s", file) + cW(buf, useColor, lineColor, "%s", lineno) + if num == 1 { + cW(buf, false, bWhite, "\n") + } + cW(buf, false, bWhite, "\n") + + return buf.String(), nil +} diff --git a/vendor/github.com/go-chi/chi/middleware/request_id.go b/vendor/github.com/go-chi/chi/v5/middleware/request_id.go similarity index 92% rename from vendor/github.com/go-chi/chi/middleware/request_id.go rename to vendor/github.com/go-chi/chi/v5/middleware/request_id.go index 65b58f6..4903ecc 100644 --- a/vendor/github.com/go-chi/chi/middleware/request_id.go +++ b/vendor/github.com/go-chi/chi/v5/middleware/request_id.go @@ -20,6 +20,10 @@ type ctxKeyRequestID int // RequestIDKey is the key that holds the unique request ID in a request context. const RequestIDKey ctxKeyRequestID = 0 +// RequestIDHeader is the name of the HTTP Header which contains the request id. +// Exported so that it can be changed by developers +var RequestIDHeader = "X-Request-Id" + var prefix string var reqid uint64 @@ -63,7 +67,7 @@ func init() { func RequestID(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - requestID := r.Header.Get("X-Request-Id") + requestID := r.Header.Get(RequestIDHeader) if requestID == "" { myid := atomic.AddUint64(&reqid, 1) requestID = fmt.Sprintf("%s-%06d", prefix, myid) diff --git a/vendor/github.com/go-chi/chi/v5/middleware/request_size.go b/vendor/github.com/go-chi/chi/v5/middleware/request_size.go new file mode 100644 index 0000000..678248c --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/middleware/request_size.go @@ -0,0 +1,18 @@ +package middleware + +import ( + "net/http" +) + +// RequestSize is a middleware that will limit request sizes to a specified +// number of bytes. It uses MaxBytesReader to do so. +func RequestSize(bytes int64) func(http.Handler) http.Handler { + f := func(h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + r.Body = http.MaxBytesReader(w, r.Body, bytes) + h.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) + } + return f +} diff --git a/vendor/github.com/go-chi/chi/v5/middleware/route_headers.go b/vendor/github.com/go-chi/chi/v5/middleware/route_headers.go new file mode 100644 index 0000000..997bed5 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/middleware/route_headers.go @@ -0,0 +1,151 @@ +package middleware + +import ( + "net/http" + "strings" +) + +// RouteHeaders is a neat little header-based router that allows you to direct +// the flow of a request through a middleware stack based on a request header. +// +// For example, lets say you'd like to setup multiple routers depending on the +// request Host header, you could then do something as so: +// +// r := chi.NewRouter() +// rSubdomain := chi.NewRouter() +// r.Use(middleware.RouteHeaders(). +// Route("Host", "example.com", middleware.New(r)). +// Route("Host", "*.example.com", middleware.New(rSubdomain)). +// Handler) +// r.Get("/", h) +// rSubdomain.Get("/", h2) +// +// Another example, imagine you want to setup multiple CORS handlers, where for +// your origin servers you allow authorized requests, but for third-party public +// requests, authorization is disabled. +// +// r := chi.NewRouter() +// r.Use(middleware.RouteHeaders(). +// Route("Origin", "https://app.skyweaver.net", cors.Handler(cors.Options{ +// AllowedOrigins: []string{"https://api.skyweaver.net"}, +// AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, +// AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"}, +// AllowCredentials: true, // <----------<<< allow credentials +// })). +// Route("Origin", "*", cors.Handler(cors.Options{ +// AllowedOrigins: []string{"*"}, +// AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, +// AllowedHeaders: []string{"Accept", "Content-Type"}, +// AllowCredentials: false, // <----------<<< do not allow credentials +// })). +// Handler) +func RouteHeaders() HeaderRouter { + return HeaderRouter{} +} + +type HeaderRouter map[string][]HeaderRoute + +func (hr HeaderRouter) Route(header, match string, middlewareHandler func(next http.Handler) http.Handler) HeaderRouter { + header = strings.ToLower(header) + k := hr[header] + if k == nil { + hr[header] = []HeaderRoute{} + } + hr[header] = append(hr[header], HeaderRoute{MatchOne: NewPattern(match), Middleware: middlewareHandler}) + return hr +} + +func (hr HeaderRouter) RouteAny(header string, match []string, middlewareHandler func(next http.Handler) http.Handler) HeaderRouter { + header = strings.ToLower(header) + k := hr[header] + if k == nil { + hr[header] = []HeaderRoute{} + } + patterns := []Pattern{} + for _, m := range match { + patterns = append(patterns, NewPattern(m)) + } + hr[header] = append(hr[header], HeaderRoute{MatchAny: patterns, Middleware: middlewareHandler}) + return hr +} + +func (hr HeaderRouter) RouteDefault(handler func(next http.Handler) http.Handler) HeaderRouter { + hr["*"] = []HeaderRoute{{Middleware: handler}} + return hr +} + +func (hr HeaderRouter) Handler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if len(hr) == 0 { + // skip if no routes set + next.ServeHTTP(w, r) + } + + // find first matching header route, and continue + for header, matchers := range hr { + headerValue := r.Header.Get(header) + if headerValue == "" { + continue + } + headerValue = strings.ToLower(headerValue) + for _, matcher := range matchers { + if matcher.IsMatch(headerValue) { + matcher.Middleware(next).ServeHTTP(w, r) + return + } + } + } + + // if no match, check for "*" default route + matcher, ok := hr["*"] + if !ok || matcher[0].Middleware == nil { + next.ServeHTTP(w, r) + return + } + matcher[0].Middleware(next).ServeHTTP(w, r) + }) +} + +type HeaderRoute struct { + Middleware func(next http.Handler) http.Handler + MatchOne Pattern + MatchAny []Pattern +} + +func (r HeaderRoute) IsMatch(value string) bool { + if len(r.MatchAny) > 0 { + for _, m := range r.MatchAny { + if m.Match(value) { + return true + } + } + } else if r.MatchOne.Match(value) { + return true + } + return false +} + +type Pattern struct { + prefix string + suffix string + wildcard bool +} + +func NewPattern(value string) Pattern { + p := Pattern{} + if i := strings.IndexByte(value, '*'); i >= 0 { + p.wildcard = true + p.prefix = value[0:i] + p.suffix = value[i+1:] + } else { + p.prefix = value + } + return p +} + +func (p Pattern) Match(v string) bool { + if !p.wildcard { + return p.prefix == v + } + return len(v) >= len(p.prefix+p.suffix) && strings.HasPrefix(v, p.prefix) && strings.HasSuffix(v, p.suffix) +} diff --git a/vendor/github.com/go-chi/chi/middleware/strip.go b/vendor/github.com/go-chi/chi/v5/middleware/strip.go similarity index 80% rename from vendor/github.com/go-chi/chi/middleware/strip.go rename to vendor/github.com/go-chi/chi/v5/middleware/strip.go index 2b8b184..ce8ebfc 100644 --- a/vendor/github.com/go-chi/chi/middleware/strip.go +++ b/vendor/github.com/go-chi/chi/v5/middleware/strip.go @@ -4,7 +4,7 @@ import ( "fmt" "net/http" - "github.com/go-chi/chi" + "github.com/go-chi/chi/v5" ) // StripSlashes is a middleware that will match request paths with a trailing @@ -14,13 +14,18 @@ func StripSlashes(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { var path string rctx := chi.RouteContext(r.Context()) - if rctx.RoutePath != "" { + if rctx != nil && rctx.RoutePath != "" { path = rctx.RoutePath } else { path = r.URL.Path } if len(path) > 1 && path[len(path)-1] == '/' { - rctx.RoutePath = path[:len(path)-1] + newPath := path[:len(path)-1] + if rctx == nil { + r.URL.Path = newPath + } else { + rctx.RoutePath = newPath + } } next.ServeHTTP(w, r) } @@ -36,7 +41,7 @@ func RedirectSlashes(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { var path string rctx := chi.RouteContext(r.Context()) - if rctx.RoutePath != "" { + if rctx != nil && rctx.RoutePath != "" { path = rctx.RoutePath } else { path = r.URL.Path @@ -47,7 +52,8 @@ func RedirectSlashes(next http.Handler) http.Handler { } else { path = path[:len(path)-1] } - http.Redirect(w, r, path, 301) + redirectURL := fmt.Sprintf("//%s%s", r.Host, path) + http.Redirect(w, r, redirectURL, 301) return } next.ServeHTTP(w, r) diff --git a/vendor/github.com/go-chi/chi/v5/middleware/sunset.go b/vendor/github.com/go-chi/chi/v5/middleware/sunset.go new file mode 100644 index 0000000..18815d5 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/middleware/sunset.go @@ -0,0 +1,25 @@ +package middleware + +import ( + "net/http" + "time" +) + +// Sunset set Deprecation/Sunset header to response +// This can be used to enable Sunset in a route or a route group +// For more: https://www.rfc-editor.org/rfc/rfc8594.html +func Sunset(sunsetAt time.Time, links ...string) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !sunsetAt.IsZero() { + w.Header().Set("Sunset", sunsetAt.Format(http.TimeFormat)) + w.Header().Set("Deprecation", sunsetAt.Format(http.TimeFormat)) + + for _, link := range links { + w.Header().Add("Link", link) + } + } + next.ServeHTTP(w, r) + }) + } +} diff --git a/vendor/github.com/go-chi/chi/v5/middleware/supress_notfound.go b/vendor/github.com/go-chi/chi/v5/middleware/supress_notfound.go new file mode 100644 index 0000000..83a8a87 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/middleware/supress_notfound.go @@ -0,0 +1,27 @@ +package middleware + +import ( + "net/http" + + "github.com/go-chi/chi/v5" +) + +// SupressNotFound will quickly respond with a 404 if the route is not found +// and will not continue to the next middleware handler. +// +// This is handy to put at the top of your middleware stack to avoid unnecessary +// processing of requests that are not going to match any routes anyway. For +// example its super annoying to see a bunch of 404's in your logs from bots. +func SupressNotFound(router *chi.Mux) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + rctx := chi.RouteContext(r.Context()) + match := rctx.Routes.Match(rctx, r.Method, r.URL.Path) + if !match { + router.NotFoundHandler().ServeHTTP(w, r) + return + } + next.ServeHTTP(w, r) + }) + } +} diff --git a/vendor/github.com/go-chi/chi/middleware/terminal.go b/vendor/github.com/go-chi/chi/v5/middleware/terminal.go similarity index 95% rename from vendor/github.com/go-chi/chi/middleware/terminal.go rename to vendor/github.com/go-chi/chi/v5/middleware/terminal.go index a5d4241..5ead7b9 100644 --- a/vendor/github.com/go-chi/chi/middleware/terminal.go +++ b/vendor/github.com/go-chi/chi/v5/middleware/terminal.go @@ -32,7 +32,7 @@ var ( reset = []byte{'\033', '[', '0', 'm'} ) -var isTTY bool +var IsTTY bool func init() { // This is sort of cheating: if stdout is a character device, we assume @@ -47,17 +47,17 @@ func init() { fi, err := os.Stdout.Stat() if err == nil { m := os.ModeDevice | os.ModeCharDevice - isTTY = fi.Mode()&m == m + IsTTY = fi.Mode()&m == m } } // colorWrite func cW(w io.Writer, useColor bool, color []byte, s string, args ...interface{}) { - if isTTY && useColor { + if IsTTY && useColor { w.Write(color) } fmt.Fprintf(w, s, args...) - if isTTY && useColor { + if IsTTY && useColor { w.Write(reset) } } diff --git a/vendor/github.com/go-chi/chi/v5/middleware/throttle.go b/vendor/github.com/go-chi/chi/v5/middleware/throttle.go new file mode 100644 index 0000000..bdf4f9f --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/middleware/throttle.go @@ -0,0 +1,132 @@ +package middleware + +import ( + "net/http" + "strconv" + "time" +) + +const ( + errCapacityExceeded = "Server capacity exceeded." + errTimedOut = "Timed out while waiting for a pending request to complete." + errContextCanceled = "Context was canceled." +) + +var ( + defaultBacklogTimeout = time.Second * 60 +) + +// ThrottleOpts represents a set of throttling options. +type ThrottleOpts struct { + RetryAfterFn func(ctxDone bool) time.Duration + Limit int + BacklogLimit int + BacklogTimeout time.Duration +} + +// Throttle is a middleware that limits number of currently processed requests +// at a time across all users. Note: Throttle is not a rate-limiter per user, +// instead it just puts a ceiling on the number of currently in-flight requests +// being processed from the point from where the Throttle middleware is mounted. +func Throttle(limit int) func(http.Handler) http.Handler { + return ThrottleWithOpts(ThrottleOpts{Limit: limit, BacklogTimeout: defaultBacklogTimeout}) +} + +// ThrottleBacklog is a middleware that limits number of currently processed +// requests at a time and provides a backlog for holding a finite number of +// pending requests. +func ThrottleBacklog(limit, backlogLimit int, backlogTimeout time.Duration) func(http.Handler) http.Handler { + return ThrottleWithOpts(ThrottleOpts{Limit: limit, BacklogLimit: backlogLimit, BacklogTimeout: backlogTimeout}) +} + +// ThrottleWithOpts is a middleware that limits number of currently processed requests using passed ThrottleOpts. +func ThrottleWithOpts(opts ThrottleOpts) func(http.Handler) http.Handler { + if opts.Limit < 1 { + panic("chi/middleware: Throttle expects limit > 0") + } + + if opts.BacklogLimit < 0 { + panic("chi/middleware: Throttle expects backlogLimit to be positive") + } + + t := throttler{ + tokens: make(chan token, opts.Limit), + backlogTokens: make(chan token, opts.Limit+opts.BacklogLimit), + backlogTimeout: opts.BacklogTimeout, + retryAfterFn: opts.RetryAfterFn, + } + + // Filling tokens. + for i := 0; i < opts.Limit+opts.BacklogLimit; i++ { + if i < opts.Limit { + t.tokens <- token{} + } + t.backlogTokens <- token{} + } + + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + select { + + case <-ctx.Done(): + t.setRetryAfterHeaderIfNeeded(w, true) + http.Error(w, errContextCanceled, http.StatusTooManyRequests) + return + + case btok := <-t.backlogTokens: + timer := time.NewTimer(t.backlogTimeout) + + defer func() { + t.backlogTokens <- btok + }() + + select { + case <-timer.C: + t.setRetryAfterHeaderIfNeeded(w, false) + http.Error(w, errTimedOut, http.StatusTooManyRequests) + return + case <-ctx.Done(): + timer.Stop() + t.setRetryAfterHeaderIfNeeded(w, true) + http.Error(w, errContextCanceled, http.StatusTooManyRequests) + return + case tok := <-t.tokens: + defer func() { + timer.Stop() + t.tokens <- tok + }() + next.ServeHTTP(w, r) + } + return + + default: + t.setRetryAfterHeaderIfNeeded(w, false) + http.Error(w, errCapacityExceeded, http.StatusTooManyRequests) + return + } + } + + return http.HandlerFunc(fn) + } +} + +// token represents a request that is being processed. +type token struct{} + +// throttler limits number of currently processed requests at a time. +type throttler struct { + tokens chan token + backlogTokens chan token + retryAfterFn func(ctxDone bool) time.Duration + backlogTimeout time.Duration +} + +// setRetryAfterHeaderIfNeeded sets Retry-After HTTP header if corresponding retryAfterFn option of throttler is initialized. +func (t throttler) setRetryAfterHeaderIfNeeded(w http.ResponseWriter, ctxDone bool) { + if t.retryAfterFn == nil { + return + } + w.Header().Set("Retry-After", strconv.Itoa(int(t.retryAfterFn(ctxDone).Seconds()))) +} diff --git a/vendor/github.com/go-chi/chi/middleware/timeout.go b/vendor/github.com/go-chi/chi/v5/middleware/timeout.go similarity index 72% rename from vendor/github.com/go-chi/chi/middleware/timeout.go rename to vendor/github.com/go-chi/chi/v5/middleware/timeout.go index 8e37353..add596d 100644 --- a/vendor/github.com/go-chi/chi/middleware/timeout.go +++ b/vendor/github.com/go-chi/chi/v5/middleware/timeout.go @@ -15,21 +15,20 @@ import ( // // ie. a route/handler may look like: // -// r.Get("/long", func(w http.ResponseWriter, r *http.Request) { -// ctx := r.Context() -// processTime := time.Duration(rand.Intn(4)+1) * time.Second +// r.Get("/long", func(w http.ResponseWriter, r *http.Request) { +// ctx := r.Context() +// processTime := time.Duration(rand.Intn(4)+1) * time.Second // -// select { -// case <-ctx.Done(): -// return +// select { +// case <-ctx.Done(): +// return // -// case <-time.After(processTime): -// // The above channel simulates some hard work. -// } -// -// w.Write([]byte("done")) -// }) +// case <-time.After(processTime): +// // The above channel simulates some hard work. +// } // +// w.Write([]byte("done")) +// }) func Timeout(timeout time.Duration) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { diff --git a/vendor/github.com/go-chi/chi/middleware/url_format.go b/vendor/github.com/go-chi/chi/v5/middleware/url_format.go similarity index 58% rename from vendor/github.com/go-chi/chi/middleware/url_format.go rename to vendor/github.com/go-chi/chi/v5/middleware/url_format.go index 5749e4f..d8a651e 100644 --- a/vendor/github.com/go-chi/chi/middleware/url_format.go +++ b/vendor/github.com/go-chi/chi/v5/middleware/url_format.go @@ -5,7 +5,7 @@ import ( "net/http" "strings" - "github.com/go-chi/chi" + "github.com/go-chi/chi/v5" ) var ( @@ -20,30 +20,29 @@ var ( // // Routers should not include a url parameter for the suffix when using this middleware. // -// Sample usage.. for url paths: `/articles/1`, `/articles/1.json` and `/articles/1.xml` +// Sample usage for url paths `/articles/1`, `/articles/1.json` and `/articles/1.xml`: // -// func routes() http.Handler { -// r := chi.NewRouter() -// r.Use(middleware.URLFormat) +// func routes() http.Handler { +// r := chi.NewRouter() +// r.Use(middleware.URLFormat) // -// r.Get("/articles/{id}", ListArticles) +// r.Get("/articles/{id}", ListArticles) // -// return r -// } +// return r +// } // -// func ListArticles(w http.ResponseWriter, r *http.Request) { -// urlFormat, _ := r.Context().Value(middleware.URLFormatCtxKey).(string) -// -// switch urlFormat { -// case "json": -// render.JSON(w, r, articles) -// case "xml:" -// render.XML(w, r, articles) -// default: -// render.JSON(w, r, articles) -// } -// } +// func ListArticles(w http.ResponseWriter, r *http.Request) { +// urlFormat, _ := r.Context().Value(middleware.URLFormatCtxKey).(string) // +// switch urlFormat { +// case "json": +// render.JSON(w, r, articles) +// case "xml:" +// render.XML(w, r, articles) +// default: +// render.JSON(w, r, articles) +// } +// } func URLFormat(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -51,15 +50,19 @@ func URLFormat(next http.Handler) http.Handler { var format string path := r.URL.Path + rctx := chi.RouteContext(r.Context()) + if rctx != nil && rctx.RoutePath != "" { + path = rctx.RoutePath + } + if strings.Index(path, ".") > 0 { base := strings.LastIndex(path, "/") - idx := strings.Index(path[base:], ".") + idx := strings.LastIndex(path[base:], ".") if idx > 0 { idx += base format = path[idx+1:] - rctx := chi.RouteContext(r.Context()) rctx.RoutePath = path[:idx] } } diff --git a/vendor/github.com/go-chi/chi/middleware/value.go b/vendor/github.com/go-chi/chi/v5/middleware/value.go similarity index 80% rename from vendor/github.com/go-chi/chi/middleware/value.go rename to vendor/github.com/go-chi/chi/v5/middleware/value.go index fbbd039..a9dfd43 100644 --- a/vendor/github.com/go-chi/chi/middleware/value.go +++ b/vendor/github.com/go-chi/chi/v5/middleware/value.go @@ -6,7 +6,7 @@ import ( ) // WithValue is a middleware that sets a given key/value in a context chain. -func WithValue(key interface{}, val interface{}) func(next http.Handler) http.Handler { +func WithValue(key, val interface{}) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { r = r.WithContext(context.WithValue(r.Context(), key, val)) diff --git a/vendor/github.com/go-chi/chi/middleware/wrap_writer.go b/vendor/github.com/go-chi/chi/v5/middleware/wrap_writer.go similarity index 73% rename from vendor/github.com/go-chi/chi/middleware/wrap_writer.go rename to vendor/github.com/go-chi/chi/v5/middleware/wrap_writer.go index 5e5594f..bf27088 100644 --- a/vendor/github.com/go-chi/chi/middleware/wrap_writer.go +++ b/vendor/github.com/go-chi/chi/v5/middleware/wrap_writer.go @@ -6,6 +6,7 @@ package middleware import ( "bufio" "io" + "io/ioutil" "net" "net/http" ) @@ -28,7 +29,14 @@ func NewWrapResponseWriter(w http.ResponseWriter, protoMajor int) WrapResponseWr if fl && hj && rf { return &httpFancyWriter{bw} } + if fl && hj { + return &flushHijackWriter{bw} + } + if hj { + return &hijackWriter{bw} + } } + if fl { return &flushWriter{bw} } @@ -54,6 +62,11 @@ type WrapResponseWriter interface { Tee(io.Writer) // Unwrap returns the original proxied target. Unwrap() http.ResponseWriter + // Discard causes all writes to the original ResponseWriter be discarded, + // instead writing only to the tee'd writer if it's set. + // The caller is responsible for calling WriteHeader and Write on the + // original ResponseWriter once the processing is done. + Discard() } // basicWriter wraps a http.ResponseWriter that implements the minimal @@ -64,25 +77,34 @@ type basicWriter struct { code int bytes int tee io.Writer + discard bool } func (b *basicWriter) WriteHeader(code int) { if !b.wroteHeader { b.code = code b.wroteHeader = true - b.ResponseWriter.WriteHeader(code) + if !b.discard { + b.ResponseWriter.WriteHeader(code) + } } } -func (b *basicWriter) Write(buf []byte) (int, error) { - b.WriteHeader(http.StatusOK) - n, err := b.ResponseWriter.Write(buf) - if b.tee != nil { - _, err2 := b.tee.Write(buf[:n]) - // Prefer errors generated by the proxied writer. - if err == nil { - err = err2 +func (b *basicWriter) Write(buf []byte) (n int, err error) { + b.maybeWriteHeader() + if !b.discard { + n, err = b.ResponseWriter.Write(buf) + if b.tee != nil { + _, err2 := b.tee.Write(buf[:n]) + // Prefer errors generated by the proxied writer. + if err == nil { + err = err2 + } } + } else if b.tee != nil { + n, err = b.tee.Write(buf) + } else { + n, err = ioutil.Discard.Write(buf) } b.bytes += n return n, err @@ -110,19 +132,54 @@ func (b *basicWriter) Unwrap() http.ResponseWriter { return b.ResponseWriter } +func (b *basicWriter) Discard() { + b.discard = true +} + +// flushWriter ... type flushWriter struct { basicWriter } func (f *flushWriter) Flush() { f.wroteHeader = true - fl := f.basicWriter.ResponseWriter.(http.Flusher) fl.Flush() } var _ http.Flusher = &flushWriter{} +// hijackWriter ... +type hijackWriter struct { + basicWriter +} + +func (f *hijackWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + hj := f.basicWriter.ResponseWriter.(http.Hijacker) + return hj.Hijack() +} + +var _ http.Hijacker = &hijackWriter{} + +// flushHijackWriter ... +type flushHijackWriter struct { + basicWriter +} + +func (f *flushHijackWriter) Flush() { + f.wroteHeader = true + fl := f.basicWriter.ResponseWriter.(http.Flusher) + fl.Flush() +} + +func (f *flushHijackWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + hj := f.basicWriter.ResponseWriter.(http.Hijacker) + return hj.Hijack() +} + +var _ http.Flusher = &flushHijackWriter{} +var _ http.Hijacker = &flushHijackWriter{} + // httpFancyWriter is a HTTP writer that additionally satisfies // http.Flusher, http.Hijacker, and io.ReaderFrom. It exists for the common case // of wrapping the http.ResponseWriter that package http gives you, in order to @@ -133,7 +190,6 @@ type httpFancyWriter struct { func (f *httpFancyWriter) Flush() { f.wroteHeader = true - fl := f.basicWriter.ResponseWriter.(http.Flusher) fl.Flush() } @@ -175,7 +231,6 @@ type http2FancyWriter struct { func (f *http2FancyWriter) Flush() { f.wroteHeader = true - fl := f.basicWriter.ResponseWriter.(http.Flusher) fl.Flush() } diff --git a/vendor/github.com/go-chi/chi/mux.go b/vendor/github.com/go-chi/chi/v5/mux.go similarity index 84% rename from vendor/github.com/go-chi/chi/mux.go rename to vendor/github.com/go-chi/chi/v5/mux.go index e553287..6dc1904 100644 --- a/vendor/github.com/go-chi/chi/mux.go +++ b/vendor/github.com/go-chi/chi/v5/mux.go @@ -19,29 +19,32 @@ var _ Router = &Mux{} // particularly useful for writing large REST API services that break a handler // into many smaller parts composed of middlewares and end handlers. type Mux struct { + // The computed mux handler made of the chained middleware stack and + // the tree router + handler http.Handler + // The radix trie router tree *node - // The middleware stack - middlewares []func(http.Handler) http.Handler + // Custom method not allowed handler + methodNotAllowedHandler http.HandlerFunc - // Controls the behaviour of middleware chain generation when a mux - // is registered as an inline group inside another mux. - inline bool + // A reference to the parent mux used by subrouters when mounting + // to a parent mux parent *Mux - // The computed mux handler made of the chained middleware stack and - // the tree router - handler http.Handler - // Routing context pool pool *sync.Pool // Custom route not found handler notFoundHandler http.HandlerFunc - // Custom method not allowed handler - methodNotAllowedHandler http.HandlerFunc + // The middleware stack + middlewares []func(http.Handler) http.Handler + + // Controls the behaviour of middleware chain generation when a mux + // is registered as an inline group inside another mux. + inline bool } // NewMux returns a newly initialized Mux object that implements the Router @@ -78,7 +81,12 @@ func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { rctx = mx.pool.Get().(*Context) rctx.Reset() rctx.Routes = mx + rctx.parentCtx = r.Context() + + // NOTE: r.WithContext() causes 2 allocations and context.WithValue() causes 1 allocation r = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx)) + + // Serve the request and once its done, put the request context back in the sync pool mx.handler.ServeHTTP(w, r) mx.pool.Put(rctx) } @@ -99,12 +107,24 @@ func (mx *Mux) Use(middlewares ...func(http.Handler) http.Handler) { // Handle adds the route `pattern` that matches any http method to // execute the `handler` http.Handler. func (mx *Mux) Handle(pattern string, handler http.Handler) { + parts := strings.SplitN(pattern, " ", 2) + if len(parts) == 2 { + mx.Method(parts[0], parts[1], handler) + return + } + mx.handle(mALL, pattern, handler) } // HandleFunc adds the route `pattern` that matches any http method to // execute the `handlerFn` http.HandlerFunc. func (mx *Mux) HandleFunc(pattern string, handlerFn http.HandlerFunc) { + parts := strings.SplitN(pattern, " ", 2) + if len(parts) == 2 { + mx.Method(parts[0], parts[1], handlerFn) + return + } + mx.handle(mALL, pattern, handlerFn) } @@ -148,7 +168,7 @@ func (mx *Mux) Head(pattern string, handlerFn http.HandlerFunc) { mx.handle(mHEAD, pattern, handlerFn) } -// Options adds the route `pattern` that matches a OPTIONS http method to +// Options adds the route `pattern` that matches an OPTIONS http method to // execute the `handlerFn` http.HandlerFunc. func (mx *Mux) Options(pattern string, handlerFn http.HandlerFunc) { mx.handle(mOPTIONS, pattern, handlerFn) @@ -220,10 +240,10 @@ func (mx *Mux) MethodNotAllowed(handlerFn http.HandlerFunc) { // With adds inline middlewares for an endpoint handler. func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router { - // Similarly as in handle(), we must build the mux handler once further + // Similarly as in handle(), we must build the mux handler once additional // middleware registration isn't allowed for this stack, like now. if !mx.inline && mx.handler == nil { - mx.buildRouteHandler() + mx.updateRouteHandler() } // Copy middlewares from parent inline muxs @@ -234,30 +254,33 @@ func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router { } mws = append(mws, middlewares...) - im := &Mux{pool: mx.pool, inline: true, parent: mx, tree: mx.tree, middlewares: mws} + im := &Mux{ + pool: mx.pool, inline: true, parent: mx, tree: mx.tree, middlewares: mws, + notFoundHandler: mx.notFoundHandler, methodNotAllowedHandler: mx.methodNotAllowedHandler, + } return im } -// Group creates a new inline-Mux with a fresh middleware stack. It's useful +// Group creates a new inline-Mux with a copy of middleware stack. It's useful // for a group of handlers along the same routing path that use an additional // set of middlewares. See _examples/. func (mx *Mux) Group(fn func(r Router)) Router { - im := mx.With().(*Mux) + im := mx.With() if fn != nil { fn(im) } return im } -// Route creates a new Mux with a fresh middleware stack and mounts it -// along the `pattern` as a subrouter. Effectively, this is a short-hand -// call to Mount. See _examples/. +// Route creates a new Mux and mounts it along the `pattern` as a subrouter. +// Effectively, this is a short-hand call to Mount. See _examples/. func (mx *Mux) Route(pattern string, fn func(r Router)) Router { - subRouter := NewRouter() - if fn != nil { - fn(subRouter) + if fn == nil { + panic(fmt.Sprintf("chi: attempting to Route() a nil subrouter on '%s'", pattern)) } + subRouter := NewRouter() + fn(subRouter) mx.Mount(pattern, subRouter) return subRouter } @@ -270,6 +293,10 @@ func (mx *Mux) Route(pattern string, fn func(r Router)) Router { // routing at the `handler`, which in most cases is another chi.Router. As a result, // if you define two Mount() routes on the exact same pattern the mount will panic. func (mx *Mux) Mount(pattern string, handler http.Handler) { + if handler == nil { + panic(fmt.Sprintf("chi: attempting to Mount() a nil handler on '%s'", pattern)) + } + // Provide runtime safety for ensuring a pattern isn't mounted on an existing // routing pattern. if mx.tree.findPattern(pattern+"*") || mx.tree.findPattern(pattern+"/*") { @@ -285,10 +312,18 @@ func (mx *Mux) Mount(pattern string, handler http.Handler) { subr.MethodNotAllowed(mx.methodNotAllowedHandler) } - // Wrap the sub-router in a handlerFunc to scope the request path for routing. mountHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rctx := RouteContext(r.Context()) + + // shift the url path past the previous subrouter rctx.RoutePath = mx.nextRoutePath(rctx) + + // reset the wildcard URLParam which connects the subrouter + n := len(rctx.URLParams.Keys) - 1 + if n >= 0 && rctx.URLParams.Keys[n] == "*" && len(rctx.URLParams.Values) > n { + rctx.URLParams.Values[n] = "" + } + handler.ServeHTTP(w, r) }) @@ -354,19 +389,11 @@ func (mx *Mux) NotFoundHandler() http.HandlerFunc { // MethodNotAllowedHandler returns the default Mux 405 responder whenever // a method cannot be resolved for a route. -func (mx *Mux) MethodNotAllowedHandler() http.HandlerFunc { +func (mx *Mux) MethodNotAllowedHandler(methodsAllowed ...methodTyp) http.HandlerFunc { if mx.methodNotAllowedHandler != nil { return mx.methodNotAllowedHandler } - return methodNotAllowedHandler -} - -// buildRouteHandler builds the single mux handler that is a chain of the middleware -// stack, as defined by calls to Use(), and the tree router (Mux) itself. After this -// point, no other middlewares can be registered on this Mux's stack. But you can still -// compose additional middlewares via Group()'s or using a chained middleware handler. -func (mx *Mux) buildRouteHandler() { - mx.handler = chain(mx.middlewares, http.HandlerFunc(mx.routeHTTP)) + return methodNotAllowedHandler(methodsAllowed...) } // handle registers a http.Handler in the routing tree for a particular http method @@ -376,9 +403,9 @@ func (mx *Mux) handle(method methodTyp, pattern string, handler http.Handler) *n panic(fmt.Sprintf("chi: routing pattern must begin with '/' in '%s'", pattern)) } - // Build the final routing handler for this Mux. + // Build the computed routing handler for this routing pattern. if !mx.inline && mx.handler == nil { - mx.buildRouteHandler() + mx.updateRouteHandler() } // Build endpoint handler with inline middlewares for the route @@ -408,6 +435,9 @@ func (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) { } else { routePath = r.URL.Path } + if routePath == "" { + routePath = "/" + } } // Check if method is supported by chi @@ -422,11 +452,15 @@ func (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) { // Find the route if _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil { + if supportsPathValue { + setPathValue(rctx, r) + } + h.ServeHTTP(w, r) return } if rctx.methodNotAllowed { - mx.MethodNotAllowedHandler().ServeHTTP(w, r) + mx.MethodNotAllowedHandler(rctx.methodsAllowed...).ServeHTTP(w, r) } else { mx.NotFoundHandler().ServeHTTP(w, r) } @@ -436,7 +470,7 @@ func (mx *Mux) nextRoutePath(rctx *Context) string { routePath := "/" nx := len(rctx.routeParams.Keys) - 1 // index of last param in list if nx >= 0 && rctx.routeParams.Keys[nx] == "*" && len(rctx.routeParams.Values) > nx { - routePath += rctx.routeParams.Values[nx] + routePath = "/" + rctx.routeParams.Values[nx] } return routePath } @@ -452,9 +486,23 @@ func (mx *Mux) updateSubRoutes(fn func(subMux *Mux)) { } } +// updateRouteHandler builds the single mux handler that is a chain of the middleware +// stack, as defined by calls to Use(), and the tree router (Mux) itself. After this +// point, no other middlewares can be registered on this Mux's stack. But you can still +// compose additional middlewares via Group()'s or using a chained middleware handler. +func (mx *Mux) updateRouteHandler() { + mx.handler = chain(mx.middlewares, http.HandlerFunc(mx.routeHTTP)) +} + // methodNotAllowedHandler is a helper function to respond with a 405, -// method not allowed. -func methodNotAllowedHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(405) - w.Write(nil) +// method not allowed. It sets the Allow header with the list of allowed +// methods for the route. +func methodNotAllowedHandler(methodsAllowed ...methodTyp) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + for _, m := range methodsAllowed { + w.Header().Add("Allow", reverseMethodMap[m]) + } + w.WriteHeader(405) + w.Write(nil) + } } diff --git a/vendor/github.com/go-chi/chi/v5/path_value.go b/vendor/github.com/go-chi/chi/v5/path_value.go new file mode 100644 index 0000000..7e78171 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/path_value.go @@ -0,0 +1,20 @@ +//go:build go1.22 +// +build go1.22 + +package chi + +import "net/http" + +// supportsPathValue is true if the Go version is 1.22 and above. +// +// If this is true, `net/http.Request` has methods `SetPathValue` and `PathValue`. +const supportsPathValue = true + +// setPathValue sets the path values in the Request value +// based on the provided request context. +func setPathValue(rctx *Context, r *http.Request) { + for i, key := range rctx.URLParams.Keys { + value := rctx.URLParams.Values[i] + r.SetPathValue(key, value) + } +} diff --git a/vendor/github.com/go-chi/chi/v5/path_value_fallback.go b/vendor/github.com/go-chi/chi/v5/path_value_fallback.go new file mode 100644 index 0000000..f551781 --- /dev/null +++ b/vendor/github.com/go-chi/chi/v5/path_value_fallback.go @@ -0,0 +1,19 @@ +//go:build !go1.22 +// +build !go1.22 + +package chi + +import "net/http" + +// supportsPathValue is true if the Go version is 1.22 and above. +// +// If this is true, `net/http.Request` has methods `SetPathValue` and `PathValue`. +const supportsPathValue = false + +// setPathValue sets the path values in the Request value +// based on the provided request context. +// +// setPathValue is only supported in Go 1.22 and above so +// this is just a blank function so that it compiles. +func setPathValue(rctx *Context, r *http.Request) { +} diff --git a/vendor/github.com/go-chi/chi/tree.go b/vendor/github.com/go-chi/chi/v5/tree.go similarity index 90% rename from vendor/github.com/go-chi/chi/tree.go rename to vendor/github.com/go-chi/chi/v5/tree.go index 8a044f3..c7d3bc5 100644 --- a/vendor/github.com/go-chi/chi/tree.go +++ b/vendor/github.com/go-chi/chi/v5/tree.go @@ -6,7 +6,6 @@ package chi import ( "fmt" - "math" "net/http" "regexp" "sort" @@ -14,7 +13,7 @@ import ( "strings" ) -type methodTyp int +type methodTyp uint const ( mSTUB methodTyp = 1 << iota @@ -44,6 +43,18 @@ var methodMap = map[string]methodTyp{ http.MethodTrace: mTRACE, } +var reverseMethodMap = map[methodTyp]string{ + mCONNECT: http.MethodConnect, + mDELETE: http.MethodDelete, + mGET: http.MethodGet, + mHEAD: http.MethodHead, + mOPTIONS: http.MethodOptions, + mPATCH: http.MethodPatch, + mPOST: http.MethodPost, + mPUT: http.MethodPut, + mTRACE: http.MethodTrace, +} + // RegisterMethod adds support for custom HTTP method handlers, available // via Router#Method and Router#MethodFunc func RegisterMethod(method string) { @@ -55,10 +66,10 @@ func RegisterMethod(method string) { return } n := len(methodMap) - if n > strconv.IntSize { + if n > strconv.IntSize-2 { panic(fmt.Sprintf("chi: max number of methods reached (%d)", strconv.IntSize)) } - mt := methodTyp(math.Exp2(float64(n))) + mt := methodTyp(2 << n) methodMap[method] = mt mALL |= mt } @@ -73,17 +84,8 @@ const ( ) type node struct { - // node type: static, regexp, param, catchAll - typ nodeTyp - - // first byte of the prefix - label byte - - // first byte of the child prefix - tail byte - - // prefix is the common prefix we ignore - prefix string + // subroutes on the leaf node + subroutes Routes // regexp matcher for regexp nodes rex *regexp.Regexp @@ -91,12 +93,21 @@ type node struct { // HTTP handler endpoints on the leaf node endpoints endpoints - // subroutes on the leaf node - subroutes Routes + // prefix is the common prefix we ignore + prefix string // child nodes should be stored in-order for iteration, // in groups of the node type. children [ntCatchAll + 1]nodes + + // first byte of the child prefix + tail byte + + // node type: static, regexp, param, catchAll + typ nodeTyp + + // first byte of the prefix + label byte } // endpoints is a mapping of http method constants to handlers @@ -331,7 +342,7 @@ func (n *node) getEdge(ntyp nodeTyp, label, tail byte, prefix string) *node { func (n *node) setEndpoint(method methodTyp, handler http.Handler, pattern string) { // Set the handler for the method type on the node if n.endpoints == nil { - n.endpoints = make(endpoints, 0) + n.endpoints = make(endpoints) } paramKeys := patParamKeys(pattern) @@ -430,10 +441,12 @@ func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node { } else { continue } + } else if ntyp == ntRegexp && p == 0 { + continue } if ntyp == ntRegexp && xn.rex != nil { - if xn.rex.Match([]byte(xsearch[:p])) == false { + if !xn.rex.MatchString(xsearch[:p]) { continue } } else if strings.IndexByte(xsearch[:p], '/') != -1 { @@ -441,11 +454,44 @@ func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node { continue } + prevlen := len(rctx.routeParams.Values) rctx.routeParams.Values = append(rctx.routeParams.Values, xsearch[:p]) xsearch = xsearch[p:] - break + + if len(xsearch) == 0 { + if xn.isLeaf() { + h := xn.endpoints[method] + if h != nil && h.handler != nil { + rctx.routeParams.Keys = append(rctx.routeParams.Keys, h.paramKeys...) + return xn + } + + for endpoints := range xn.endpoints { + if endpoints == mALL || endpoints == mSTUB { + continue + } + rctx.methodsAllowed = append(rctx.methodsAllowed, endpoints) + } + + // flag that the routing context found a route, but not a corresponding + // supported method + rctx.methodNotAllowed = true + } + } + + // recursively find the next node on this branch + fin := xn.findRoute(rctx, method, xsearch) + if fin != nil { + return fin + } + + // not found on this branch, reset vars + rctx.routeParams.Values = rctx.routeParams.Values[:prevlen] + xsearch = search } + rctx.routeParams.Values = append(rctx.routeParams.Values, "") + default: // catch-all nodes rctx.routeParams.Values = append(rctx.routeParams.Values, search) @@ -460,12 +506,19 @@ func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node { // did we find it yet? if len(xsearch) == 0 { if xn.isLeaf() { - h, _ := xn.endpoints[method] + h := xn.endpoints[method] if h != nil && h.handler != nil { rctx.routeParams.Keys = append(rctx.routeParams.Keys, h.paramKeys...) return xn } + for endpoints := range xn.endpoints { + if endpoints == mALL || endpoints == mSTUB { + continue + } + rctx.methodsAllowed = append(rctx.methodsAllowed, endpoints) + } + // flag that the routing context found a route, but not a corresponding // supported method rctx.methodNotAllowed = true @@ -518,15 +571,6 @@ func (n *node) findEdge(ntyp nodeTyp, label byte) *node { } } -func (n *node) isEmpty() bool { - for _, nds := range n.children { - if len(nds) > 0 { - return false - } - } - return true -} - func (n *node) isLeaf() bool { return n.endpoints != nil } @@ -582,7 +626,7 @@ func (n *node) routes() []Route { } // Group methodHandlers by unique patterns - pats := make(map[string]endpoints, 0) + pats := make(map[string]endpoints) for mt, h := range eps { if h.pattern == "" { @@ -597,7 +641,7 @@ func (n *node) routes() []Route { } for p, mh := range pats { - hs := make(map[string]http.Handler, 0) + hs := make(map[string]http.Handler) if mh[mALL] != nil && mh[mALL].handler != nil { hs["*"] = mh[mALL].handler } @@ -613,7 +657,7 @@ func (n *node) routes() []Route { hs[m] = h.handler } - rt := Route{p, hs, subroutes} + rt := Route{subroutes, hs, p} rts = append(rts, rt) } @@ -698,7 +742,7 @@ func patNextSegment(pattern string) (nodeTyp, string, string, byte, int, int) { rexpat = "^" + rexpat } if rexpat[len(rexpat)-1] != '$' { - rexpat = rexpat + "$" + rexpat += "$" } } @@ -795,10 +839,11 @@ func (ns nodes) findEdge(label byte) *node { } // Route describes the details of a routing handler. +// Handlers map key is an HTTP method type Route struct { - Pattern string - Handlers map[string]http.Handler SubRoutes Routes + Handlers map[string]http.Handler + Pattern string } // WalkFunc is the type of the function called for each method and route visited by Walk. @@ -829,6 +874,7 @@ func walk(r Routes, walkFn WalkFunc, parentRoute string, parentMw ...func(http.H } fullRoute := parentRoute + route.Pattern + fullRoute = strings.Replace(fullRoute, "/*/", "/", -1) if chain, ok := handler.(*ChainHandler); ok { if err := walkFn(method, fullRoute, chain.Endpoint, append(mws, chain.Middlewares...)...); err != nil { diff --git a/vendor/modules.txt b/vendor/modules.txt index a2a979b..af0a4c0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -53,10 +53,10 @@ github.com/fatih/color # github.com/fsouza/go-dockerclient v1.12.0 ## explicit; go 1.22 github.com/fsouza/go-dockerclient -# github.com/go-chi/chi v4.0.2+incompatible -## explicit -github.com/go-chi/chi -github.com/go-chi/chi/middleware +# github.com/go-chi/chi/v5 v5.1.0 +## explicit; go 1.14 +github.com/go-chi/chi/v5 +github.com/go-chi/chi/v5/middleware # github.com/go-chi/render v1.0.3 ## explicit; go 1.16 github.com/go-chi/render @@ -224,8 +224,6 @@ go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage golang.org/x/crypto/ocsp golang.org/x/crypto/pbkdf2 golang.org/x/crypto/scrypt -# golang.org/x/net v0.31.0 -## explicit; go 1.18 # golang.org/x/sync v0.9.0 ## explicit; go 1.18 golang.org/x/sync/errgroup