mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-28 08:07:48 -04:00 
			
		
		
		
	Introduce go chi web framework as frontend of macaron, so that we can move routes from macaron to chi step by step (#7420)
* When route cannot be found on chi, go to macaron * Stick chi version to 1.5.0 * Follow router log setting
This commit is contained in:
		
							
								
								
									
										3
									
								
								vendor/github.com/go-chi/chi/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/go-chi/chi/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| .idea | ||||
| *.sw? | ||||
| .vscode | ||||
							
								
								
									
										20
									
								
								vendor/github.com/go-chi/chi/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/go-chi/chi/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| language: go | ||||
|  | ||||
| go: | ||||
|   - 1.10.x | ||||
|   - 1.11.x | ||||
|   - 1.12.x | ||||
|   - 1.13.x | ||||
|   - 1.14.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 | ||||
|  | ||||
							
								
								
									
										190
									
								
								vendor/github.com/go-chi/chi/CHANGELOG.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								vendor/github.com/go-chi/chi/CHANGELOG.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,190 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 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 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")` | ||||
							
								
								
									
										31
									
								
								vendor/github.com/go-chi/chi/CONTRIBUTING.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								vendor/github.com/go-chi/chi/CONTRIBUTING.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| # Contributing | ||||
|  | ||||
| ## Prerequisites | ||||
|  | ||||
| 1. [Install Go][go-install]. | ||||
| 2. Download the sources and switch the working directory: | ||||
|  | ||||
|     ```bash | ||||
|     go get -u -d github.com/go-chi/chi | ||||
|     cd $GOPATH/src/github.com/go-chi/chi | ||||
|     ``` | ||||
|  | ||||
| ## Submitting a Pull Request | ||||
|  | ||||
| A typical workflow is: | ||||
|  | ||||
| 1. [Fork the repository.][fork] [This tip maybe also helpful.][go-fork-tip] | ||||
| 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. | ||||
| 5. Implement the change and ensure the steps from the previous step pass. | ||||
| 6. Run `goimports -w .`, to ensure the new code conforms to Go formatting guideline. | ||||
| 7. [Add, commit and push your changes.][git-help] | ||||
| 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 | ||||
							
								
								
									
										20
									
								
								vendor/github.com/go-chi/chi/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/go-chi/chi/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| Copyright (c) 2015-present Peter Kieltyka (https://github.com/pkieltyka), Google Inc. | ||||
|  | ||||
| MIT License | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of | ||||
| this software and associated documentation files (the "Software"), to deal in | ||||
| the Software without restriction, including without limitation the rights to | ||||
| use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | ||||
| the Software, and to permit persons to whom the Software is furnished to do so, | ||||
| subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||||
| FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||||
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||||
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||||
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
							
								
								
									
										496
									
								
								vendor/github.com/go-chi/chi/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										496
									
								
								vendor/github.com/go-chi/chi/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,496 @@ | ||||
| # <img alt="chi" src="https://cdn.rawgit.com/go-chi/chi/master/_examples/chi.svg" width="220" /> | ||||
|  | ||||
|  | ||||
| [![GoDoc Widget]][GoDoc] [![Travis Widget]][Travis] | ||||
|  | ||||
| `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 | ||||
| project grows and changes. `chi` is built on the new `context` package introduced in Go 1.7 to | ||||
| handle signaling, cancelation and request-scoped values across a handler chain. | ||||
|  | ||||
| The focus of the project has been to seek out an elegant and comfortable design for writing | ||||
| REST API servers, written during the development of the Pressly API service that powers our | ||||
| 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! | ||||
|  | ||||
| ## Install | ||||
|  | ||||
| `go get -u github.com/go-chi/chi` | ||||
|  | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| * **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, 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 | ||||
| * **No external dependencies** - plain ol' Go stdlib + net/http | ||||
|  | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| See [_examples/](https://github.com/go-chi/chi/blob/master/_examples/) for a variety of examples. | ||||
|  | ||||
|  | ||||
| **As easy as:** | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/go-chi/chi" | ||||
| 	"github.com/go-chi/chi/middleware" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	r := chi.NewRouter() | ||||
| 	r.Use(middleware.Logger) | ||||
| 	r.Get("/", func(w http.ResponseWriter, r *http.Request) { | ||||
| 		w.Write([]byte("welcome")) | ||||
| 	}) | ||||
| 	http.ListenAndServe(":3000", r) | ||||
| } | ||||
| ``` | ||||
|  | ||||
| **REST Preview:** | ||||
|  | ||||
| Here is a little preview of how routing looks like with chi. Also take a look at the generated routing docs | ||||
| in JSON ([routes.json](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.json)) and in | ||||
| Markdown ([routes.md](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.md)). | ||||
|  | ||||
| I highly recommend reading the source of the [examples](https://github.com/go-chi/chi/blob/master/_examples/) listed | ||||
| above, they will show you all the features of chi and serve as a good form of documentation. | ||||
|  | ||||
| ```go | ||||
| import ( | ||||
|   //... | ||||
|   "context" | ||||
|   "github.com/go-chi/chi" | ||||
|   "github.com/go-chi/chi/middleware" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
|   r := chi.NewRouter() | ||||
|  | ||||
|   // A good base middleware stack | ||||
|   r.Use(middleware.RequestID) | ||||
|   r.Use(middleware.RealIP) | ||||
|   r.Use(middleware.Logger) | ||||
|   r.Use(middleware.Recoverer) | ||||
|  | ||||
|   // Set a timeout value on the request context (ctx), that will signal | ||||
|   // through ctx.Done() that the request has timed out and further | ||||
|   // processing should be stopped. | ||||
|   r.Use(middleware.Timeout(60 * time.Second)) | ||||
|  | ||||
|   r.Get("/", func(w http.ResponseWriter, r *http.Request) { | ||||
|     w.Write([]byte("hi")) | ||||
|   }) | ||||
|  | ||||
|   // RESTy routes for "articles" resource | ||||
|   r.Route("/articles", func(r chi.Router) { | ||||
|     r.With(paginate).Get("/", listArticles)                           // GET /articles | ||||
|     r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate) // GET /articles/01-16-2017 | ||||
|  | ||||
|     r.Post("/", createArticle)                                        // POST /articles | ||||
|     r.Get("/search", searchArticles)                                  // GET /articles/search | ||||
|  | ||||
|     // Regexp url parameters: | ||||
|     r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug)                // GET /articles/home-is-toronto | ||||
|  | ||||
|     // Subrouters: | ||||
|     r.Route("/{articleID}", func(r chi.Router) { | ||||
|       r.Use(ArticleCtx) | ||||
|       r.Get("/", getArticle)                                          // GET /articles/123 | ||||
|       r.Put("/", updateArticle)                                       // PUT /articles/123 | ||||
|       r.Delete("/", deleteArticle)                                    // DELETE /articles/123 | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   // Mount the admin sub-router | ||||
|   r.Mount("/admin", adminRouter()) | ||||
|  | ||||
|   http.ListenAndServe(":3333", r) | ||||
| } | ||||
|  | ||||
| func ArticleCtx(next http.Handler) http.Handler { | ||||
|   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||
|     articleID := chi.URLParam(r, "articleID") | ||||
|     article, err := dbGetArticle(articleID) | ||||
|     if err != nil { | ||||
|       http.Error(w, http.StatusText(404), 404) | ||||
|       return | ||||
|     } | ||||
|     ctx := context.WithValue(r.Context(), "article", article) | ||||
|     next.ServeHTTP(w, r.WithContext(ctx)) | ||||
|   }) | ||||
| } | ||||
|  | ||||
| func getArticle(w http.ResponseWriter, r *http.Request) { | ||||
|   ctx := r.Context() | ||||
|   article, ok := ctx.Value("article").(*Article) | ||||
|   if !ok { | ||||
|     http.Error(w, http.StatusText(422), 422) | ||||
|     return | ||||
|   } | ||||
|   w.Write([]byte(fmt.Sprintf("title:%s", article.Title))) | ||||
| } | ||||
|  | ||||
| // A completely separate router for administrator routes | ||||
| func adminRouter() http.Handler { | ||||
|   r := chi.NewRouter() | ||||
|   r.Use(AdminOnly) | ||||
|   r.Get("/", adminIndex) | ||||
|   r.Get("/accounts", adminListAccounts) | ||||
|   return r | ||||
| } | ||||
|  | ||||
| func AdminOnly(next http.Handler) http.Handler { | ||||
|   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||
|     ctx := r.Context() | ||||
|     perm, ok := ctx.Value("acl.permission").(YourPermissionType) | ||||
|     if !ok || !perm.IsAdmin() { | ||||
|       http.Error(w, http.StatusText(403), 403) | ||||
|       return | ||||
|     } | ||||
|     next.ServeHTTP(w, r) | ||||
|   }) | ||||
| } | ||||
| ``` | ||||
|  | ||||
|  | ||||
| ## 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`. | ||||
|  | ||||
| Built on top of the tree is the `Router` interface: | ||||
|  | ||||
| ```go | ||||
| // Router consisting of the core routing methods used by chi's Mux, | ||||
| // using only the standard net/http. | ||||
| type Router interface { | ||||
| 	http.Handler | ||||
| 	Routes | ||||
|  | ||||
| 	// 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. | ||||
| 	With(middlewares ...func(http.Handler) http.Handler) Router | ||||
|  | ||||
| 	// Group adds a new inline-Router along the current routing | ||||
| 	// path, with a fresh middleware stack for the inline-Router. | ||||
| 	Group(fn func(r Router)) Router | ||||
|  | ||||
| 	// Route mounts a sub-Router along a `pattern`` string. | ||||
| 	Route(pattern string, fn func(r Router)) Router | ||||
|  | ||||
| 	// Mount attaches another http.Handler along ./pattern/* | ||||
| 	Mount(pattern string, h http.Handler) | ||||
|  | ||||
| 	// Handle and HandleFunc adds routes for `pattern` that matches | ||||
| 	// all HTTP methods. | ||||
| 	Handle(pattern string, h http.Handler) | ||||
| 	HandleFunc(pattern string, h http.HandlerFunc) | ||||
|  | ||||
| 	// Method and MethodFunc adds routes for `pattern` that matches | ||||
| 	// the `method` HTTP method. | ||||
| 	Method(method, pattern string, h http.Handler) | ||||
| 	MethodFunc(method, pattern string, h http.HandlerFunc) | ||||
|  | ||||
| 	// HTTP-method routing along `pattern` | ||||
| 	Connect(pattern string, h http.HandlerFunc) | ||||
| 	Delete(pattern string, h http.HandlerFunc) | ||||
| 	Get(pattern string, h http.HandlerFunc) | ||||
| 	Head(pattern string, h http.HandlerFunc) | ||||
| 	Options(pattern string, h http.HandlerFunc) | ||||
| 	Patch(pattern string, h http.HandlerFunc) | ||||
| 	Post(pattern string, h http.HandlerFunc) | ||||
| 	Put(pattern string, h http.HandlerFunc) | ||||
| 	Trace(pattern string, h http.HandlerFunc) | ||||
|  | ||||
| 	// NotFound defines a handler to respond whenever a route could | ||||
| 	// not be found. | ||||
| 	NotFound(h http.HandlerFunc) | ||||
|  | ||||
| 	// MethodNotAllowed defines a handler to respond whenever a method is | ||||
| 	// not allowed. | ||||
| 	MethodNotAllowed(h http.HandlerFunc) | ||||
| } | ||||
|  | ||||
| // Routes interface adds two methods for router traversal, which is also | ||||
| // used by the github.com/go-chi/docgen package to generate documentation for Routers. | ||||
| type Routes interface { | ||||
| 	// Routes returns the routing tree in an easily traversable structure. | ||||
| 	Routes() []Route | ||||
|  | ||||
| 	// Middlewares returns the list of middlewares in use by the router. | ||||
| 	Middlewares() Middlewares | ||||
|  | ||||
| 	// Match searches the routing tree for a handler that matches | ||||
| 	// the method/path - similar to routing a http request, but without | ||||
| 	// executing the handler thereafter. | ||||
| 	Match(rctx *Context, method, path string) bool | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Each routing method accepts a URL `pattern` and chain of `handlers`. The URL pattern | ||||
| supports named params (ie. `/users/{userID}`) and wildcards (ie. `/admin/*`). URL parameters | ||||
| can be fetched at runtime by calling `chi.URLParam(r, "userID")` for named parameters | ||||
| and `chi.URLParam(r, "*")` for a wildcard parameter. | ||||
|  | ||||
|  | ||||
| ### Middleware handlers | ||||
|  | ||||
| chi's middlewares are just stdlib net/http middleware handlers. There is nothing special | ||||
| about them, which means the router and all the tooling is designed to be compatible and | ||||
| 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 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)) | ||||
|   }) | ||||
| } | ||||
| ``` | ||||
|  | ||||
|  | ||||
| ### Request handlers | ||||
|  | ||||
| chi uses standard net/http request handlers. This little snippet is an example of a http.Handler | ||||
| func that reads a user identifier from the request context - hypothetically, identifying | ||||
| the user sending an authenticated request, validated+set by a previous middleware handler. | ||||
|  | ||||
| ```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))) | ||||
| } | ||||
| ``` | ||||
|  | ||||
|  | ||||
| ### URL parameters | ||||
|  | ||||
| chi's router parses and stores URL parameters right onto the request context. Here is | ||||
| an example of how to access URL params in your net/http handlers. And of course, middlewares | ||||
| are able to access the same information. | ||||
|  | ||||
| ```go | ||||
| // HTTP handler accessing the url routing parameters. | ||||
| func MyRequestHandler(w http.ResponseWriter, r *http.Request) { | ||||
|   // 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))) | ||||
| } | ||||
| ``` | ||||
|  | ||||
|  | ||||
| ## Middlewares | ||||
|  | ||||
| chi comes equipped with an optional `middleware` package, providing a suite of standard | ||||
| `net/http` middlewares. Please note, any middleware in the ecosystem that is also compatible | ||||
| with `net/http` can be used with chi's mux. | ||||
|  | ||||
| ### Core middlewares | ||||
|  | ||||
| ---------------------------------------------------------------------------------------------------- | ||||
| | chi/middleware Handler | description                                                             | | ||||
| | :--------------------- | :---------------------------------------------------------------------- | | ||||
| | [AllowContentType]     | Explicit whitelist of accepted request Content-Types                    | | ||||
| | [BasicAuth]            | Basic HTTP authentication                                               | | ||||
| | [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         | | ||||
| ---------------------------------------------------------------------------------------------------- | ||||
|  | ||||
| [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 | ||||
| [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 | ||||
| [New]: https://pkg.go.dev/github.com/go-chi/chi/middleware#New | ||||
| [NextRequestID]: https://pkg.go.dev/github.com/go-chi/chi/middleware#NextRequestID | ||||
| [NoCache]: https://pkg.go.dev/github.com/go-chi/chi/middleware#NoCache | ||||
| [PrintPrettyStack]: https://pkg.go.dev/github.com/go-chi/chi/middleware#PrintPrettyStack | ||||
| [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 | ||||
| [RequestID]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RequestID | ||||
| [RequestLogger]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RequestLogger | ||||
| [SetHeader]: https://pkg.go.dev/github.com/go-chi/chi/middleware#SetHeader | ||||
| [StripSlashes]: https://pkg.go.dev/github.com/go-chi/chi/middleware#StripSlashes | ||||
| [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 | ||||
| [Pattern]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Pattern | ||||
| [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. | ||||
|  | ||||
| -------------------------------------------------------------------------------------------------------------------- | ||||
| | package                                            | description                                                 | | ||||
| |:---------------------------------------------------|:------------------------------------------------------------- | ||||
| | [cors](https://github.com/go-chi/cors)             | Cross-origin resource sharing (CORS)                        | | ||||
| | [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                           | | ||||
| | [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                                      | | ||||
| -------------------------------------------------------------------------------------------------------------------- | ||||
|  | ||||
|  | ||||
| ## context? | ||||
|  | ||||
| `context` is a tiny pkg that provides simple interface to signal context across call stacks | ||||
| and goroutines. It was originally written by [Sameer Ajmani](https://github.com/Sajmani) | ||||
| and is available in stdlib since go1.7. | ||||
|  | ||||
| Learn more at https://blog.golang.org/context | ||||
|  | ||||
| and.. | ||||
| * Docs: https://golang.org/pkg/context | ||||
| * Source: https://github.com/golang/go/tree/master/src/context | ||||
|  | ||||
|  | ||||
| ## Benchmarks | ||||
|  | ||||
| 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 | ||||
|  | ||||
| ```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 | ||||
| ``` | ||||
|  | ||||
| Comparison with other routers: https://gist.github.com/pkieltyka/123032f12052520aaccab752bd3e78cc | ||||
|  | ||||
| NOTE: the allocs in the benchmark above are from the calls to http.Request's | ||||
| `WithContext(context.Context)` method that clones the http.Request, sets the `Context()` | ||||
| on the duplicated (alloc'd) request and returns it the new request object. This is just | ||||
| how setting context on a request in Go works. | ||||
|  | ||||
|  | ||||
| ## Credits | ||||
|  | ||||
| * Carl Jackson for https://github.com/zenazn/goji | ||||
|   * Parts of chi's thinking comes from goji, and chi's middleware package | ||||
|     sources from goji. | ||||
| * Armon Dadgar for https://github.com/armon/go-radix | ||||
| * Contributions: [@VojtechVitek](https://github.com/VojtechVitek) | ||||
|  | ||||
| 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 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 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 | ||||
|  | ||||
| Copyright (c) 2015-present [Peter Kieltyka](https://github.com/pkieltyka) | ||||
|  | ||||
| Licensed under [MIT License](./LICENSE) | ||||
|  | ||||
| [GoDoc]: https://pkg.go.dev/github.com/go-chi/chi?tab=versions | ||||
| [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 | ||||
							
								
								
									
										49
									
								
								vendor/github.com/go-chi/chi/chain.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								vendor/github.com/go-chi/chi/chain.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| package chi | ||||
|  | ||||
| import "net/http" | ||||
|  | ||||
| // Chain returns a Middlewares type from a slice of middleware handlers. | ||||
| func Chain(middlewares ...func(http.Handler) http.Handler) Middlewares { | ||||
| 	return Middlewares(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)} | ||||
| } | ||||
|  | ||||
| // 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)} | ||||
| } | ||||
|  | ||||
| // ChainHandler is a http.Handler with support for handler composition and | ||||
| // execution. | ||||
| type ChainHandler struct { | ||||
| 	Middlewares Middlewares | ||||
| 	Endpoint    http.Handler | ||||
| 	chain       http.Handler | ||||
| } | ||||
|  | ||||
| func (c *ChainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	c.chain.ServeHTTP(w, r) | ||||
| } | ||||
|  | ||||
| // chain builds a http.Handler composed of an inline middleware stack and endpoint | ||||
| // handler in the order they are passed. | ||||
| func chain(middlewares []func(http.Handler) http.Handler, endpoint http.Handler) http.Handler { | ||||
| 	// Return ahead of time if there aren't any middlewares for the chain | ||||
| 	if len(middlewares) == 0 { | ||||
| 		return endpoint | ||||
| 	} | ||||
|  | ||||
| 	// Wrap the end handler with the middleware chain | ||||
| 	h := middlewares[len(middlewares)-1](endpoint) | ||||
| 	for i := len(middlewares) - 2; i >= 0; i-- { | ||||
| 		h = middlewares[i](h) | ||||
| 	} | ||||
|  | ||||
| 	return h | ||||
| } | ||||
							
								
								
									
										134
									
								
								vendor/github.com/go-chi/chi/chi.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								vendor/github.com/go-chi/chi/chi.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | ||||
| // | ||||
| // Package chi is a small, idiomatic and composable router for building HTTP services. | ||||
| // | ||||
| // chi requires Go 1.10 or newer. | ||||
| // | ||||
| // Example: | ||||
| //  package main | ||||
| // | ||||
| //  import ( | ||||
| //  	"net/http" | ||||
| // | ||||
| //  	"github.com/go-chi/chi" | ||||
| //  	"github.com/go-chi/chi/middleware" | ||||
| //  ) | ||||
| // | ||||
| //  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.")) | ||||
| //  	}) | ||||
| // | ||||
| //  	http.ListenAndServe(":3333", r) | ||||
| //  } | ||||
| // | ||||
| // See github.com/go-chi/chi/_examples/ for more in-depth examples. | ||||
| // | ||||
| // URL patterns allow for easy matching of path components in HTTP | ||||
| // requests. The matching components can then be accessed using | ||||
| // chi.URLParam(). All patterns must begin with a slash. | ||||
| // | ||||
| // A simple named placeholder {name} matches any sequence of characters | ||||
| // up to the next / or the end of the URL. Trailing slashes on paths must | ||||
| // be handled explicitly. | ||||
| // | ||||
| // A placeholder with a name followed by a colon allows a regular | ||||
| // expression match, for example {number:\\d+}. The regular expression | ||||
| // syntax is Go's normal regexp RE2 syntax, except that regular expressions | ||||
| // including { or } are not supported, and / will never be | ||||
| // matched. An anonymous regexp pattern is allowed, using an empty string | ||||
| // before the colon in the placeholder, such as {:\\d+} | ||||
| // | ||||
| // The special placeholder of asterisk matches the rest of the requested | ||||
| // URL. Any trailing characters in the pattern are ignored. This is the only | ||||
| // 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" | ||||
| // | ||||
| package chi | ||||
|  | ||||
| import "net/http" | ||||
|  | ||||
| // NewRouter returns a new Mux object that implements the Router interface. | ||||
| func NewRouter() *Mux { | ||||
| 	return NewMux() | ||||
| } | ||||
|  | ||||
| // Router consisting of the core routing methods used by chi's Mux, | ||||
| // using only the standard net/http. | ||||
| type Router interface { | ||||
| 	http.Handler | ||||
| 	Routes | ||||
|  | ||||
| 	// 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. | ||||
| 	With(middlewares ...func(http.Handler) http.Handler) Router | ||||
|  | ||||
| 	// Group adds a new inline-Router along the current routing | ||||
| 	// path, with a fresh middleware stack for the inline-Router. | ||||
| 	Group(fn func(r Router)) Router | ||||
|  | ||||
| 	// Route mounts a sub-Router along a `pattern`` string. | ||||
| 	Route(pattern string, fn func(r Router)) Router | ||||
|  | ||||
| 	// Mount attaches another http.Handler along ./pattern/* | ||||
| 	Mount(pattern string, h http.Handler) | ||||
|  | ||||
| 	// Handle and HandleFunc adds routes for `pattern` that matches | ||||
| 	// all HTTP methods. | ||||
| 	Handle(pattern string, h http.Handler) | ||||
| 	HandleFunc(pattern string, h http.HandlerFunc) | ||||
|  | ||||
| 	// Method and MethodFunc adds routes for `pattern` that matches | ||||
| 	// the `method` HTTP method. | ||||
| 	Method(method, pattern string, h http.Handler) | ||||
| 	MethodFunc(method, pattern string, h http.HandlerFunc) | ||||
|  | ||||
| 	// HTTP-method routing along `pattern` | ||||
| 	Connect(pattern string, h http.HandlerFunc) | ||||
| 	Delete(pattern string, h http.HandlerFunc) | ||||
| 	Get(pattern string, h http.HandlerFunc) | ||||
| 	Head(pattern string, h http.HandlerFunc) | ||||
| 	Options(pattern string, h http.HandlerFunc) | ||||
| 	Patch(pattern string, h http.HandlerFunc) | ||||
| 	Post(pattern string, h http.HandlerFunc) | ||||
| 	Put(pattern string, h http.HandlerFunc) | ||||
| 	Trace(pattern string, h http.HandlerFunc) | ||||
|  | ||||
| 	// NotFound defines a handler to respond whenever a route could | ||||
| 	// not be found. | ||||
| 	NotFound(h http.HandlerFunc) | ||||
|  | ||||
| 	// MethodNotAllowed defines a handler to respond whenever a method is | ||||
| 	// not allowed. | ||||
| 	MethodNotAllowed(h http.HandlerFunc) | ||||
| } | ||||
|  | ||||
| // Routes interface adds two methods for router traversal, which is also | ||||
| // used by the `docgen` subpackage to generation documentation for Routers. | ||||
| type Routes interface { | ||||
| 	// Routes returns the routing tree in an easily traversable structure. | ||||
| 	Routes() []Route | ||||
|  | ||||
| 	// Middlewares returns the list of middlewares in use by the router. | ||||
| 	Middlewares() Middlewares | ||||
|  | ||||
| 	// Match searches the routing tree for a handler that matches | ||||
| 	// the method/path - similar to routing a http request, but without | ||||
| 	// executing the handler thereafter. | ||||
| 	Match(rctx *Context, method, path string) bool | ||||
| } | ||||
|  | ||||
| // Middlewares type is a slice of standard middleware handlers with methods | ||||
| // to compose middleware chains and http.Handler's. | ||||
| type Middlewares []func(http.Handler) http.Handler | ||||
							
								
								
									
										172
									
								
								vendor/github.com/go-chi/chi/context.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								vendor/github.com/go-chi/chi/context.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,172 @@ | ||||
| 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 | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| } | ||||
|  | ||||
| // 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"} | ||||
| ) | ||||
|  | ||||
| // Context is the default routing context set on the root node of a | ||||
| // request context to track route patterns, URL parameters and | ||||
| // an optional routing path. | ||||
| type Context struct { | ||||
| 	Routes Routes | ||||
|  | ||||
| 	// 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 | ||||
|  | ||||
| 	// 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 | ||||
|  | ||||
| 	// methodNotAllowed hint | ||||
| 	methodNotAllowed bool | ||||
| } | ||||
|  | ||||
| // Reset a routing context to its initial state. | ||||
| func (x *Context) Reset() { | ||||
| 	x.Routes = nil | ||||
| 	x.RoutePath = "" | ||||
| 	x.RouteMethod = "" | ||||
| 	x.RoutePatterns = x.RoutePatterns[:0] | ||||
| 	x.URLParams.Keys = x.URLParams.Keys[:0] | ||||
| 	x.URLParams.Values = x.URLParams.Values[:0] | ||||
|  | ||||
| 	x.routePattern = "" | ||||
| 	x.routeParams.Keys = x.routeParams.Keys[:0] | ||||
| 	x.routeParams.Values = x.routeParams.Values[:0] | ||||
| 	x.methodNotAllowed = false | ||||
| } | ||||
|  | ||||
| // URLParam returns the corresponding URL parameter value from the request | ||||
| // routing context. | ||||
| func (x *Context) URLParam(key string) string { | ||||
| 	for k := len(x.URLParams.Keys) - 1; k >= 0; k-- { | ||||
| 		if x.URLParams.Keys[k] == key { | ||||
| 			return x.URLParams.Values[k] | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| // RoutePattern builds the routing pattern string for the particular | ||||
| // request, at the particular point during routing. This means, the value | ||||
| // will change throughout the execution of a request in a router. That is | ||||
| // why its advised to only use this value after calling the next handler. | ||||
| // | ||||
| // 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 (x *Context) RoutePattern() string { | ||||
| 	routePattern := strings.Join(x.RoutePatterns, "") | ||||
| 	return replaceWildcards(routePattern) | ||||
| } | ||||
|  | ||||
| // 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 p | ||||
| } | ||||
|  | ||||
| // RouteParams is a structure to track URL routing parameters efficiently. | ||||
| type RouteParams struct { | ||||
| 	Keys, Values []string | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| } | ||||
|  | ||||
| // 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. | ||||
| type contextKey struct { | ||||
| 	name string | ||||
| } | ||||
|  | ||||
| func (k *contextKey) String() string { | ||||
| 	return "chi context value " + k.name | ||||
| } | ||||
							
								
								
									
										3
									
								
								vendor/github.com/go-chi/chi/go.mod
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/go-chi/chi/go.mod
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| module github.com/go-chi/chi | ||||
|  | ||||
| go 1.15 | ||||
							
								
								
									
										32
									
								
								vendor/github.com/go-chi/chi/middleware/basic_auth.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								vendor/github.com/go-chi/chi/middleware/basic_auth.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| package middleware | ||||
|  | ||||
| import ( | ||||
| 	"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 || pass != credPass { | ||||
| 				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) | ||||
| } | ||||
							
								
								
									
										399
									
								
								vendor/github.com/go-chi/chi/middleware/compress.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										399
									
								
								vendor/github.com/go-chi/chi/middleware/compress.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,399 @@ | ||||
| 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 { | ||||
| 	level int // The compression level. | ||||
| 	// 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 | ||||
| } | ||||
|  | ||||
| // 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 gzip. | ||||
| 	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 algortithm: | ||||
| // | ||||
| //  import brotli_enc "gopkg.in/kothar/brotli-go.v0/enc" | ||||
| // | ||||
| //  compressor := middleware.NewCompressor(5, "text/html") | ||||
| //  compressor.SetEncoder("br", func(w http.ResponseWriter, 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. | ||||
| 	if _, ok := c.pooledEncoders[encoding]; ok { | ||||
| 		delete(c.pooledEncoders, encoding) | ||||
| 	} | ||||
| 	if _, ok := c.encoders[encoding]; ok { | ||||
| 		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, | ||||
| 			compressable:     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 | ||||
| 	encoding         string | ||||
| 	contentTypes     map[string]struct{} | ||||
| 	contentWildcards map[string]struct{} | ||||
| 	wroteHeader      bool | ||||
| 	compressable     bool | ||||
| } | ||||
|  | ||||
| func (cw *compressResponseWriter) isCompressable() 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 compressable? | ||||
| 	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.isCompressable() { | ||||
| 		cw.compressable = false | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if cw.encoding != "" { | ||||
| 		cw.compressable = true | ||||
| 		cw.Header().Set("Content-Encoding", cw.encoding) | ||||
| 		cw.Header().Set("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.compressable { | ||||
| 		return cw.w | ||||
| 	} else { | ||||
| 		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 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 | ||||
| } | ||||
							
								
								
									
										51
									
								
								vendor/github.com/go-chi/chi/middleware/content_charset.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								vendor/github.com/go-chi/chi/middleware/content_charset.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| package middleware | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // ContentCharset generates a handler that writes a 415 Unsupported Media Type response if none of the charsets match. | ||||
| // An empty charset will allow requests with no Content-Type header or no specified charset. | ||||
| func ContentCharset(charsets ...string) func(next http.Handler) http.Handler { | ||||
| 	for i, c := range charsets { | ||||
| 		charsets[i] = strings.ToLower(c) | ||||
| 	} | ||||
|  | ||||
| 	return func(next http.Handler) http.Handler { | ||||
| 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||
| 			if !contentEncoding(r.Header.Get("Content-Type"), charsets...) { | ||||
| 				w.WriteHeader(http.StatusUnsupportedMediaType) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			next.ServeHTTP(w, r) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Check the content encoding against a list of acceptable values. | ||||
| func contentEncoding(ce string, charsets ...string) bool { | ||||
| 	_, ce = split(strings.ToLower(ce), ";") | ||||
| 	_, ce = split(ce, "charset=") | ||||
| 	ce, _ = split(ce, ";") | ||||
| 	for _, c := range charsets { | ||||
| 		if ce == c { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // Split a string in two parts, cleaning any whitespace. | ||||
| func split(str, sep string) (string, string) { | ||||
| 	var a, b string | ||||
| 	var parts = strings.SplitN(str, sep, 2) | ||||
| 	a = strings.TrimSpace(parts[0]) | ||||
| 	if len(parts) == 2 { | ||||
| 		b = strings.TrimSpace(parts[1]) | ||||
| 	} | ||||
|  | ||||
| 	return a, b | ||||
| } | ||||
							
								
								
									
										34
									
								
								vendor/github.com/go-chi/chi/middleware/content_encoding.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								vendor/github.com/go-chi/chi/middleware/content_encoding.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										51
									
								
								vendor/github.com/go-chi/chi/middleware/content_type.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								vendor/github.com/go-chi/chi/middleware/content_type.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| package middleware | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // SetHeader is a convenience handler to set a response header key/value | ||||
| func SetHeader(key, value string) func(next http.Handler) http.Handler { | ||||
| 	return func(next http.Handler) http.Handler { | ||||
| 		fn := func(w http.ResponseWriter, r *http.Request) { | ||||
| 			w.Header().Set(key, value) | ||||
| 			next.ServeHTTP(w, r) | ||||
| 		} | ||||
| 		return http.HandlerFunc(fn) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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)) | ||||
| 	} | ||||
|  | ||||
| 	return func(next http.Handler) http.Handler { | ||||
| 		fn := func(w http.ResponseWriter, r *http.Request) { | ||||
| 			if r.ContentLength == 0 { | ||||
| 				// skip check for empty content body | ||||
| 				next.ServeHTTP(w, r) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			s := strings.ToLower(strings.TrimSpace(r.Header.Get("Content-Type"))) | ||||
| 			if i := strings.Index(s, ";"); i > -1 { | ||||
| 				s = s[0:i] | ||||
| 			} | ||||
|  | ||||
| 			for _, t := range cT { | ||||
| 				if t == s { | ||||
| 					next.ServeHTTP(w, r) | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			w.WriteHeader(http.StatusUnsupportedMediaType) | ||||
| 		} | ||||
| 		return http.HandlerFunc(fn) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										39
									
								
								vendor/github.com/go-chi/chi/middleware/get_head.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								vendor/github.com/go-chi/chi/middleware/get_head.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| package middleware | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/go-chi/chi" | ||||
| ) | ||||
|  | ||||
| // GetHead automatically route undefined HEAD requests to GET handlers. | ||||
| func GetHead(next http.Handler) http.Handler { | ||||
| 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||
| 		if r.Method == "HEAD" { | ||||
| 			rctx := chi.RouteContext(r.Context()) | ||||
| 			routePath := rctx.RoutePath | ||||
| 			if routePath == "" { | ||||
| 				if r.URL.RawPath != "" { | ||||
| 					routePath = r.URL.RawPath | ||||
| 				} else { | ||||
| 					routePath = r.URL.Path | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			// Temporary routing context to look-ahead before routing the request | ||||
| 			tctx := chi.NewRouteContext() | ||||
|  | ||||
| 			// Attempt to find a HEAD handler for the routing path, if not found, traverse | ||||
| 			// the router as through its a GET route, but proceed with the request | ||||
| 			// with the HEAD method. | ||||
| 			if !rctx.Routes.Match(tctx, "HEAD", routePath) { | ||||
| 				rctx.RouteMethod = "GET" | ||||
| 				rctx.RoutePath = routePath | ||||
| 				next.ServeHTTP(w, r) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		next.ServeHTTP(w, r) | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										26
									
								
								vendor/github.com/go-chi/chi/middleware/heartbeat.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								vendor/github.com/go-chi/chi/middleware/heartbeat.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| package middleware | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // Heartbeat endpoint middleware useful to setting up a path like | ||||
| // `/ping` that load balancers or uptime testing external services | ||||
| // can make a request before hitting any routes. It's also convenient | ||||
| // to place this above ACL middlewares as well. | ||||
| 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) { | ||||
| 				w.Header().Set("Content-Type", "text/plain") | ||||
| 				w.WriteHeader(http.StatusOK) | ||||
| 				w.Write([]byte(".")) | ||||
| 				return | ||||
| 			} | ||||
| 			h.ServeHTTP(w, r) | ||||
| 		} | ||||
| 		return http.HandlerFunc(fn) | ||||
| 	} | ||||
| 	return f | ||||
| } | ||||
							
								
								
									
										155
									
								
								vendor/github.com/go-chi/chi/middleware/logger.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								vendor/github.com/go-chi/chi/middleware/logger.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,155 @@ | ||||
| package middleware | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// LogEntryCtxKey is the context.Context key to store the request log entry. | ||||
| 	LogEntryCtxKey = &contextKey{"LogEntry"} | ||||
|  | ||||
| 	// 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}) | ||||
| ) | ||||
|  | ||||
| // Logger is a middleware that logs the start and end of each request, along | ||||
| // with some useful data about what was requested, what the response status was, | ||||
| // and how long it took to return. When standard output is a TTY, Logger will | ||||
| // 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/goware/httplog for a more in-depth | ||||
| // http logger with structured logging support. | ||||
| func Logger(next http.Handler) http.Handler { | ||||
| 	return DefaultLogger(next) | ||||
| } | ||||
|  | ||||
| // RequestLogger returns a logger handler using a custom LogFormatter. | ||||
| func RequestLogger(f LogFormatter) func(next http.Handler) http.Handler { | ||||
| 	return func(next http.Handler) http.Handler { | ||||
| 		fn := func(w http.ResponseWriter, r *http.Request) { | ||||
| 			entry := f.NewLogEntry(r) | ||||
| 			ww := NewWrapResponseWriter(w, r.ProtoMajor) | ||||
|  | ||||
| 			t1 := time.Now() | ||||
| 			defer func() { | ||||
| 				entry.Write(ww.Status(), ww.BytesWritten(), ww.Header(), time.Since(t1), nil) | ||||
| 			}() | ||||
|  | ||||
| 			next.ServeHTTP(ww, WithLogEntry(r, entry)) | ||||
| 		} | ||||
| 		return http.HandlerFunc(fn) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // LogFormatter initiates the beginning of a new LogEntry per request. | ||||
| // See DefaultLogFormatter for an example implementation. | ||||
| type LogFormatter interface { | ||||
| 	NewLogEntry(r *http.Request) LogEntry | ||||
| } | ||||
|  | ||||
| // LogEntry records the final log when a request completes. | ||||
| // See defaultLogEntry for an example implementation. | ||||
| type LogEntry interface { | ||||
| 	Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) | ||||
| 	Panic(v interface{}, stack []byte) | ||||
| } | ||||
|  | ||||
| // GetLogEntry returns the in-context LogEntry for a request. | ||||
| func GetLogEntry(r *http.Request) LogEntry { | ||||
| 	entry, _ := r.Context().Value(LogEntryCtxKey).(LogEntry) | ||||
| 	return entry | ||||
| } | ||||
|  | ||||
| // WithLogEntry sets the in-context LogEntry for a request. | ||||
| func WithLogEntry(r *http.Request, entry LogEntry) *http.Request { | ||||
| 	r = r.WithContext(context.WithValue(r.Context(), LogEntryCtxKey, entry)) | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // LoggerInterface accepts printing to stdlib logger or compatible logger. | ||||
| type LoggerInterface interface { | ||||
| 	Print(v ...interface{}) | ||||
| } | ||||
|  | ||||
| // DefaultLogFormatter is a simple logger that implements a LogFormatter. | ||||
| type DefaultLogFormatter struct { | ||||
| 	Logger  LoggerInterface | ||||
| 	NoColor bool | ||||
| } | ||||
|  | ||||
| // NewLogEntry creates a new LogEntry for the request. | ||||
| func (l *DefaultLogFormatter) NewLogEntry(r *http.Request) LogEntry { | ||||
| 	useColor := !l.NoColor | ||||
| 	entry := &defaultLogEntry{ | ||||
| 		DefaultLogFormatter: l, | ||||
| 		request:             r, | ||||
| 		buf:                 &bytes.Buffer{}, | ||||
| 		useColor:            useColor, | ||||
| 	} | ||||
|  | ||||
| 	reqID := GetReqID(r.Context()) | ||||
| 	if reqID != "" { | ||||
| 		cW(entry.buf, useColor, nYellow, "[%s] ", reqID) | ||||
| 	} | ||||
| 	cW(entry.buf, useColor, nCyan, "\"") | ||||
| 	cW(entry.buf, useColor, bMagenta, "%s ", r.Method) | ||||
|  | ||||
| 	scheme := "http" | ||||
| 	if r.TLS != nil { | ||||
| 		scheme = "https" | ||||
| 	} | ||||
| 	cW(entry.buf, useColor, nCyan, "%s://%s%s %s\" ", scheme, r.Host, r.RequestURI, r.Proto) | ||||
|  | ||||
| 	entry.buf.WriteString("from ") | ||||
| 	entry.buf.WriteString(r.RemoteAddr) | ||||
| 	entry.buf.WriteString(" - ") | ||||
|  | ||||
| 	return entry | ||||
| } | ||||
|  | ||||
| type defaultLogEntry struct { | ||||
| 	*DefaultLogFormatter | ||||
| 	request  *http.Request | ||||
| 	buf      *bytes.Buffer | ||||
| 	useColor bool | ||||
| } | ||||
|  | ||||
| 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) | ||||
| 	case status < 300: | ||||
| 		cW(l.buf, l.useColor, bGreen, "%03d", status) | ||||
| 	case status < 400: | ||||
| 		cW(l.buf, l.useColor, bCyan, "%03d", status) | ||||
| 	case status < 500: | ||||
| 		cW(l.buf, l.useColor, bYellow, "%03d", status) | ||||
| 	default: | ||||
| 		cW(l.buf, l.useColor, bRed, "%03d", status) | ||||
| 	} | ||||
|  | ||||
| 	cW(l.buf, l.useColor, bBlue, " %dB", bytes) | ||||
|  | ||||
| 	l.buf.WriteString(" in ") | ||||
| 	if elapsed < 500*time.Millisecond { | ||||
| 		cW(l.buf, l.useColor, nGreen, "%s", elapsed) | ||||
| 	} else if elapsed < 5*time.Second { | ||||
| 		cW(l.buf, l.useColor, nYellow, "%s", elapsed) | ||||
| 	} else { | ||||
| 		cW(l.buf, l.useColor, nRed, "%s", elapsed) | ||||
| 	} | ||||
|  | ||||
| 	l.Logger.Print(l.buf.String()) | ||||
| } | ||||
|  | ||||
| func (l *defaultLogEntry) Panic(v interface{}, stack []byte) { | ||||
| 	PrintPrettyStack(v) | ||||
| } | ||||
							
								
								
									
										23
									
								
								vendor/github.com/go-chi/chi/middleware/middleware.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								vendor/github.com/go-chi/chi/middleware/middleware.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| 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. | ||||
| type contextKey struct { | ||||
| 	name string | ||||
| } | ||||
|  | ||||
| func (k *contextKey) String() string { | ||||
| 	return "chi/middleware context value " + k.name | ||||
| } | ||||
							
								
								
									
										58
									
								
								vendor/github.com/go-chi/chi/middleware/nocache.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								vendor/github.com/go-chi/chi/middleware/nocache.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| package middleware | ||||
|  | ||||
| // Ported from Goji's middleware, source: | ||||
| // https://github.com/zenazn/goji/tree/master/web/middleware | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // Unix epoch time | ||||
| var epoch = time.Unix(0, 0).Format(time.RFC1123) | ||||
|  | ||||
| // Taken from https://github.com/mytrile/nocache | ||||
| var noCacheHeaders = map[string]string{ | ||||
| 	"Expires":         epoch, | ||||
| 	"Cache-Control":   "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", | ||||
| 	"Pragma":          "no-cache", | ||||
| 	"X-Accel-Expires": "0", | ||||
| } | ||||
|  | ||||
| var etagHeaders = []string{ | ||||
| 	"ETag", | ||||
| 	"If-Modified-Since", | ||||
| 	"If-Match", | ||||
| 	"If-None-Match", | ||||
| 	"If-Range", | ||||
| 	"If-Unmodified-Since", | ||||
| } | ||||
|  | ||||
| // NoCache is a simple piece of middleware that sets a number of HTTP headers to prevent | ||||
| // 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) | ||||
| func NoCache(h http.Handler) http.Handler { | ||||
| 	fn := func(w http.ResponseWriter, r *http.Request) { | ||||
|  | ||||
| 		// Delete any ETag headers that may have been set | ||||
| 		for _, v := range etagHeaders { | ||||
| 			if r.Header.Get(v) != "" { | ||||
| 				r.Header.Del(v) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Set our NoCache headers | ||||
| 		for k, v := range noCacheHeaders { | ||||
| 			w.Header().Set(k, v) | ||||
| 		} | ||||
|  | ||||
| 		h.ServeHTTP(w, r) | ||||
| 	} | ||||
|  | ||||
| 	return http.HandlerFunc(fn) | ||||
| } | ||||
							
								
								
									
										55
									
								
								vendor/github.com/go-chi/chi/middleware/profiler.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								vendor/github.com/go-chi/chi/middleware/profiler.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| 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") | ||||
| } | ||||
							
								
								
									
										54
									
								
								vendor/github.com/go-chi/chi/middleware/realip.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								vendor/github.com/go-chi/chi/middleware/realip.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| package middleware | ||||
|  | ||||
| // Ported from Goji's middleware, source: | ||||
| // https://github.com/zenazn/goji/tree/master/web/middleware | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| 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). | ||||
| // | ||||
| // 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 | ||||
| // 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 | ||||
| // proxy, malicious clients will be able to make you very sad (or, depending on | ||||
| // how you're using RemoteAddr, vulnerable to an attack of some sort). | ||||
| func RealIP(h http.Handler) http.Handler { | ||||
| 	fn := func(w http.ResponseWriter, r *http.Request) { | ||||
| 		if rip := realIP(r); rip != "" { | ||||
| 			r.RemoteAddr = rip | ||||
| 		} | ||||
| 		h.ServeHTTP(w, r) | ||||
| 	} | ||||
|  | ||||
| 	return http.HandlerFunc(fn) | ||||
| } | ||||
|  | ||||
| func realIP(r *http.Request) string { | ||||
| 	var ip string | ||||
|  | ||||
| 	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] | ||||
| 	} | ||||
|  | ||||
| 	return ip | ||||
| } | ||||
							
								
								
									
										192
									
								
								vendor/github.com/go-chi/chi/middleware/recoverer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								vendor/github.com/go-chi/chi/middleware/recoverer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,192 @@ | ||||
| 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" | ||||
| 	"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/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 && rvr != http.ErrAbortHandler { | ||||
|  | ||||
| 				logEntry := GetLogEntry(r) | ||||
| 				if logEntry != nil { | ||||
| 					logEntry.Panic(rvr, debug.Stack()) | ||||
| 				} else { | ||||
| 					PrintPrettyStack(rvr) | ||||
| 				} | ||||
|  | ||||
| 				w.WriteHeader(http.StatusInternalServerError) | ||||
| 			} | ||||
| 		}() | ||||
|  | ||||
| 		next.ServeHTTP(w, r) | ||||
| 	} | ||||
|  | ||||
| 	return http.HandlerFunc(fn) | ||||
| } | ||||
|  | ||||
| func PrintPrettyStack(rvr interface{}) { | ||||
| 	debugStack := debug.Stack() | ||||
| 	s := prettyStack{} | ||||
| 	out, err := s.parse(debugStack, rvr) | ||||
| 	if err == nil { | ||||
| 		os.Stderr.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(0x") { | ||||
| 			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) | ||||
| 	} else if strings.HasSuffix(line, ")") { | ||||
| 		return s.decorateFuncCallLine(line, useColor, num) | ||||
| 	} else { | ||||
| 		if strings.HasPrefix(line, "\t") { | ||||
| 			return strings.Replace(line, "\t", "      ", 1), nil | ||||
| 		} else { | ||||
| 			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 := "" | ||||
|  | ||||
| 	idx = strings.LastIndex(pkg, string(os.PathSeparator)) | ||||
| 	if idx < 0 { | ||||
| 		idx = strings.Index(pkg, ".") | ||||
| 		method = pkg[idx:] | ||||
| 		pkg = pkg[0:idx] | ||||
| 	} else { | ||||
| 		method = pkg[idx+1:] | ||||
| 		pkg = pkg[0 : idx+1] | ||||
| 		idx = strings.Index(method, ".") | ||||
| 		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 | ||||
| } | ||||
							
								
								
									
										96
									
								
								vendor/github.com/go-chi/chi/middleware/request_id.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								vendor/github.com/go-chi/chi/middleware/request_id.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| package middleware | ||||
|  | ||||
| // Ported from Goji's middleware, source: | ||||
| // https://github.com/zenazn/goji/tree/master/web/middleware | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/rand" | ||||
| 	"encoding/base64" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"sync/atomic" | ||||
| ) | ||||
|  | ||||
| // Key to use when setting the request ID. | ||||
| 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 | ||||
|  | ||||
| // A quick note on the statistics here: we're trying to calculate the chance that | ||||
| // two randomly generated base62 prefixes will collide. We use the formula from | ||||
| // http://en.wikipedia.org/wiki/Birthday_problem | ||||
| // | ||||
| // P[m, n] \approx 1 - e^{-m^2/2n} | ||||
| // | ||||
| // We ballpark an upper bound for $m$ by imagining (for whatever reason) a server | ||||
| // that restarts every second over 10 years, for $m = 86400 * 365 * 10 = 315360000$ | ||||
| // | ||||
| // For a $k$ character base-62 identifier, we have $n(k) = 62^k$ | ||||
| // | ||||
| // Plugging this in, we find $P[m, n(10)] \approx 5.75%$, which is good enough for | ||||
| // our purposes, and is surely more than anyone would ever need in practice -- a | ||||
| // process that is rebooted a handful of times a day for a hundred years has less | ||||
| // than a millionth of a percent chance of generating two colliding IDs. | ||||
|  | ||||
| func init() { | ||||
| 	hostname, err := os.Hostname() | ||||
| 	if hostname == "" || err != nil { | ||||
| 		hostname = "localhost" | ||||
| 	} | ||||
| 	var buf [12]byte | ||||
| 	var b64 string | ||||
| 	for len(b64) < 10 { | ||||
| 		rand.Read(buf[:]) | ||||
| 		b64 = base64.StdEncoding.EncodeToString(buf[:]) | ||||
| 		b64 = strings.NewReplacer("+", "", "/", "").Replace(b64) | ||||
| 	} | ||||
|  | ||||
| 	prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10]) | ||||
| } | ||||
|  | ||||
| // RequestID is a middleware that injects a request ID into the context of each | ||||
| // request. A request ID is a string of the form "host.example.com/random-0001", | ||||
| // where "random" is a base62 random string that uniquely identifies this go | ||||
| // process, and where the last number is an atomically incremented request | ||||
| // counter. | ||||
| func RequestID(next http.Handler) http.Handler { | ||||
| 	fn := func(w http.ResponseWriter, r *http.Request) { | ||||
| 		ctx := r.Context() | ||||
| 		requestID := r.Header.Get(RequestIDHeader) | ||||
| 		if requestID == "" { | ||||
| 			myid := atomic.AddUint64(&reqid, 1) | ||||
| 			requestID = fmt.Sprintf("%s-%06d", prefix, myid) | ||||
| 		} | ||||
| 		ctx = context.WithValue(ctx, RequestIDKey, requestID) | ||||
| 		next.ServeHTTP(w, r.WithContext(ctx)) | ||||
| 	} | ||||
| 	return http.HandlerFunc(fn) | ||||
| } | ||||
|  | ||||
| // GetReqID returns a request ID from the given context if one is present. | ||||
| // Returns the empty string if a request ID cannot be found. | ||||
| func GetReqID(ctx context.Context) string { | ||||
| 	if ctx == nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	if reqID, ok := ctx.Value(RequestIDKey).(string); ok { | ||||
| 		return reqID | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| // NextRequestID generates the next request ID in the sequence. | ||||
| func NextRequestID() uint64 { | ||||
| 	return atomic.AddUint64(&reqid, 1) | ||||
| } | ||||
							
								
								
									
										160
									
								
								vendor/github.com/go-chi/chi/middleware/route_headers.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								vendor/github.com/go-chi/chi/middleware/route_headers.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,160 @@ | ||||
| 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 string, 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 { | ||||
| 	MatchAny   []Pattern | ||||
| 	MatchOne   Pattern | ||||
| 	Middleware func(next http.Handler) http.Handler | ||||
| } | ||||
|  | ||||
| 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 { | ||||
| 		if p.prefix == v { | ||||
| 			return true | ||||
| 		} else { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return len(v) >= len(p.prefix+p.suffix) && strings.HasPrefix(v, p.prefix) && strings.HasSuffix(v, p.suffix) | ||||
| } | ||||
							
								
								
									
										56
									
								
								vendor/github.com/go-chi/chi/middleware/strip.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								vendor/github.com/go-chi/chi/middleware/strip.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| package middleware | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/go-chi/chi" | ||||
| ) | ||||
|  | ||||
| // StripSlashes is a middleware that will match request paths with a trailing | ||||
| // slash, strip it from the path and continue routing through the mux, if a route | ||||
| // matches, then it will serve the handler. | ||||
| 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 != "" { | ||||
| 			path = rctx.RoutePath | ||||
| 		} else { | ||||
| 			path = r.URL.Path | ||||
| 		} | ||||
| 		if len(path) > 1 && path[len(path)-1] == '/' { | ||||
| 			rctx.RoutePath = path[:len(path)-1] | ||||
| 		} | ||||
| 		next.ServeHTTP(w, r) | ||||
| 	} | ||||
| 	return http.HandlerFunc(fn) | ||||
| } | ||||
|  | ||||
| // RedirectSlashes is a middleware that will match request paths with a trailing | ||||
| // slash and redirect to the same path, less the trailing slash. | ||||
| // | ||||
| // NOTE: RedirectSlashes middleware is *incompatible* with http.FileServer, | ||||
| // see https://github.com/go-chi/chi/issues/343 | ||||
| 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 != "" { | ||||
| 			path = rctx.RoutePath | ||||
| 		} else { | ||||
| 			path = r.URL.Path | ||||
| 		} | ||||
| 		if len(path) > 1 && path[len(path)-1] == '/' { | ||||
| 			if r.URL.RawQuery != "" { | ||||
| 				path = fmt.Sprintf("%s?%s", path[:len(path)-1], r.URL.RawQuery) | ||||
| 			} else { | ||||
| 				path = path[:len(path)-1] | ||||
| 			} | ||||
| 			http.Redirect(w, r, path, 301) | ||||
| 			return | ||||
| 		} | ||||
| 		next.ServeHTTP(w, r) | ||||
| 	} | ||||
| 	return http.HandlerFunc(fn) | ||||
| } | ||||
							
								
								
									
										63
									
								
								vendor/github.com/go-chi/chi/middleware/terminal.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								vendor/github.com/go-chi/chi/middleware/terminal.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| package middleware | ||||
|  | ||||
| // Ported from Goji's middleware, source: | ||||
| // https://github.com/zenazn/goji/tree/master/web/middleware | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// Normal colors | ||||
| 	nBlack   = []byte{'\033', '[', '3', '0', 'm'} | ||||
| 	nRed     = []byte{'\033', '[', '3', '1', 'm'} | ||||
| 	nGreen   = []byte{'\033', '[', '3', '2', 'm'} | ||||
| 	nYellow  = []byte{'\033', '[', '3', '3', 'm'} | ||||
| 	nBlue    = []byte{'\033', '[', '3', '4', 'm'} | ||||
| 	nMagenta = []byte{'\033', '[', '3', '5', 'm'} | ||||
| 	nCyan    = []byte{'\033', '[', '3', '6', 'm'} | ||||
| 	nWhite   = []byte{'\033', '[', '3', '7', 'm'} | ||||
| 	// Bright colors | ||||
| 	bBlack   = []byte{'\033', '[', '3', '0', ';', '1', 'm'} | ||||
| 	bRed     = []byte{'\033', '[', '3', '1', ';', '1', 'm'} | ||||
| 	bGreen   = []byte{'\033', '[', '3', '2', ';', '1', 'm'} | ||||
| 	bYellow  = []byte{'\033', '[', '3', '3', ';', '1', 'm'} | ||||
| 	bBlue    = []byte{'\033', '[', '3', '4', ';', '1', 'm'} | ||||
| 	bMagenta = []byte{'\033', '[', '3', '5', ';', '1', 'm'} | ||||
| 	bCyan    = []byte{'\033', '[', '3', '6', ';', '1', 'm'} | ||||
| 	bWhite   = []byte{'\033', '[', '3', '7', ';', '1', 'm'} | ||||
|  | ||||
| 	reset = []byte{'\033', '[', '0', 'm'} | ||||
| ) | ||||
|  | ||||
| var IsTTY bool | ||||
|  | ||||
| func init() { | ||||
| 	// This is sort of cheating: if stdout is a character device, we assume | ||||
| 	// that means it's a TTY. Unfortunately, there are many non-TTY | ||||
| 	// character devices, but fortunately stdout is rarely set to any of | ||||
| 	// them. | ||||
| 	// | ||||
| 	// We could solve this properly by pulling in a dependency on | ||||
| 	// code.google.com/p/go.crypto/ssh/terminal, for instance, but as a | ||||
| 	// heuristic for whether to print in color or in black-and-white, I'd | ||||
| 	// really rather not. | ||||
| 	fi, err := os.Stdout.Stat() | ||||
| 	if err == nil { | ||||
| 		m := os.ModeDevice | os.ModeCharDevice | ||||
| 		IsTTY = fi.Mode()&m == m | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // colorWrite | ||||
| func cW(w io.Writer, useColor bool, color []byte, s string, args ...interface{}) { | ||||
| 	if IsTTY && useColor { | ||||
| 		w.Write(color) | ||||
| 	} | ||||
| 	fmt.Fprintf(w, s, args...) | ||||
| 	if IsTTY && useColor { | ||||
| 		w.Write(reset) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										132
									
								
								vendor/github.com/go-chi/chi/middleware/throttle.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								vendor/github.com/go-chi/chi/middleware/throttle.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 { | ||||
| 	Limit          int | ||||
| 	BacklogLimit   int | ||||
| 	BacklogTimeout time.Duration | ||||
| 	RetryAfterFn   func(ctxDone bool) 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 currentl 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 int, 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 | ||||
| 	backlogTimeout time.Duration | ||||
| 	retryAfterFn   func(ctxDone bool) 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()))) | ||||
| } | ||||
							
								
								
									
										49
									
								
								vendor/github.com/go-chi/chi/middleware/timeout.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								vendor/github.com/go-chi/chi/middleware/timeout.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| package middleware | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // Timeout is a middleware that cancels ctx after a given timeout and return | ||||
| // a 504 Gateway Timeout error to the client. | ||||
| // | ||||
| // It's required that you select the ctx.Done() channel to check for the signal | ||||
| // if the context has reached its deadline and return, otherwise the timeout | ||||
| // signal will be just ignored. | ||||
| // | ||||
| // 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 | ||||
| // | ||||
| // 	 select { | ||||
| // 	 case <-ctx.Done(): | ||||
| // 	 	return | ||||
| // | ||||
| // 	 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) { | ||||
| 			ctx, cancel := context.WithTimeout(r.Context(), timeout) | ||||
| 			defer func() { | ||||
| 				cancel() | ||||
| 				if ctx.Err() == context.DeadlineExceeded { | ||||
| 					w.WriteHeader(http.StatusGatewayTimeout) | ||||
| 				} | ||||
| 			}() | ||||
|  | ||||
| 			r = r.WithContext(ctx) | ||||
| 			next.ServeHTTP(w, r) | ||||
| 		} | ||||
| 		return http.HandlerFunc(fn) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										72
									
								
								vendor/github.com/go-chi/chi/middleware/url_format.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								vendor/github.com/go-chi/chi/middleware/url_format.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| package middleware | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/go-chi/chi" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// URLFormatCtxKey is the context.Context key to store the URL format data | ||||
| 	// for a request. | ||||
| 	URLFormatCtxKey = &contextKey{"URLFormat"} | ||||
| ) | ||||
|  | ||||
| // URLFormat is a middleware that parses the url extension from a request path and stores it | ||||
| // on the context as a string under the key `middleware.URLFormatCtxKey`. The middleware will | ||||
| // trim the suffix from the routing path and continue routing. | ||||
| // | ||||
| // 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` | ||||
| // | ||||
| //  func routes() http.Handler { | ||||
| //    r := chi.NewRouter() | ||||
| //    r.Use(middleware.URLFormat) | ||||
| // | ||||
| //    r.Get("/articles/{id}", ListArticles) | ||||
| // | ||||
| //    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 URLFormat(next http.Handler) http.Handler { | ||||
| 	fn := func(w http.ResponseWriter, r *http.Request) { | ||||
| 		ctx := r.Context() | ||||
|  | ||||
| 		var format string | ||||
| 		path := r.URL.Path | ||||
|  | ||||
| 		if strings.Index(path, ".") > 0 { | ||||
| 			base := strings.LastIndex(path, "/") | ||||
| 			idx := strings.Index(path[base:], ".") | ||||
|  | ||||
| 			if idx > 0 { | ||||
| 				idx += base | ||||
| 				format = path[idx+1:] | ||||
|  | ||||
| 				rctx := chi.RouteContext(r.Context()) | ||||
| 				rctx.RoutePath = path[:idx] | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		r = r.WithContext(context.WithValue(ctx, URLFormatCtxKey, format)) | ||||
|  | ||||
| 		next.ServeHTTP(w, r) | ||||
| 	} | ||||
| 	return http.HandlerFunc(fn) | ||||
| } | ||||
							
								
								
									
										17
									
								
								vendor/github.com/go-chi/chi/middleware/value.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/go-chi/chi/middleware/value.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| package middleware | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| // 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 { | ||||
| 	return func(next http.Handler) http.Handler { | ||||
| 		fn := func(w http.ResponseWriter, r *http.Request) { | ||||
| 			r = r.WithContext(context.WithValue(r.Context(), key, val)) | ||||
| 			next.ServeHTTP(w, r) | ||||
| 		} | ||||
| 		return http.HandlerFunc(fn) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										180
									
								
								vendor/github.com/go-chi/chi/middleware/wrap_writer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								vendor/github.com/go-chi/chi/middleware/wrap_writer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | ||||
| package middleware | ||||
|  | ||||
| // The original work was derived from Goji's middleware, source: | ||||
| // https://github.com/zenazn/goji/tree/master/web/middleware | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| // NewWrapResponseWriter wraps an http.ResponseWriter, returning a proxy that allows you to | ||||
| // hook into various parts of the response process. | ||||
| func NewWrapResponseWriter(w http.ResponseWriter, protoMajor int) WrapResponseWriter { | ||||
| 	_, fl := w.(http.Flusher) | ||||
|  | ||||
| 	bw := basicWriter{ResponseWriter: w} | ||||
|  | ||||
| 	if protoMajor == 2 { | ||||
| 		_, ps := w.(http.Pusher) | ||||
| 		if fl && ps { | ||||
| 			return &http2FancyWriter{bw} | ||||
| 		} | ||||
| 	} else { | ||||
| 		_, hj := w.(http.Hijacker) | ||||
| 		_, rf := w.(io.ReaderFrom) | ||||
| 		if fl && hj && rf { | ||||
| 			return &httpFancyWriter{bw} | ||||
| 		} | ||||
| 	} | ||||
| 	if fl { | ||||
| 		return &flushWriter{bw} | ||||
| 	} | ||||
|  | ||||
| 	return &bw | ||||
| } | ||||
|  | ||||
| // WrapResponseWriter is a proxy around an http.ResponseWriter that allows you to hook | ||||
| // into various parts of the response process. | ||||
| type WrapResponseWriter interface { | ||||
| 	http.ResponseWriter | ||||
| 	// Status returns the HTTP status of the request, or 0 if one has not | ||||
| 	// yet been sent. | ||||
| 	Status() int | ||||
| 	// BytesWritten returns the total number of bytes sent to the client. | ||||
| 	BytesWritten() int | ||||
| 	// Tee causes the response body to be written to the given io.Writer in | ||||
| 	// addition to proxying the writes through. Only one io.Writer can be | ||||
| 	// tee'd to at once: setting a second one will overwrite the first. | ||||
| 	// Writes will be sent to the proxy before being written to this | ||||
| 	// io.Writer. It is illegal for the tee'd writer to be modified | ||||
| 	// concurrently with writes. | ||||
| 	Tee(io.Writer) | ||||
| 	// Unwrap returns the original proxied target. | ||||
| 	Unwrap() http.ResponseWriter | ||||
| } | ||||
|  | ||||
| // basicWriter wraps a http.ResponseWriter that implements the minimal | ||||
| // http.ResponseWriter interface. | ||||
| type basicWriter struct { | ||||
| 	http.ResponseWriter | ||||
| 	wroteHeader bool | ||||
| 	code        int | ||||
| 	bytes       int | ||||
| 	tee         io.Writer | ||||
| } | ||||
|  | ||||
| func (b *basicWriter) WriteHeader(code int) { | ||||
| 	if !b.wroteHeader { | ||||
| 		b.code = code | ||||
| 		b.wroteHeader = true | ||||
| 		b.ResponseWriter.WriteHeader(code) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *basicWriter) Write(buf []byte) (int, error) { | ||||
| 	b.maybeWriteHeader() | ||||
| 	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 | ||||
| 		} | ||||
| 	} | ||||
| 	b.bytes += n | ||||
| 	return n, err | ||||
| } | ||||
|  | ||||
| func (b *basicWriter) maybeWriteHeader() { | ||||
| 	if !b.wroteHeader { | ||||
| 		b.WriteHeader(http.StatusOK) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *basicWriter) Status() int { | ||||
| 	return b.code | ||||
| } | ||||
|  | ||||
| func (b *basicWriter) BytesWritten() int { | ||||
| 	return b.bytes | ||||
| } | ||||
|  | ||||
| func (b *basicWriter) Tee(w io.Writer) { | ||||
| 	b.tee = w | ||||
| } | ||||
|  | ||||
| func (b *basicWriter) Unwrap() http.ResponseWriter { | ||||
| 	return b.ResponseWriter | ||||
| } | ||||
|  | ||||
| type flushWriter struct { | ||||
| 	basicWriter | ||||
| } | ||||
|  | ||||
| func (f *flushWriter) Flush() { | ||||
| 	f.wroteHeader = true | ||||
| 	fl := f.basicWriter.ResponseWriter.(http.Flusher) | ||||
| 	fl.Flush() | ||||
| } | ||||
|  | ||||
| var _ http.Flusher = &flushWriter{} | ||||
|  | ||||
| // 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 | ||||
| // make the proxied object support the full method set of the proxied object. | ||||
| type httpFancyWriter struct { | ||||
| 	basicWriter | ||||
| } | ||||
|  | ||||
| func (f *httpFancyWriter) Flush() { | ||||
| 	f.wroteHeader = true | ||||
| 	fl := f.basicWriter.ResponseWriter.(http.Flusher) | ||||
| 	fl.Flush() | ||||
| } | ||||
|  | ||||
| func (f *httpFancyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { | ||||
| 	hj := f.basicWriter.ResponseWriter.(http.Hijacker) | ||||
| 	return hj.Hijack() | ||||
| } | ||||
|  | ||||
| func (f *http2FancyWriter) Push(target string, opts *http.PushOptions) error { | ||||
| 	return f.basicWriter.ResponseWriter.(http.Pusher).Push(target, opts) | ||||
| } | ||||
|  | ||||
| func (f *httpFancyWriter) ReadFrom(r io.Reader) (int64, error) { | ||||
| 	if f.basicWriter.tee != nil { | ||||
| 		n, err := io.Copy(&f.basicWriter, r) | ||||
| 		f.basicWriter.bytes += int(n) | ||||
| 		return n, err | ||||
| 	} | ||||
| 	rf := f.basicWriter.ResponseWriter.(io.ReaderFrom) | ||||
| 	f.basicWriter.maybeWriteHeader() | ||||
| 	n, err := rf.ReadFrom(r) | ||||
| 	f.basicWriter.bytes += int(n) | ||||
| 	return n, err | ||||
| } | ||||
|  | ||||
| var _ http.Flusher = &httpFancyWriter{} | ||||
| var _ http.Hijacker = &httpFancyWriter{} | ||||
| var _ http.Pusher = &http2FancyWriter{} | ||||
| var _ io.ReaderFrom = &httpFancyWriter{} | ||||
|  | ||||
| // http2FancyWriter is a HTTP2 writer that additionally satisfies | ||||
| // http.Flusher, and io.ReaderFrom. It exists for the common case | ||||
| // of wrapping the http.ResponseWriter that package http gives you, in order to | ||||
| // make the proxied object support the full method set of the proxied object. | ||||
| type http2FancyWriter struct { | ||||
| 	basicWriter | ||||
| } | ||||
|  | ||||
| func (f *http2FancyWriter) Flush() { | ||||
| 	f.wroteHeader = true | ||||
| 	fl := f.basicWriter.ResponseWriter.(http.Flusher) | ||||
| 	fl.Flush() | ||||
| } | ||||
|  | ||||
| var _ http.Flusher = &http2FancyWriter{} | ||||
							
								
								
									
										466
									
								
								vendor/github.com/go-chi/chi/mux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										466
									
								
								vendor/github.com/go-chi/chi/mux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,466 @@ | ||||
| package chi | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| var _ Router = &Mux{} | ||||
|  | ||||
| // Mux is a simple HTTP route multiplexer that parses a request path, | ||||
| // records any URL params, and executes an end handler. It implements | ||||
| // the http.Handler interface and is friendly with the standard library. | ||||
| // | ||||
| // Mux is designed to be fast, minimal and offer a powerful API for building | ||||
| // modular and composable HTTP services with a large set of handlers. It's | ||||
| // 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 radix trie router | ||||
| 	tree *node | ||||
|  | ||||
| 	// 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 | ||||
| 	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 | ||||
| } | ||||
|  | ||||
| // NewMux returns a newly initialized Mux object that implements the Router | ||||
| // interface. | ||||
| func NewMux() *Mux { | ||||
| 	mux := &Mux{tree: &node{}, pool: &sync.Pool{}} | ||||
| 	mux.pool.New = func() interface{} { | ||||
| 		return NewRouteContext() | ||||
| 	} | ||||
| 	return mux | ||||
| } | ||||
|  | ||||
| // ServeHTTP is the single method of the http.Handler interface that makes | ||||
| // Mux interoperable with the standard library. It uses a sync.Pool to get and | ||||
| // reuse routing contexts for each request. | ||||
| func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	// Ensure the mux has some routes defined on the mux | ||||
| 	if mx.handler == nil { | ||||
| 		mx.NotFoundHandler().ServeHTTP(w, r) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Check if a routing context already exists from a parent router. | ||||
| 	rctx, _ := r.Context().Value(RouteCtxKey).(*Context) | ||||
| 	if rctx != nil { | ||||
| 		mx.handler.ServeHTTP(w, r) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Fetch a RouteContext object from the sync pool, and call the computed | ||||
| 	// mx.handler that is comprised of mx.middlewares + mx.routeHTTP. | ||||
| 	// Once the request is finished, reset the routing context and put it back | ||||
| 	// into the pool for reuse from another request. | ||||
| 	rctx = mx.pool.Get().(*Context) | ||||
| 	rctx.Reset() | ||||
| 	rctx.Routes = mx | ||||
|  | ||||
| 	// 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) | ||||
| } | ||||
|  | ||||
| // Use appends a middleware handler to the Mux middleware stack. | ||||
| // | ||||
| // The middleware stack for any Mux will execute before searching for a matching | ||||
| // route to a specific handler, which provides opportunity to respond early, | ||||
| // change the course of the request execution, or set request-scoped values for | ||||
| // the next http.Handler. | ||||
| func (mx *Mux) Use(middlewares ...func(http.Handler) http.Handler) { | ||||
| 	if mx.handler != nil { | ||||
| 		panic("chi: all middlewares must be defined before routes on a mux") | ||||
| 	} | ||||
| 	mx.middlewares = append(mx.middlewares, middlewares...) | ||||
| } | ||||
|  | ||||
| // 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) { | ||||
| 	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) { | ||||
| 	mx.handle(mALL, pattern, handlerFn) | ||||
| } | ||||
|  | ||||
| // Method adds the route `pattern` that matches `method` http method to | ||||
| // execute the `handler` http.Handler. | ||||
| func (mx *Mux) Method(method, pattern string, handler http.Handler) { | ||||
| 	m, ok := methodMap[strings.ToUpper(method)] | ||||
| 	if !ok { | ||||
| 		panic(fmt.Sprintf("chi: '%s' http method is not supported.", method)) | ||||
| 	} | ||||
| 	mx.handle(m, pattern, handler) | ||||
| } | ||||
|  | ||||
| // MethodFunc adds the route `pattern` that matches `method` http method to | ||||
| // execute the `handlerFn` http.HandlerFunc. | ||||
| func (mx *Mux) MethodFunc(method, pattern string, handlerFn http.HandlerFunc) { | ||||
| 	mx.Method(method, pattern, handlerFn) | ||||
| } | ||||
|  | ||||
| // Connect adds the route `pattern` that matches a CONNECT http method to | ||||
| // execute the `handlerFn` http.HandlerFunc. | ||||
| func (mx *Mux) Connect(pattern string, handlerFn http.HandlerFunc) { | ||||
| 	mx.handle(mCONNECT, pattern, handlerFn) | ||||
| } | ||||
|  | ||||
| // Delete adds the route `pattern` that matches a DELETE http method to | ||||
| // execute the `handlerFn` http.HandlerFunc. | ||||
| func (mx *Mux) Delete(pattern string, handlerFn http.HandlerFunc) { | ||||
| 	mx.handle(mDELETE, pattern, handlerFn) | ||||
| } | ||||
|  | ||||
| // Get adds the route `pattern` that matches a GET http method to | ||||
| // execute the `handlerFn` http.HandlerFunc. | ||||
| func (mx *Mux) Get(pattern string, handlerFn http.HandlerFunc) { | ||||
| 	mx.handle(mGET, pattern, handlerFn) | ||||
| } | ||||
|  | ||||
| // Head adds the route `pattern` that matches a HEAD http method to | ||||
| // execute the `handlerFn` http.HandlerFunc. | ||||
| 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 | ||||
| // execute the `handlerFn` http.HandlerFunc. | ||||
| func (mx *Mux) Options(pattern string, handlerFn http.HandlerFunc) { | ||||
| 	mx.handle(mOPTIONS, pattern, handlerFn) | ||||
| } | ||||
|  | ||||
| // Patch adds the route `pattern` that matches a PATCH http method to | ||||
| // execute the `handlerFn` http.HandlerFunc. | ||||
| func (mx *Mux) Patch(pattern string, handlerFn http.HandlerFunc) { | ||||
| 	mx.handle(mPATCH, pattern, handlerFn) | ||||
| } | ||||
|  | ||||
| // Post adds the route `pattern` that matches a POST http method to | ||||
| // execute the `handlerFn` http.HandlerFunc. | ||||
| func (mx *Mux) Post(pattern string, handlerFn http.HandlerFunc) { | ||||
| 	mx.handle(mPOST, pattern, handlerFn) | ||||
| } | ||||
|  | ||||
| // Put adds the route `pattern` that matches a PUT http method to | ||||
| // execute the `handlerFn` http.HandlerFunc. | ||||
| func (mx *Mux) Put(pattern string, handlerFn http.HandlerFunc) { | ||||
| 	mx.handle(mPUT, pattern, handlerFn) | ||||
| } | ||||
|  | ||||
| // Trace adds the route `pattern` that matches a TRACE http method to | ||||
| // execute the `handlerFn` http.HandlerFunc. | ||||
| func (mx *Mux) Trace(pattern string, handlerFn http.HandlerFunc) { | ||||
| 	mx.handle(mTRACE, pattern, handlerFn) | ||||
| } | ||||
|  | ||||
| // NotFound sets a custom http.HandlerFunc for routing paths that could | ||||
| // not be found. The default 404 handler is `http.NotFound`. | ||||
| func (mx *Mux) NotFound(handlerFn http.HandlerFunc) { | ||||
| 	// Build NotFound handler chain | ||||
| 	m := mx | ||||
| 	hFn := handlerFn | ||||
| 	if mx.inline && mx.parent != nil { | ||||
| 		m = mx.parent | ||||
| 		hFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP | ||||
| 	} | ||||
|  | ||||
| 	// Update the notFoundHandler from this point forward | ||||
| 	m.notFoundHandler = hFn | ||||
| 	m.updateSubRoutes(func(subMux *Mux) { | ||||
| 		if subMux.notFoundHandler == nil { | ||||
| 			subMux.NotFound(hFn) | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // MethodNotAllowed sets a custom http.HandlerFunc for routing paths where the | ||||
| // method is unresolved. The default handler returns a 405 with an empty body. | ||||
| func (mx *Mux) MethodNotAllowed(handlerFn http.HandlerFunc) { | ||||
| 	// Build MethodNotAllowed handler chain | ||||
| 	m := mx | ||||
| 	hFn := handlerFn | ||||
| 	if mx.inline && mx.parent != nil { | ||||
| 		m = mx.parent | ||||
| 		hFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP | ||||
| 	} | ||||
|  | ||||
| 	// Update the methodNotAllowedHandler from this point forward | ||||
| 	m.methodNotAllowedHandler = hFn | ||||
| 	m.updateSubRoutes(func(subMux *Mux) { | ||||
| 		if subMux.methodNotAllowedHandler == nil { | ||||
| 			subMux.MethodNotAllowed(hFn) | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // 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 additional | ||||
| 	// middleware registration isn't allowed for this stack, like now. | ||||
| 	if !mx.inline && mx.handler == nil { | ||||
| 		mx.buildRouteHandler() | ||||
| 	} | ||||
|  | ||||
| 	// Copy middlewares from parent inline muxs | ||||
| 	var mws Middlewares | ||||
| 	if mx.inline { | ||||
| 		mws = make(Middlewares, len(mx.middlewares)) | ||||
| 		copy(mws, mx.middlewares) | ||||
| 	} | ||||
| 	mws = append(mws, middlewares...) | ||||
|  | ||||
| 	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 | ||||
| // 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) | ||||
| 	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/. | ||||
| func (mx *Mux) Route(pattern string, fn func(r Router)) Router { | ||||
| 	subRouter := NewRouter() | ||||
| 	if fn != nil { | ||||
| 		fn(subRouter) | ||||
| 	} | ||||
| 	mx.Mount(pattern, subRouter) | ||||
| 	return subRouter | ||||
| } | ||||
|  | ||||
| // Mount attaches another http.Handler or chi Router as a subrouter along a routing | ||||
| // path. It's very useful to split up a large API as many independent routers and | ||||
| // compose them as a single service using Mount. See _examples/. | ||||
| // | ||||
| // Note that Mount() simply sets a wildcard along the `pattern` that will continue | ||||
| // 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) { | ||||
| 	// Provide runtime safety for ensuring a pattern isn't mounted on an existing | ||||
| 	// routing pattern. | ||||
| 	if mx.tree.findPattern(pattern+"*") || mx.tree.findPattern(pattern+"/*") { | ||||
| 		panic(fmt.Sprintf("chi: attempting to Mount() a handler on an existing path, '%s'", pattern)) | ||||
| 	} | ||||
|  | ||||
| 	// Assign sub-Router's with the parent not found & method not allowed handler if not specified. | ||||
| 	subr, ok := handler.(*Mux) | ||||
| 	if ok && subr.notFoundHandler == nil && mx.notFoundHandler != nil { | ||||
| 		subr.NotFound(mx.notFoundHandler) | ||||
| 	} | ||||
| 	if ok && subr.methodNotAllowedHandler == nil && mx.methodNotAllowedHandler != nil { | ||||
| 		subr.MethodNotAllowed(mx.methodNotAllowedHandler) | ||||
| 	} | ||||
|  | ||||
| 	mountHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||
| 		rctx := RouteContext(r.Context()) | ||||
| 		rctx.RoutePath = mx.nextRoutePath(rctx) | ||||
| 		handler.ServeHTTP(w, r) | ||||
| 	}) | ||||
|  | ||||
| 	if pattern == "" || pattern[len(pattern)-1] != '/' { | ||||
| 		mx.handle(mALL|mSTUB, pattern, mountHandler) | ||||
| 		mx.handle(mALL|mSTUB, pattern+"/", mountHandler) | ||||
| 		pattern += "/" | ||||
| 	} | ||||
|  | ||||
| 	method := mALL | ||||
| 	subroutes, _ := handler.(Routes) | ||||
| 	if subroutes != nil { | ||||
| 		method |= mSTUB | ||||
| 	} | ||||
| 	n := mx.handle(method, pattern+"*", mountHandler) | ||||
|  | ||||
| 	if subroutes != nil { | ||||
| 		n.subroutes = subroutes | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Routes returns a slice of routing information from the tree, | ||||
| // useful for traversing available routes of a router. | ||||
| func (mx *Mux) Routes() []Route { | ||||
| 	return mx.tree.routes() | ||||
| } | ||||
|  | ||||
| // Middlewares returns a slice of middleware handler functions. | ||||
| func (mx *Mux) Middlewares() Middlewares { | ||||
| 	return mx.middlewares | ||||
| } | ||||
|  | ||||
| // Match searches the routing tree for a handler that matches the method/path. | ||||
| // It's similar to routing a http request, but without executing the handler | ||||
| // thereafter. | ||||
| // | ||||
| // Note: the *Context state is updated during execution, so manage | ||||
| // the state carefully or make a NewRouteContext(). | ||||
| func (mx *Mux) Match(rctx *Context, method, path string) bool { | ||||
| 	m, ok := methodMap[method] | ||||
| 	if !ok { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	node, _, h := mx.tree.FindRoute(rctx, m, path) | ||||
|  | ||||
| 	if node != nil && node.subroutes != nil { | ||||
| 		rctx.RoutePath = mx.nextRoutePath(rctx) | ||||
| 		return node.subroutes.Match(rctx, method, rctx.RoutePath) | ||||
| 	} | ||||
|  | ||||
| 	return h != nil | ||||
| } | ||||
|  | ||||
| // NotFoundHandler returns the default Mux 404 responder whenever a route | ||||
| // cannot be found. | ||||
| func (mx *Mux) NotFoundHandler() http.HandlerFunc { | ||||
| 	if mx.notFoundHandler != nil { | ||||
| 		return mx.notFoundHandler | ||||
| 	} | ||||
| 	return http.NotFound | ||||
| } | ||||
|  | ||||
| // MethodNotAllowedHandler returns the default Mux 405 responder whenever | ||||
| // a method cannot be resolved for a route. | ||||
| func (mx *Mux) MethodNotAllowedHandler() 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)) | ||||
| } | ||||
|  | ||||
| // handle registers a http.Handler in the routing tree for a particular http method | ||||
| // and routing pattern. | ||||
| func (mx *Mux) handle(method methodTyp, pattern string, handler http.Handler) *node { | ||||
| 	if len(pattern) == 0 || pattern[0] != '/' { | ||||
| 		panic(fmt.Sprintf("chi: routing pattern must begin with '/' in '%s'", pattern)) | ||||
| 	} | ||||
|  | ||||
| 	// Build the computed routing handler for this routing pattern. | ||||
| 	if !mx.inline && mx.handler == nil { | ||||
| 		mx.buildRouteHandler() | ||||
| 	} | ||||
|  | ||||
| 	// Build endpoint handler with inline middlewares for the route | ||||
| 	var h http.Handler | ||||
| 	if mx.inline { | ||||
| 		mx.handler = http.HandlerFunc(mx.routeHTTP) | ||||
| 		h = Chain(mx.middlewares...).Handler(handler) | ||||
| 	} else { | ||||
| 		h = handler | ||||
| 	} | ||||
|  | ||||
| 	// Add the endpoint to the tree and return the node | ||||
| 	return mx.tree.InsertRoute(method, pattern, h) | ||||
| } | ||||
|  | ||||
| // routeHTTP routes a http.Request through the Mux routing tree to serve | ||||
| // the matching handler for a particular http method. | ||||
| func (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	// Grab the route context object | ||||
| 	rctx := r.Context().Value(RouteCtxKey).(*Context) | ||||
|  | ||||
| 	// The request routing path | ||||
| 	routePath := rctx.RoutePath | ||||
| 	if routePath == "" { | ||||
| 		if r.URL.RawPath != "" { | ||||
| 			routePath = r.URL.RawPath | ||||
| 		} else { | ||||
| 			routePath = r.URL.Path | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Check if method is supported by chi | ||||
| 	if rctx.RouteMethod == "" { | ||||
| 		rctx.RouteMethod = r.Method | ||||
| 	} | ||||
| 	method, ok := methodMap[rctx.RouteMethod] | ||||
| 	if !ok { | ||||
| 		mx.MethodNotAllowedHandler().ServeHTTP(w, r) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Find the route | ||||
| 	if _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil { | ||||
| 		h.ServeHTTP(w, r) | ||||
| 		return | ||||
| 	} | ||||
| 	if rctx.methodNotAllowed { | ||||
| 		mx.MethodNotAllowedHandler().ServeHTTP(w, r) | ||||
| 	} else { | ||||
| 		mx.NotFoundHandler().ServeHTTP(w, r) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 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] | ||||
| 	} | ||||
| 	return routePath | ||||
| } | ||||
|  | ||||
| // Recursively update data on child routers. | ||||
| func (mx *Mux) updateSubRoutes(fn func(subMux *Mux)) { | ||||
| 	for _, r := range mx.tree.routes() { | ||||
| 		subMux, ok := r.SubRoutes.(*Mux) | ||||
| 		if !ok { | ||||
| 			continue | ||||
| 		} | ||||
| 		fn(subMux) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| } | ||||
							
								
								
									
										865
									
								
								vendor/github.com/go-chi/chi/tree.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										865
									
								
								vendor/github.com/go-chi/chi/tree.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,865 @@ | ||||
| package chi | ||||
|  | ||||
| // Radix tree implementation below is a based on the original work by | ||||
| // Armon Dadgar in https://github.com/armon/go-radix/blob/master/radix.go | ||||
| // (MIT licensed). It's been heavily modified for use as a HTTP routing tree. | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"math" | ||||
| 	"net/http" | ||||
| 	"regexp" | ||||
| 	"sort" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type methodTyp int | ||||
|  | ||||
| const ( | ||||
| 	mSTUB methodTyp = 1 << iota | ||||
| 	mCONNECT | ||||
| 	mDELETE | ||||
| 	mGET | ||||
| 	mHEAD | ||||
| 	mOPTIONS | ||||
| 	mPATCH | ||||
| 	mPOST | ||||
| 	mPUT | ||||
| 	mTRACE | ||||
| ) | ||||
|  | ||||
| var mALL = mCONNECT | mDELETE | mGET | mHEAD | | ||||
| 	mOPTIONS | mPATCH | mPOST | mPUT | mTRACE | ||||
|  | ||||
| var methodMap = map[string]methodTyp{ | ||||
| 	http.MethodConnect: mCONNECT, | ||||
| 	http.MethodDelete:  mDELETE, | ||||
| 	http.MethodGet:     mGET, | ||||
| 	http.MethodHead:    mHEAD, | ||||
| 	http.MethodOptions: mOPTIONS, | ||||
| 	http.MethodPatch:   mPATCH, | ||||
| 	http.MethodPost:    mPOST, | ||||
| 	http.MethodPut:     mPUT, | ||||
| 	http.MethodTrace:   mTRACE, | ||||
| } | ||||
|  | ||||
| // RegisterMethod adds support for custom HTTP method handlers, available | ||||
| // via Router#Method and Router#MethodFunc | ||||
| func RegisterMethod(method string) { | ||||
| 	if method == "" { | ||||
| 		return | ||||
| 	} | ||||
| 	method = strings.ToUpper(method) | ||||
| 	if _, ok := methodMap[method]; ok { | ||||
| 		return | ||||
| 	} | ||||
| 	n := len(methodMap) | ||||
| 	if n > strconv.IntSize { | ||||
| 		panic(fmt.Sprintf("chi: max number of methods reached (%d)", strconv.IntSize)) | ||||
| 	} | ||||
| 	mt := methodTyp(math.Exp2(float64(n))) | ||||
| 	methodMap[method] = mt | ||||
| 	mALL |= mt | ||||
| } | ||||
|  | ||||
| type nodeTyp uint8 | ||||
|  | ||||
| const ( | ||||
| 	ntStatic   nodeTyp = iota // /home | ||||
| 	ntRegexp                  // /{id:[0-9]+} | ||||
| 	ntParam                   // /{user} | ||||
| 	ntCatchAll                // /api/v1/* | ||||
| ) | ||||
|  | ||||
| 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 | ||||
|  | ||||
| 	// regexp matcher for regexp nodes | ||||
| 	rex *regexp.Regexp | ||||
|  | ||||
| 	// HTTP handler endpoints on the leaf node | ||||
| 	endpoints endpoints | ||||
|  | ||||
| 	// subroutes on the leaf node | ||||
| 	subroutes Routes | ||||
|  | ||||
| 	// child nodes should be stored in-order for iteration, | ||||
| 	// in groups of the node type. | ||||
| 	children [ntCatchAll + 1]nodes | ||||
| } | ||||
|  | ||||
| // endpoints is a mapping of http method constants to handlers | ||||
| // for a given route. | ||||
| type endpoints map[methodTyp]*endpoint | ||||
|  | ||||
| type endpoint struct { | ||||
| 	// endpoint handler | ||||
| 	handler http.Handler | ||||
|  | ||||
| 	// pattern is the routing pattern for handler nodes | ||||
| 	pattern string | ||||
|  | ||||
| 	// parameter keys recorded on handler nodes | ||||
| 	paramKeys []string | ||||
| } | ||||
|  | ||||
| func (s endpoints) Value(method methodTyp) *endpoint { | ||||
| 	mh, ok := s[method] | ||||
| 	if !ok { | ||||
| 		mh = &endpoint{} | ||||
| 		s[method] = mh | ||||
| 	} | ||||
| 	return mh | ||||
| } | ||||
|  | ||||
| func (n *node) InsertRoute(method methodTyp, pattern string, handler http.Handler) *node { | ||||
| 	var parent *node | ||||
| 	search := pattern | ||||
|  | ||||
| 	for { | ||||
| 		// Handle key exhaustion | ||||
| 		if len(search) == 0 { | ||||
| 			// Insert or update the node's leaf handler | ||||
| 			n.setEndpoint(method, handler, pattern) | ||||
| 			return n | ||||
| 		} | ||||
|  | ||||
| 		// We're going to be searching for a wild node next, | ||||
| 		// in this case, we need to get the tail | ||||
| 		var label = search[0] | ||||
| 		var segTail byte | ||||
| 		var segEndIdx int | ||||
| 		var segTyp nodeTyp | ||||
| 		var segRexpat string | ||||
| 		if label == '{' || label == '*' { | ||||
| 			segTyp, _, segRexpat, segTail, _, segEndIdx = patNextSegment(search) | ||||
| 		} | ||||
|  | ||||
| 		var prefix string | ||||
| 		if segTyp == ntRegexp { | ||||
| 			prefix = segRexpat | ||||
| 		} | ||||
|  | ||||
| 		// Look for the edge to attach to | ||||
| 		parent = n | ||||
| 		n = n.getEdge(segTyp, label, segTail, prefix) | ||||
|  | ||||
| 		// No edge, create one | ||||
| 		if n == nil { | ||||
| 			child := &node{label: label, tail: segTail, prefix: search} | ||||
| 			hn := parent.addChild(child, search) | ||||
| 			hn.setEndpoint(method, handler, pattern) | ||||
|  | ||||
| 			return hn | ||||
| 		} | ||||
|  | ||||
| 		// Found an edge to match the pattern | ||||
|  | ||||
| 		if n.typ > ntStatic { | ||||
| 			// We found a param node, trim the param from the search path and continue. | ||||
| 			// This param/wild pattern segment would already be on the tree from a previous | ||||
| 			// call to addChild when creating a new node. | ||||
| 			search = search[segEndIdx:] | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// Static nodes fall below here. | ||||
| 		// Determine longest prefix of the search key on match. | ||||
| 		commonPrefix := longestPrefix(search, n.prefix) | ||||
| 		if commonPrefix == len(n.prefix) { | ||||
| 			// the common prefix is as long as the current node's prefix we're attempting to insert. | ||||
| 			// keep the search going. | ||||
| 			search = search[commonPrefix:] | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// Split the node | ||||
| 		child := &node{ | ||||
| 			typ:    ntStatic, | ||||
| 			prefix: search[:commonPrefix], | ||||
| 		} | ||||
| 		parent.replaceChild(search[0], segTail, child) | ||||
|  | ||||
| 		// Restore the existing node | ||||
| 		n.label = n.prefix[commonPrefix] | ||||
| 		n.prefix = n.prefix[commonPrefix:] | ||||
| 		child.addChild(n, n.prefix) | ||||
|  | ||||
| 		// If the new key is a subset, set the method/handler on this node and finish. | ||||
| 		search = search[commonPrefix:] | ||||
| 		if len(search) == 0 { | ||||
| 			child.setEndpoint(method, handler, pattern) | ||||
| 			return child | ||||
| 		} | ||||
|  | ||||
| 		// Create a new edge for the node | ||||
| 		subchild := &node{ | ||||
| 			typ:    ntStatic, | ||||
| 			label:  search[0], | ||||
| 			prefix: search, | ||||
| 		} | ||||
| 		hn := child.addChild(subchild, search) | ||||
| 		hn.setEndpoint(method, handler, pattern) | ||||
| 		return hn | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // addChild appends the new `child` node to the tree using the `pattern` as the trie key. | ||||
| // For a URL router like chi's, we split the static, param, regexp and wildcard segments | ||||
| // into different nodes. In addition, addChild will recursively call itself until every | ||||
| // pattern segment is added to the url pattern tree as individual nodes, depending on type. | ||||
| func (n *node) addChild(child *node, prefix string) *node { | ||||
| 	search := prefix | ||||
|  | ||||
| 	// handler leaf node added to the tree is the child. | ||||
| 	// this may be overridden later down the flow | ||||
| 	hn := child | ||||
|  | ||||
| 	// Parse next segment | ||||
| 	segTyp, _, segRexpat, segTail, segStartIdx, segEndIdx := patNextSegment(search) | ||||
|  | ||||
| 	// Add child depending on next up segment | ||||
| 	switch segTyp { | ||||
|  | ||||
| 	case ntStatic: | ||||
| 		// Search prefix is all static (that is, has no params in path) | ||||
| 		// noop | ||||
|  | ||||
| 	default: | ||||
| 		// Search prefix contains a param, regexp or wildcard | ||||
|  | ||||
| 		if segTyp == ntRegexp { | ||||
| 			rex, err := regexp.Compile(segRexpat) | ||||
| 			if err != nil { | ||||
| 				panic(fmt.Sprintf("chi: invalid regexp pattern '%s' in route param", segRexpat)) | ||||
| 			} | ||||
| 			child.prefix = segRexpat | ||||
| 			child.rex = rex | ||||
| 		} | ||||
|  | ||||
| 		if segStartIdx == 0 { | ||||
| 			// Route starts with a param | ||||
| 			child.typ = segTyp | ||||
|  | ||||
| 			if segTyp == ntCatchAll { | ||||
| 				segStartIdx = -1 | ||||
| 			} else { | ||||
| 				segStartIdx = segEndIdx | ||||
| 			} | ||||
| 			if segStartIdx < 0 { | ||||
| 				segStartIdx = len(search) | ||||
| 			} | ||||
| 			child.tail = segTail // for params, we set the tail | ||||
|  | ||||
| 			if segStartIdx != len(search) { | ||||
| 				// add static edge for the remaining part, split the end. | ||||
| 				// its not possible to have adjacent param nodes, so its certainly | ||||
| 				// going to be a static node next. | ||||
|  | ||||
| 				search = search[segStartIdx:] // advance search position | ||||
|  | ||||
| 				nn := &node{ | ||||
| 					typ:    ntStatic, | ||||
| 					label:  search[0], | ||||
| 					prefix: search, | ||||
| 				} | ||||
| 				hn = child.addChild(nn, search) | ||||
| 			} | ||||
|  | ||||
| 		} else if segStartIdx > 0 { | ||||
| 			// Route has some param | ||||
|  | ||||
| 			// starts with a static segment | ||||
| 			child.typ = ntStatic | ||||
| 			child.prefix = search[:segStartIdx] | ||||
| 			child.rex = nil | ||||
|  | ||||
| 			// add the param edge node | ||||
| 			search = search[segStartIdx:] | ||||
|  | ||||
| 			nn := &node{ | ||||
| 				typ:   segTyp, | ||||
| 				label: search[0], | ||||
| 				tail:  segTail, | ||||
| 			} | ||||
| 			hn = child.addChild(nn, search) | ||||
|  | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	n.children[child.typ] = append(n.children[child.typ], child) | ||||
| 	n.children[child.typ].Sort() | ||||
| 	return hn | ||||
| } | ||||
|  | ||||
| func (n *node) replaceChild(label, tail byte, child *node) { | ||||
| 	for i := 0; i < len(n.children[child.typ]); i++ { | ||||
| 		if n.children[child.typ][i].label == label && n.children[child.typ][i].tail == tail { | ||||
| 			n.children[child.typ][i] = child | ||||
| 			n.children[child.typ][i].label = label | ||||
| 			n.children[child.typ][i].tail = tail | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	panic("chi: replacing missing child") | ||||
| } | ||||
|  | ||||
| func (n *node) getEdge(ntyp nodeTyp, label, tail byte, prefix string) *node { | ||||
| 	nds := n.children[ntyp] | ||||
| 	for i := 0; i < len(nds); i++ { | ||||
| 		if nds[i].label == label && nds[i].tail == tail { | ||||
| 			if ntyp == ntRegexp && nds[i].prefix != prefix { | ||||
| 				continue | ||||
| 			} | ||||
| 			return nds[i] | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| 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) | ||||
| 	} | ||||
|  | ||||
| 	paramKeys := patParamKeys(pattern) | ||||
|  | ||||
| 	if method&mSTUB == mSTUB { | ||||
| 		n.endpoints.Value(mSTUB).handler = handler | ||||
| 	} | ||||
| 	if method&mALL == mALL { | ||||
| 		h := n.endpoints.Value(mALL) | ||||
| 		h.handler = handler | ||||
| 		h.pattern = pattern | ||||
| 		h.paramKeys = paramKeys | ||||
| 		for _, m := range methodMap { | ||||
| 			h := n.endpoints.Value(m) | ||||
| 			h.handler = handler | ||||
| 			h.pattern = pattern | ||||
| 			h.paramKeys = paramKeys | ||||
| 		} | ||||
| 	} else { | ||||
| 		h := n.endpoints.Value(method) | ||||
| 		h.handler = handler | ||||
| 		h.pattern = pattern | ||||
| 		h.paramKeys = paramKeys | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (n *node) FindRoute(rctx *Context, method methodTyp, path string) (*node, endpoints, http.Handler) { | ||||
| 	// Reset the context routing pattern and params | ||||
| 	rctx.routePattern = "" | ||||
| 	rctx.routeParams.Keys = rctx.routeParams.Keys[:0] | ||||
| 	rctx.routeParams.Values = rctx.routeParams.Values[:0] | ||||
|  | ||||
| 	// Find the routing handlers for the path | ||||
| 	rn := n.findRoute(rctx, method, path) | ||||
| 	if rn == nil { | ||||
| 		return nil, nil, nil | ||||
| 	} | ||||
|  | ||||
| 	// Record the routing params in the request lifecycle | ||||
| 	rctx.URLParams.Keys = append(rctx.URLParams.Keys, rctx.routeParams.Keys...) | ||||
| 	rctx.URLParams.Values = append(rctx.URLParams.Values, rctx.routeParams.Values...) | ||||
|  | ||||
| 	// Record the routing pattern in the request lifecycle | ||||
| 	if rn.endpoints[method].pattern != "" { | ||||
| 		rctx.routePattern = rn.endpoints[method].pattern | ||||
| 		rctx.RoutePatterns = append(rctx.RoutePatterns, rctx.routePattern) | ||||
| 	} | ||||
|  | ||||
| 	return rn, rn.endpoints, rn.endpoints[method].handler | ||||
| } | ||||
|  | ||||
| // Recursive edge traversal by checking all nodeTyp groups along the way. | ||||
| // It's like searching through a multi-dimensional radix trie. | ||||
| func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node { | ||||
| 	nn := n | ||||
| 	search := path | ||||
|  | ||||
| 	for t, nds := range nn.children { | ||||
| 		ntyp := nodeTyp(t) | ||||
| 		if len(nds) == 0 { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		var xn *node | ||||
| 		xsearch := search | ||||
|  | ||||
| 		var label byte | ||||
| 		if search != "" { | ||||
| 			label = search[0] | ||||
| 		} | ||||
|  | ||||
| 		switch ntyp { | ||||
| 		case ntStatic: | ||||
| 			xn = nds.findEdge(label) | ||||
| 			if xn == nil || !strings.HasPrefix(xsearch, xn.prefix) { | ||||
| 				continue | ||||
| 			} | ||||
| 			xsearch = xsearch[len(xn.prefix):] | ||||
|  | ||||
| 		case ntParam, ntRegexp: | ||||
| 			// short-circuit and return no matching route for empty param values | ||||
| 			if xsearch == "" { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			// serially loop through each node grouped by the tail delimiter | ||||
| 			for idx := 0; idx < len(nds); idx++ { | ||||
| 				xn = nds[idx] | ||||
|  | ||||
| 				// label for param nodes is the delimiter byte | ||||
| 				p := strings.IndexByte(xsearch, xn.tail) | ||||
|  | ||||
| 				if p < 0 { | ||||
| 					if xn.tail == '/' { | ||||
| 						p = len(xsearch) | ||||
| 					} else { | ||||
| 						continue | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				if ntyp == ntRegexp && xn.rex != nil { | ||||
| 					if !xn.rex.MatchString(xsearch[:p]) { | ||||
| 						continue | ||||
| 					} | ||||
| 				} else if strings.IndexByte(xsearch[:p], '/') != -1 { | ||||
| 					// avoid a match across path segments | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				prevlen := len(rctx.routeParams.Values) | ||||
| 				rctx.routeParams.Values = append(rctx.routeParams.Values, xsearch[:p]) | ||||
| 				xsearch = xsearch[p:] | ||||
|  | ||||
| 				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 | ||||
| 						} | ||||
|  | ||||
| 						// 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) | ||||
| 			xn = nds[0] | ||||
| 			xsearch = "" | ||||
| 		} | ||||
|  | ||||
| 		if xn == nil { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// did we find it yet? | ||||
| 		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 | ||||
| 				} | ||||
|  | ||||
| 				// flag that the routing context found a route, but not a corresponding | ||||
| 				// supported method | ||||
| 				rctx.methodNotAllowed = true | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// recursively find the next node.. | ||||
| 		fin := xn.findRoute(rctx, method, xsearch) | ||||
| 		if fin != nil { | ||||
| 			return fin | ||||
| 		} | ||||
|  | ||||
| 		// Did not find final handler, let's remove the param here if it was set | ||||
| 		if xn.typ > ntStatic { | ||||
| 			if len(rctx.routeParams.Values) > 0 { | ||||
| 				rctx.routeParams.Values = rctx.routeParams.Values[:len(rctx.routeParams.Values)-1] | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (n *node) findEdge(ntyp nodeTyp, label byte) *node { | ||||
| 	nds := n.children[ntyp] | ||||
| 	num := len(nds) | ||||
| 	idx := 0 | ||||
|  | ||||
| 	switch ntyp { | ||||
| 	case ntStatic, ntParam, ntRegexp: | ||||
| 		i, j := 0, num-1 | ||||
| 		for i <= j { | ||||
| 			idx = i + (j-i)/2 | ||||
| 			if label > nds[idx].label { | ||||
| 				i = idx + 1 | ||||
| 			} else if label < nds[idx].label { | ||||
| 				j = idx - 1 | ||||
| 			} else { | ||||
| 				i = num // breaks cond | ||||
| 			} | ||||
| 		} | ||||
| 		if nds[idx].label != label { | ||||
| 			return nil | ||||
| 		} | ||||
| 		return nds[idx] | ||||
|  | ||||
| 	default: // catch all | ||||
| 		return nds[idx] | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (n *node) isLeaf() bool { | ||||
| 	return n.endpoints != nil | ||||
| } | ||||
|  | ||||
| func (n *node) findPattern(pattern string) bool { | ||||
| 	nn := n | ||||
| 	for _, nds := range nn.children { | ||||
| 		if len(nds) == 0 { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		n = nn.findEdge(nds[0].typ, pattern[0]) | ||||
| 		if n == nil { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		var idx int | ||||
| 		var xpattern string | ||||
|  | ||||
| 		switch n.typ { | ||||
| 		case ntStatic: | ||||
| 			idx = longestPrefix(pattern, n.prefix) | ||||
| 			if idx < len(n.prefix) { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 		case ntParam, ntRegexp: | ||||
| 			idx = strings.IndexByte(pattern, '}') + 1 | ||||
|  | ||||
| 		case ntCatchAll: | ||||
| 			idx = longestPrefix(pattern, "*") | ||||
|  | ||||
| 		default: | ||||
| 			panic("chi: unknown node type") | ||||
| 		} | ||||
|  | ||||
| 		xpattern = pattern[idx:] | ||||
| 		if len(xpattern) == 0 { | ||||
| 			return true | ||||
| 		} | ||||
|  | ||||
| 		return n.findPattern(xpattern) | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (n *node) routes() []Route { | ||||
| 	rts := []Route{} | ||||
|  | ||||
| 	n.walk(func(eps endpoints, subroutes Routes) bool { | ||||
| 		if eps[mSTUB] != nil && eps[mSTUB].handler != nil && subroutes == nil { | ||||
| 			return false | ||||
| 		} | ||||
|  | ||||
| 		// Group methodHandlers by unique patterns | ||||
| 		pats := make(map[string]endpoints) | ||||
|  | ||||
| 		for mt, h := range eps { | ||||
| 			if h.pattern == "" { | ||||
| 				continue | ||||
| 			} | ||||
| 			p, ok := pats[h.pattern] | ||||
| 			if !ok { | ||||
| 				p = endpoints{} | ||||
| 				pats[h.pattern] = p | ||||
| 			} | ||||
| 			p[mt] = h | ||||
| 		} | ||||
|  | ||||
| 		for p, mh := range pats { | ||||
| 			hs := make(map[string]http.Handler) | ||||
| 			if mh[mALL] != nil && mh[mALL].handler != nil { | ||||
| 				hs["*"] = mh[mALL].handler | ||||
| 			} | ||||
|  | ||||
| 			for mt, h := range mh { | ||||
| 				if h.handler == nil { | ||||
| 					continue | ||||
| 				} | ||||
| 				m := methodTypString(mt) | ||||
| 				if m == "" { | ||||
| 					continue | ||||
| 				} | ||||
| 				hs[m] = h.handler | ||||
| 			} | ||||
|  | ||||
| 			rt := Route{p, hs, subroutes} | ||||
| 			rts = append(rts, rt) | ||||
| 		} | ||||
|  | ||||
| 		return false | ||||
| 	}) | ||||
|  | ||||
| 	return rts | ||||
| } | ||||
|  | ||||
| func (n *node) walk(fn func(eps endpoints, subroutes Routes) bool) bool { | ||||
| 	// Visit the leaf values if any | ||||
| 	if (n.endpoints != nil || n.subroutes != nil) && fn(n.endpoints, n.subroutes) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// Recurse on the children | ||||
| 	for _, ns := range n.children { | ||||
| 		for _, cn := range ns { | ||||
| 			if cn.walk(fn) { | ||||
| 				return true | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // patNextSegment returns the next segment details from a pattern: | ||||
| // node type, param key, regexp string, param tail byte, param starting index, param ending index | ||||
| func patNextSegment(pattern string) (nodeTyp, string, string, byte, int, int) { | ||||
| 	ps := strings.Index(pattern, "{") | ||||
| 	ws := strings.Index(pattern, "*") | ||||
|  | ||||
| 	if ps < 0 && ws < 0 { | ||||
| 		return ntStatic, "", "", 0, 0, len(pattern) // we return the entire thing | ||||
| 	} | ||||
|  | ||||
| 	// Sanity check | ||||
| 	if ps >= 0 && ws >= 0 && ws < ps { | ||||
| 		panic("chi: wildcard '*' must be the last pattern in a route, otherwise use a '{param}'") | ||||
| 	} | ||||
|  | ||||
| 	var tail byte = '/' // Default endpoint tail to / byte | ||||
|  | ||||
| 	if ps >= 0 { | ||||
| 		// Param/Regexp pattern is next | ||||
| 		nt := ntParam | ||||
|  | ||||
| 		// Read to closing } taking into account opens and closes in curl count (cc) | ||||
| 		cc := 0 | ||||
| 		pe := ps | ||||
| 		for i, c := range pattern[ps:] { | ||||
| 			if c == '{' { | ||||
| 				cc++ | ||||
| 			} else if c == '}' { | ||||
| 				cc-- | ||||
| 				if cc == 0 { | ||||
| 					pe = ps + i | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		if pe == ps { | ||||
| 			panic("chi: route param closing delimiter '}' is missing") | ||||
| 		} | ||||
|  | ||||
| 		key := pattern[ps+1 : pe] | ||||
| 		pe++ // set end to next position | ||||
|  | ||||
| 		if pe < len(pattern) { | ||||
| 			tail = pattern[pe] | ||||
| 		} | ||||
|  | ||||
| 		var rexpat string | ||||
| 		if idx := strings.Index(key, ":"); idx >= 0 { | ||||
| 			nt = ntRegexp | ||||
| 			rexpat = key[idx+1:] | ||||
| 			key = key[:idx] | ||||
| 		} | ||||
|  | ||||
| 		if len(rexpat) > 0 { | ||||
| 			if rexpat[0] != '^' { | ||||
| 				rexpat = "^" + rexpat | ||||
| 			} | ||||
| 			if rexpat[len(rexpat)-1] != '$' { | ||||
| 				rexpat += "$" | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return nt, key, rexpat, tail, ps, pe | ||||
| 	} | ||||
|  | ||||
| 	// Wildcard pattern as finale | ||||
| 	if ws < len(pattern)-1 { | ||||
| 		panic("chi: wildcard '*' must be the last value in a route. trim trailing text or use a '{param}' instead") | ||||
| 	} | ||||
| 	return ntCatchAll, "*", "", 0, ws, len(pattern) | ||||
| } | ||||
|  | ||||
| func patParamKeys(pattern string) []string { | ||||
| 	pat := pattern | ||||
| 	paramKeys := []string{} | ||||
| 	for { | ||||
| 		ptyp, paramKey, _, _, _, e := patNextSegment(pat) | ||||
| 		if ptyp == ntStatic { | ||||
| 			return paramKeys | ||||
| 		} | ||||
| 		for i := 0; i < len(paramKeys); i++ { | ||||
| 			if paramKeys[i] == paramKey { | ||||
| 				panic(fmt.Sprintf("chi: routing pattern '%s' contains duplicate param key, '%s'", pattern, paramKey)) | ||||
| 			} | ||||
| 		} | ||||
| 		paramKeys = append(paramKeys, paramKey) | ||||
| 		pat = pat[e:] | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // longestPrefix finds the length of the shared prefix | ||||
| // of two strings | ||||
| func longestPrefix(k1, k2 string) int { | ||||
| 	max := len(k1) | ||||
| 	if l := len(k2); l < max { | ||||
| 		max = l | ||||
| 	} | ||||
| 	var i int | ||||
| 	for i = 0; i < max; i++ { | ||||
| 		if k1[i] != k2[i] { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	return i | ||||
| } | ||||
|  | ||||
| func methodTypString(method methodTyp) string { | ||||
| 	for s, t := range methodMap { | ||||
| 		if method == t { | ||||
| 			return s | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| type nodes []*node | ||||
|  | ||||
| // Sort the list of nodes by label | ||||
| func (ns nodes) Sort()              { sort.Sort(ns); ns.tailSort() } | ||||
| func (ns nodes) Len() int           { return len(ns) } | ||||
| func (ns nodes) Swap(i, j int)      { ns[i], ns[j] = ns[j], ns[i] } | ||||
| func (ns nodes) Less(i, j int) bool { return ns[i].label < ns[j].label } | ||||
|  | ||||
| // tailSort pushes nodes with '/' as the tail to the end of the list for param nodes. | ||||
| // The list order determines the traversal order. | ||||
| func (ns nodes) tailSort() { | ||||
| 	for i := len(ns) - 1; i >= 0; i-- { | ||||
| 		if ns[i].typ > ntStatic && ns[i].tail == '/' { | ||||
| 			ns.Swap(i, len(ns)-1) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (ns nodes) findEdge(label byte) *node { | ||||
| 	num := len(ns) | ||||
| 	idx := 0 | ||||
| 	i, j := 0, num-1 | ||||
| 	for i <= j { | ||||
| 		idx = i + (j-i)/2 | ||||
| 		if label > ns[idx].label { | ||||
| 			i = idx + 1 | ||||
| 		} else if label < ns[idx].label { | ||||
| 			j = idx - 1 | ||||
| 		} else { | ||||
| 			i = num // breaks cond | ||||
| 		} | ||||
| 	} | ||||
| 	if ns[idx].label != label { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return ns[idx] | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| } | ||||
|  | ||||
| // WalkFunc is the type of the function called for each method and route visited by Walk. | ||||
| type WalkFunc func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error | ||||
|  | ||||
| // Walk walks any router tree that implements Routes interface. | ||||
| func Walk(r Routes, walkFn WalkFunc) error { | ||||
| 	return walk(r, walkFn, "") | ||||
| } | ||||
|  | ||||
| func walk(r Routes, walkFn WalkFunc, parentRoute string, parentMw ...func(http.Handler) http.Handler) error { | ||||
| 	for _, route := range r.Routes() { | ||||
| 		mws := make([]func(http.Handler) http.Handler, len(parentMw)) | ||||
| 		copy(mws, parentMw) | ||||
| 		mws = append(mws, r.Middlewares()...) | ||||
|  | ||||
| 		if route.SubRoutes != nil { | ||||
| 			if err := walk(route.SubRoutes, walkFn, parentRoute+route.Pattern, mws...); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		for method, handler := range route.Handlers { | ||||
| 			if method == "*" { | ||||
| 				// Ignore a "catchAll" method, since we pass down all the specific methods for each route. | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			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 { | ||||
| 					return err | ||||
| 				} | ||||
| 			} else { | ||||
| 				if err := walkFn(method, fullRoute, handler, mws...); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
		Reference in New Issue
	
	Block a user