vendor dependencies with dep
This commit is contained in:
parent
93d8310491
commit
1384296a47
2712 changed files with 965742 additions and 0 deletions
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
|
||||
19
vendor/github.com/go-chi/chi/.travis.yml
generated
vendored
Normal file
19
vendor/github.com/go-chi/chi/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- tip
|
||||
|
||||
install:
|
||||
- go get -u golang.org/x/tools/cmd/goimports
|
||||
- go get -u github.com/golang/lint/golint
|
||||
|
||||
script:
|
||||
- go get -d -t ./...
|
||||
- go vet ./...
|
||||
- golint ./...
|
||||
- go test ./...
|
||||
- >
|
||||
goimports -d -e ./ | grep '.*' && { echo; echo "Aborting due to non-empty goimports output."; exit 1; } || :
|
||||
96
vendor/github.com/go-chi/chi/CHANGELOG.md
generated
vendored
Normal file
96
vendor/github.com/go-chi/chi/CHANGELOG.md
generated
vendored
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# Changelog
|
||||
|
||||
## 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)
|
||||
|
||||
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.
|
||||
437
vendor/github.com/go-chi/chi/README.md
generated
vendored
Normal file
437
vendor/github.com/go-chi/chi/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,437 @@
|
|||
# <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 1.7+ 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, cancelations 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 1.7+ stdlib + net/http
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
* [rest](https://github.com/go-chi/chi/blob/master/_examples/rest/main.go) - REST APIs made easy, productive and maintainable
|
||||
* [logging](https://github.com/go-chi/chi/blob/master/_examples/logging/main.go) - Easy structured logging for any backend
|
||||
* [limits](https://github.com/go-chi/chi/blob/master/_examples/limits/main.go) - Timeouts and Throttling
|
||||
* [todos-resource](https://github.com/go-chi/chi/blob/master/_examples/todos-resource/main.go) - Struct routers/handlers, an example of another code layout style
|
||||
* [versions](https://github.com/go-chi/chi/blob/master/_examples/versions/main.go) - Demo of `chi/render` subpkg
|
||||
* [fileserver](https://github.com/go-chi/chi/blob/master/_examples/fileserver/main.go) - Easily serve static files
|
||||
* [graceful](https://github.com/go-chi/chi/blob/master/_examples/graceful/main.go) - Graceful context signaling and server shutdown
|
||||
|
||||
|
||||
**As easy as:**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
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](#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 design
|
||||
|
||||
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 of 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
|
||||
}
|
||||
```
|
||||
|
||||
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 handler using the new request context
|
||||
available in Go 1.7+. 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) {
|
||||
ctx := context.WithValue(r.Context(), "user", "123")
|
||||
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) {
|
||||
user := r.Context().Value("user").(string)
|
||||
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) {
|
||||
userID := chi.URLParam(r, "userID") // from a route like /users/{userID}
|
||||
|
||||
ctx := r.Context()
|
||||
key := ctx.Value("key").(string)
|
||||
|
||||
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/middlware Handler | description |
|
||||
|:----------------------|:---------------------------------------------------------------------------------
|
||||
| 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 |
|
||||
| StripSlashes | Strip slashes on routing paths |
|
||||
| Throttle | Puts a ceiling on the number of concurrent requests |
|
||||
| Timeout | Signals to the request context when the timeout deadline is reached |
|
||||
| URLFormat | Parse extension from url and put it on request context |
|
||||
| WithValue | Short-hand middleware to set a key/value on the request context |
|
||||
-----------------------------------------------------------------------------------------------------------
|
||||
|
||||
### Auxiliary middlewares
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------
|
||||
| package | description |
|
||||
|:-------------------------------------------------|:------------------------------------------------------
|
||||
| [cors](https://github.com/go-chi/cors) | Cross-origin resource sharing (CORS) |
|
||||
| [jwtauth](https://github.com/go-chi/jwtauth) | JWT authentication |
|
||||
| [httpcoala](https://github.com/go-chi/httpcoala) | HTTP request coalescer |
|
||||
| [chi-authz](https://github.com/casbin/chi-authz) | Request ACL via https://github.com/hsluoyz/casbin |
|
||||
-----------------------------------------------------------------------------------------------------------
|
||||
|
||||
please [submit a PR](./CONTRIBUTING.md) if you'd like to include a link to a chi-compatible middleware
|
||||
|
||||
|
||||
## 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 Aug 31, 2017 on Go 1.9.0
|
||||
|
||||
```shell
|
||||
BenchmarkChi_Param 3000000 607 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_Param5 2000000 935 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_Param20 1000000 1944 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_ParamWrite 2000000 664 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GithubStatic 2000000 627 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GithubParam 2000000 847 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GithubAll 10000 175556 ns/op 87700 B/op 609 allocs/op
|
||||
BenchmarkChi_GPlusStatic 3000000 566 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GPlusParam 2000000 652 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GPlus2Params 2000000 767 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GPlusAll 200000 9794 ns/op 5616 B/op 39 allocs/op
|
||||
BenchmarkChi_ParseStatic 3000000 590 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_ParseParam 2000000 656 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_Parse2Params 2000000 715 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_ParseAll 100000 18045 ns/op 11232 B/op 78 allocs/op
|
||||
BenchmarkChi_StaticAll 10000 108871 ns/op 67827 B/op 471 allocs/op
|
||||
```
|
||||
|
||||
Comparison with other routers: https://gist.github.com/pkieltyka/c089f309abeb179cfc4deaa519956d8c
|
||||
|
||||
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 1.7+ 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 including Pressly.com (of course) use chi to write REST services for their public
|
||||
APIs. But, REST is just a convention for managing state via HTTP, and there's a lot of other pieces
|
||||
required to write a complete client-server system or network of microservices.
|
||||
|
||||
Looking ahead beyond REST, I also recommend some newer works in the field coming from
|
||||
[gRPC](https://github.com/grpc/grpc-go), [NATS](https://nats.io), [go-kit](https://github.com/go-kit/kit)
|
||||
and even [graphql](https://github.com/graphql-go/graphql). They're all pretty cool with their
|
||||
own unique approaches and benefits. Specifically, I'd look at gRPC since it makes client-server
|
||||
communication feel like a single program on a single computer, no need to hand-write a client library
|
||||
and the request/response payloads are typed contracts. NATS is pretty amazing too as a super
|
||||
fast and lightweight pub-sub transport that can speak protobufs, with nice service discovery -
|
||||
an excellent combination with gRPC.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2015-present [Peter Kieltyka](https://github.com/pkieltyka)
|
||||
|
||||
Licensed under [MIT License](./LICENSE)
|
||||
|
||||
[GoDoc]: https://godoc.org/github.com/go-chi/chi
|
||||
[GoDoc 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
|
||||
15
vendor/github.com/go-chi/chi/_examples/chi.svg
generated
vendored
Executable file
15
vendor/github.com/go-chi/chi/_examples/chi.svg
generated
vendored
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="203px" height="83px" viewBox="0 0 203 83" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 39.1 (31720) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Slice 1</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="EPS-RGB">
|
||||
<path d="M27.082,82.653 C21.835,82.666 17.381,81.184 13.743,79.358 C4.828,74.882 0.404,67.014 0.404,55.897 C0.404,44.783 4.85,36.963 13.743,32.439 C17.405,30.66 21.85,29.56 27.082,29.143 L69.451,29.143 C72.066,29.143 74.107,30.241 75.571,32.439 C77.035,34.635 77.767,37.198 77.767,40.127 C77.767,43.057 77.035,45.62 75.571,47.817 C74.107,50.013 72.066,51.112 69.451,51.112 L27.082,51.112 C23.942,51.112 22.374,52.708 22.374,55.897 C22.374,59.089 23.942,60.694 27.082,60.685 L99.876,60.486 C102.491,60.479 104.532,61.586 105.996,63.782 C107.458,65.978 108.193,68.541 108.193,71.47 C108.193,74.4 107.46,76.963 105.996,79.16 C104.532,81.356 102.49,82.449 99.876,82.455 L27.082,82.653" id="Fill-1" fill="#01933F"></path>
|
||||
<path d="M180.865,74.676 L180.865,37.119 C180.865,34.504 181.964,32.491 184.16,31.027 C186.358,29.56 188.921,28.829 191.85,28.829 C194.778,28.829 197.341,29.56 199.54,31.027 C201.736,32.491 202.835,34.504 202.835,37.119 L202.835,74.676 C202.835,77.291 201.736,79.306 199.54,80.77 C197.341,82.234 194.778,82.967 191.85,82.967 C188.921,82.967 186.358,82.234 184.16,80.77 C181.964,79.306 180.865,77.291 180.865,74.676" id="Fill-2" fill="#01933F"></path>
|
||||
<path d="M191.85,22.866 C188.816,22.866 186.227,21.793 184.082,19.649 C181.938,17.504 180.865,14.915 180.865,11.881 C180.865,8.847 181.938,6.258 184.082,4.113 C186.227,1.969 188.816,0.898 191.85,0.898 C194.884,0.898 197.473,1.969 199.617,4.113 C201.762,6.258 202.835,8.847 202.835,11.881 C202.835,14.915 201.762,17.504 199.617,19.649 C197.473,21.793 194.884,22.866 191.85,22.866" id="Fill-3" fill="#83C21F"></path>
|
||||
<path d="M149.481,74.65 L149.481,55.82 C149.481,52.682 147.911,51.112 144.773,51.112 L113.389,51.112 C102.301,51.112 94.505,46.666 90.007,37.773 C88.229,34.113 87.13,29.666 86.711,24.436 L86.711,11.881 C86.711,9.266 87.811,7.225 90.007,5.761 C92.205,4.297 94.768,3.565 97.697,3.565 C100.625,3.565 103.188,4.297 105.386,5.761 C107.583,7.225 108.682,9.266 108.682,11.881 L108.682,24.436 C108.682,27.574 110.25,29.143 113.389,29.143 L144.773,29.143 C150.004,29.56 154.45,30.66 158.112,32.439 C167.005,36.937 171.451,44.731 171.451,55.82 L171.451,74.65 C171.451,77.266 170.352,79.306 168.156,80.77 C165.958,82.234 163.395,82.967 160.466,82.967 C157.536,82.967 154.973,82.234 152.776,80.77 C150.58,79.306 149.481,77.266 149.481,74.65" id="Fill-4" fill="#01933F"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
35
vendor/github.com/go-chi/chi/_examples/custom-handler/main.go
generated
vendored
Normal file
35
vendor/github.com/go-chi/chi/_examples/custom-handler/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
type Handler func(w http.ResponseWriter, r *http.Request) error
|
||||
|
||||
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err := h(w, r); err != nil {
|
||||
// handle returned error here.
|
||||
w.WriteHeader(503)
|
||||
w.Write([]byte("bad"))
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
r.Method("GET", "/", Handler(customHandler))
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
|
||||
func customHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
q := r.URL.Query().Get("err")
|
||||
|
||||
if q != "" {
|
||||
return errors.New(q)
|
||||
}
|
||||
|
||||
w.Write([]byte("foo"))
|
||||
return nil
|
||||
}
|
||||
1
vendor/github.com/go-chi/chi/_examples/fileserver/files/notes.txt
generated
vendored
Normal file
1
vendor/github.com/go-chi/chi/_examples/fileserver/files/notes.txt
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
Notessszzz
|
||||
44
vendor/github.com/go-chi/chi/_examples/fileserver/main.go
generated
vendored
Normal file
44
vendor/github.com/go-chi/chi/_examples/fileserver/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("hi"))
|
||||
})
|
||||
|
||||
workDir, _ := os.Getwd()
|
||||
filesDir := filepath.Join(workDir, "files")
|
||||
FileServer(r, "/files", http.Dir(filesDir))
|
||||
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
|
||||
// FileServer conveniently sets up a http.FileServer handler to serve
|
||||
// static files from a http.FileSystem.
|
||||
func FileServer(r chi.Router, path string, root http.FileSystem) {
|
||||
if strings.ContainsAny(path, "{}*") {
|
||||
panic("FileServer does not permit URL parameters.")
|
||||
}
|
||||
|
||||
fs := http.StripPrefix(path, http.FileServer(root))
|
||||
|
||||
if path != "/" && path[len(path)-1] != '/' {
|
||||
r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP)
|
||||
path += "/"
|
||||
}
|
||||
path += "*"
|
||||
|
||||
r.Get(path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fs.ServeHTTP(w, r)
|
||||
}))
|
||||
}
|
||||
115
vendor/github.com/go-chi/chi/_examples/graceful/main.go
generated
vendored
Normal file
115
vendor/github.com/go-chi/chi/_examples/graceful/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/valve"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Our graceful valve shut-off package to manage code preemption and
|
||||
// shutdown signaling.
|
||||
valv := valve.New()
|
||||
baseCtx := valv.Context()
|
||||
|
||||
// Example of a long running background worker thing..
|
||||
go func(ctx context.Context) {
|
||||
for {
|
||||
<-time.After(1 * time.Second)
|
||||
|
||||
func() {
|
||||
valve.Lever(ctx).Open()
|
||||
defer valve.Lever(ctx).Close()
|
||||
|
||||
// actual code doing stuff..
|
||||
fmt.Println("tick..")
|
||||
time.Sleep(2 * time.Second)
|
||||
// end-logic
|
||||
|
||||
// signal control..
|
||||
select {
|
||||
case <-valve.Lever(ctx).Stop():
|
||||
fmt.Println("valve is closed")
|
||||
return
|
||||
|
||||
case <-ctx.Done():
|
||||
fmt.Println("context is cancelled, go home.")
|
||||
return
|
||||
default:
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
}(baseCtx)
|
||||
|
||||
// HTTP service running in this program as well. The valve context is set
|
||||
// as a base context on the server listener at the point where we instantiate
|
||||
// the server - look lower.
|
||||
r := chi.NewRouter()
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.Logger)
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("sup"))
|
||||
})
|
||||
|
||||
r.Get("/slow", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
valve.Lever(r.Context()).Open()
|
||||
defer valve.Lever(r.Context()).Close()
|
||||
|
||||
select {
|
||||
case <-valve.Lever(r.Context()).Stop():
|
||||
fmt.Println("valve is closed. finish up..")
|
||||
|
||||
case <-time.After(5 * time.Second):
|
||||
// The above channel simulates some hard work.
|
||||
// We want this handler to complete successfully during a shutdown signal,
|
||||
// so consider the work here as some background routine to fetch a long running
|
||||
// search query to find as many results as possible, but, instead we cut it short
|
||||
// and respond with what we have so far. How a shutdown is handled is entirely
|
||||
// up to the developer, as some code blocks are preemptable, and others are not.
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
||||
w.Write([]byte(fmt.Sprintf("all done.\n")))
|
||||
})
|
||||
|
||||
srv := http.Server{Addr: ":3333", Handler: chi.ServerBaseContext(baseCtx, r)}
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
for range c {
|
||||
// sig is a ^C, handle it
|
||||
fmt.Println("shutting down..")
|
||||
|
||||
// first valv
|
||||
valv.Shutdown(20 * time.Second)
|
||||
|
||||
// create context with timeout
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// start http shutdown
|
||||
srv.Shutdown(ctx)
|
||||
|
||||
// verify, in worst case call cancel via defer
|
||||
select {
|
||||
case <-time.After(21 * time.Second):
|
||||
fmt.Println("not all connections done")
|
||||
case <-ctx.Done():
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
srv.ListenAndServe()
|
||||
}
|
||||
18
vendor/github.com/go-chi/chi/_examples/hello-world/main.go
generated
vendored
Normal file
18
vendor/github.com/go-chi/chi/_examples/hello-world/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.Logger)
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("hello world"))
|
||||
})
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
105
vendor/github.com/go-chi/chi/_examples/limits/main.go
generated
vendored
Normal file
105
vendor/github.com/go-chi/chi/_examples/limits/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
//
|
||||
// Limits
|
||||
// ======
|
||||
// This example demonstrates the use of Timeout, CloseNotify, and
|
||||
// Throttle middlewares.
|
||||
//
|
||||
// Timeout:
|
||||
// cancel a request if processing takes longer than 2.5 seconds,
|
||||
// server will respond with a http.StatusGatewayTimeout.
|
||||
//
|
||||
// CloseNotify:
|
||||
// cancel a request if the client disconnects.
|
||||
//
|
||||
// Throttle:
|
||||
// limit the number of in-flight requests along a particular
|
||||
// routing path and backlog the others.
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("root."))
|
||||
})
|
||||
|
||||
r.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("pong"))
|
||||
})
|
||||
|
||||
r.Get("/panic", func(w http.ResponseWriter, r *http.Request) {
|
||||
panic("test")
|
||||
})
|
||||
|
||||
// Slow handlers/operations.
|
||||
r.Group(func(r chi.Router) {
|
||||
// Stop processing when client disconnects.
|
||||
r.Use(middleware.CloseNotify)
|
||||
|
||||
// Stop processing after 2.5 seconds.
|
||||
r.Use(middleware.Timeout(2500 * time.Millisecond))
|
||||
|
||||
r.Get("/slow", func(w http.ResponseWriter, r *http.Request) {
|
||||
rand.Seed(time.Now().Unix())
|
||||
|
||||
// Processing will take 1-5 seconds.
|
||||
processTime := time.Duration(rand.Intn(4)+1) * time.Second
|
||||
|
||||
select {
|
||||
case <-r.Context().Done():
|
||||
return
|
||||
|
||||
case <-time.After(processTime):
|
||||
// The above channel simulates some hard work.
|
||||
}
|
||||
|
||||
w.Write([]byte(fmt.Sprintf("Processed in %v seconds\n", processTime)))
|
||||
})
|
||||
})
|
||||
|
||||
// Throttle very expensive handlers/operations.
|
||||
r.Group(func(r chi.Router) {
|
||||
// Stop processing after 30 seconds.
|
||||
r.Use(middleware.Timeout(30 * time.Second))
|
||||
|
||||
// Only one request will be processed at a time.
|
||||
r.Use(middleware.Throttle(1))
|
||||
|
||||
r.Get("/throttled", func(w http.ResponseWriter, r *http.Request) {
|
||||
select {
|
||||
case <-r.Context().Done():
|
||||
switch r.Context().Err() {
|
||||
case context.DeadlineExceeded:
|
||||
w.WriteHeader(504)
|
||||
w.Write([]byte("Processing too slow\n"))
|
||||
default:
|
||||
w.Write([]byte("Canceled\n"))
|
||||
}
|
||||
return
|
||||
|
||||
case <-time.After(5 * time.Second):
|
||||
// The above channel simulates some hard work.
|
||||
}
|
||||
|
||||
w.Write([]byte("Processed\n"))
|
||||
})
|
||||
})
|
||||
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
141
vendor/github.com/go-chi/chi/_examples/logging/main.go
generated
vendored
Normal file
141
vendor/github.com/go-chi/chi/_examples/logging/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
//
|
||||
// Custom Structured Logger
|
||||
// ========================
|
||||
// This example demonstrates how to use middleware.RequestLogger,
|
||||
// middleware.LogFormatter and middleware.LogEntry to build a structured
|
||||
// logger using the amazing sirupsen/logrus package as the logging
|
||||
// backend.
|
||||
//
|
||||
// Also: check out https://github.com/pressly/lg for an improved context
|
||||
// logger with support for HTTP request logging, based on the example
|
||||
// below.
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Setup the logger backend using sirupsen/logrus and configure
|
||||
// it to use a custom JSONFormatter. See the logrus docs for how to
|
||||
// configure the backend at github.com/sirupsen/logrus
|
||||
logger := logrus.New()
|
||||
logger.Formatter = &logrus.JSONFormatter{
|
||||
// disable, as we set our own
|
||||
DisableTimestamp: true,
|
||||
}
|
||||
|
||||
// Routes
|
||||
r := chi.NewRouter()
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(NewStructuredLogger(logger))
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("welcome"))
|
||||
})
|
||||
r.Get("/wait", func(w http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(1 * time.Second)
|
||||
LogEntrySetField(r, "wait", true)
|
||||
w.Write([]byte("hi"))
|
||||
})
|
||||
r.Get("/panic", func(w http.ResponseWriter, r *http.Request) {
|
||||
panic("oops")
|
||||
})
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
|
||||
// StructuredLogger is a simple, but powerful implementation of a custom structured
|
||||
// logger backed on logrus. I encourage users to copy it, adapt it and make it their
|
||||
// own. Also take a look at https://github.com/pressly/lg for a dedicated pkg based
|
||||
// on this work, designed for context-based http routers.
|
||||
|
||||
func NewStructuredLogger(logger *logrus.Logger) func(next http.Handler) http.Handler {
|
||||
return middleware.RequestLogger(&StructuredLogger{logger})
|
||||
}
|
||||
|
||||
type StructuredLogger struct {
|
||||
Logger *logrus.Logger
|
||||
}
|
||||
|
||||
func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry {
|
||||
entry := &StructuredLoggerEntry{Logger: logrus.NewEntry(l.Logger)}
|
||||
logFields := logrus.Fields{}
|
||||
|
||||
logFields["ts"] = time.Now().UTC().Format(time.RFC1123)
|
||||
|
||||
if reqID := middleware.GetReqID(r.Context()); reqID != "" {
|
||||
logFields["req_id"] = reqID
|
||||
}
|
||||
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
logFields["http_scheme"] = scheme
|
||||
logFields["http_proto"] = r.Proto
|
||||
logFields["http_method"] = r.Method
|
||||
|
||||
logFields["remote_addr"] = r.RemoteAddr
|
||||
logFields["user_agent"] = r.UserAgent()
|
||||
|
||||
logFields["uri"] = fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI)
|
||||
|
||||
entry.Logger = entry.Logger.WithFields(logFields)
|
||||
|
||||
entry.Logger.Infoln("request started")
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
type StructuredLoggerEntry struct {
|
||||
Logger logrus.FieldLogger
|
||||
}
|
||||
|
||||
func (l *StructuredLoggerEntry) Write(status, bytes int, elapsed time.Duration) {
|
||||
l.Logger = l.Logger.WithFields(logrus.Fields{
|
||||
"resp_status": status, "resp_bytes_length": bytes,
|
||||
"resp_elasped_ms": float64(elapsed.Nanoseconds()) / 1000000.0,
|
||||
})
|
||||
|
||||
l.Logger.Infoln("request complete")
|
||||
}
|
||||
|
||||
func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte) {
|
||||
l.Logger = l.Logger.WithFields(logrus.Fields{
|
||||
"stack": string(stack),
|
||||
"panic": fmt.Sprintf("%+v", v),
|
||||
})
|
||||
}
|
||||
|
||||
// Helper methods used by the application to get the request-scoped
|
||||
// logger entry and set additional fields between handlers.
|
||||
//
|
||||
// This is a useful pattern to use to set state on the entry as it
|
||||
// passes through the handler chain, which at any point can be logged
|
||||
// with a call to .Print(), .Info(), etc.
|
||||
|
||||
func GetLogEntry(r *http.Request) logrus.FieldLogger {
|
||||
entry := middleware.GetLogEntry(r).(*StructuredLoggerEntry)
|
||||
return entry.Logger
|
||||
}
|
||||
|
||||
func LogEntrySetField(r *http.Request, key string, value interface{}) {
|
||||
if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok {
|
||||
entry.Logger = entry.Logger.WithField(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
func LogEntrySetFields(r *http.Request, fields map[string]interface{}) {
|
||||
if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok {
|
||||
entry.Logger = entry.Logger.WithFields(fields)
|
||||
}
|
||||
}
|
||||
515
vendor/github.com/go-chi/chi/_examples/rest/main.go
generated
vendored
Normal file
515
vendor/github.com/go-chi/chi/_examples/rest/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,515 @@
|
|||
//
|
||||
// REST
|
||||
// ====
|
||||
// This example demonstrates a HTTP REST web service with some fixture data.
|
||||
// Follow along the example and patterns.
|
||||
//
|
||||
// Also check routes.json for the generated docs from passing the -routes flag
|
||||
//
|
||||
// Boot the server:
|
||||
// ----------------
|
||||
// $ go run main.go
|
||||
//
|
||||
// Client requests:
|
||||
// ----------------
|
||||
// $ curl http://localhost:3333/
|
||||
// root.
|
||||
//
|
||||
// $ curl http://localhost:3333/articles
|
||||
// [{"id":"1","title":"Hi"},{"id":"2","title":"sup"}]
|
||||
//
|
||||
// $ curl http://localhost:3333/articles/1
|
||||
// {"id":"1","title":"Hi"}
|
||||
//
|
||||
// $ curl -X DELETE http://localhost:3333/articles/1
|
||||
// {"id":"1","title":"Hi"}
|
||||
//
|
||||
// $ curl http://localhost:3333/articles/1
|
||||
// "Not Found"
|
||||
//
|
||||
// $ curl -X POST -d '{"id":"will-be-omitted","title":"awesomeness"}' http://localhost:3333/articles
|
||||
// {"id":"97","title":"awesomeness"}
|
||||
//
|
||||
// $ curl http://localhost:3333/articles/97
|
||||
// {"id":"97","title":"awesomeness"}
|
||||
//
|
||||
// $ curl http://localhost:3333/articles
|
||||
// [{"id":"2","title":"sup"},{"id":"97","title":"awesomeness"}]
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/docgen"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
var routes = flag.Bool("routes", false, "Generate router documentation")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
r.Use(middleware.URLFormat)
|
||||
r.Use(render.SetContentType(render.ContentTypeJSON))
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("root."))
|
||||
})
|
||||
|
||||
r.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("pong"))
|
||||
})
|
||||
|
||||
r.Get("/panic", func(w http.ResponseWriter, r *http.Request) {
|
||||
panic("test")
|
||||
})
|
||||
|
||||
// RESTy routes for "articles" resource
|
||||
r.Route("/articles", func(r chi.Router) {
|
||||
r.With(paginate).Get("/", ListArticles)
|
||||
r.Post("/", CreateArticle) // POST /articles
|
||||
r.Get("/search", SearchArticles) // GET /articles/search
|
||||
|
||||
r.Route("/{articleID}", func(r chi.Router) {
|
||||
r.Use(ArticleCtx) // Load the *Article on the request context
|
||||
r.Get("/", GetArticle) // GET /articles/123
|
||||
r.Put("/", UpdateArticle) // PUT /articles/123
|
||||
r.Delete("/", DeleteArticle) // DELETE /articles/123
|
||||
})
|
||||
|
||||
// GET /articles/whats-up
|
||||
r.With(ArticleCtx).Get("/{articleSlug:[a-z-]+}", GetArticle)
|
||||
})
|
||||
|
||||
// Mount the admin sub-router, which btw is the same as:
|
||||
// r.Route("/admin", func(r chi.Router) { admin routes here })
|
||||
r.Mount("/admin", adminRouter())
|
||||
|
||||
// Passing -routes to the program will generate docs for the above
|
||||
// router definition. See the `routes.json` file in this folder for
|
||||
// the output.
|
||||
if *routes {
|
||||
// fmt.Println(docgen.JSONRoutesDoc(r))
|
||||
fmt.Println(docgen.MarkdownRoutesDoc(r, docgen.MarkdownOpts{
|
||||
ProjectPath: "github.com/go-chi/chi",
|
||||
Intro: "Welcome to the chi/_examples/rest generated docs.",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
|
||||
func ListArticles(w http.ResponseWriter, r *http.Request) {
|
||||
if err := render.RenderList(w, r, NewArticleListResponse(articles)); err != nil {
|
||||
render.Render(w, r, ErrRender(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ArticleCtx middleware is used to load an Article object from
|
||||
// the URL parameters passed through as the request. In case
|
||||
// the Article could not be found, we stop here and return a 404.
|
||||
func ArticleCtx(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var article *Article
|
||||
var err error
|
||||
|
||||
if articleID := chi.URLParam(r, "articleID"); articleID != "" {
|
||||
article, err = dbGetArticle(articleID)
|
||||
} else if articleSlug := chi.URLParam(r, "articleSlug"); articleSlug != "" {
|
||||
article, err = dbGetArticleBySlug(articleSlug)
|
||||
} else {
|
||||
render.Render(w, r, ErrNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
render.Render(w, r, ErrNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), "article", article)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
// SearchArticles searches the Articles data for a matching article.
|
||||
// It's just a stub, but you get the idea.
|
||||
func SearchArticles(w http.ResponseWriter, r *http.Request) {
|
||||
render.RenderList(w, r, NewArticleListResponse(articles))
|
||||
}
|
||||
|
||||
// CreateArticle persists the posted Article and returns it
|
||||
// back to the client as an acknowledgement.
|
||||
func CreateArticle(w http.ResponseWriter, r *http.Request) {
|
||||
data := &ArticleRequest{}
|
||||
if err := render.Bind(r, data); err != nil {
|
||||
render.Render(w, r, ErrInvalidRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
article := data.Article
|
||||
dbNewArticle(article)
|
||||
|
||||
render.Status(r, http.StatusCreated)
|
||||
render.Render(w, r, NewArticleResponse(article))
|
||||
}
|
||||
|
||||
// GetArticle returns the specific Article. You'll notice it just
|
||||
// fetches the Article right off the context, as its understood that
|
||||
// if we made it this far, the Article must be on the context. In case
|
||||
// its not due to a bug, then it will panic, and our Recoverer will save us.
|
||||
func GetArticle(w http.ResponseWriter, r *http.Request) {
|
||||
// Assume if we've reach this far, we can access the article
|
||||
// context because this handler is a child of the ArticleCtx
|
||||
// middleware. The worst case, the recoverer middleware will save us.
|
||||
article := r.Context().Value("article").(*Article)
|
||||
|
||||
if err := render.Render(w, r, NewArticleResponse(article)); err != nil {
|
||||
render.Render(w, r, ErrRender(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateArticle updates an existing Article in our persistent store.
|
||||
func UpdateArticle(w http.ResponseWriter, r *http.Request) {
|
||||
article := r.Context().Value("article").(*Article)
|
||||
|
||||
data := &ArticleRequest{Article: article}
|
||||
if err := render.Bind(r, data); err != nil {
|
||||
render.Render(w, r, ErrInvalidRequest(err))
|
||||
return
|
||||
}
|
||||
article = data.Article
|
||||
dbUpdateArticle(article.ID, article)
|
||||
|
||||
render.Render(w, r, NewArticleResponse(article))
|
||||
}
|
||||
|
||||
// DeleteArticle removes an existing Article from our persistent store.
|
||||
func DeleteArticle(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
|
||||
// Assume if we've reach this far, we can access the article
|
||||
// context because this handler is a child of the ArticleCtx
|
||||
// middleware. The worst case, the recoverer middleware will save us.
|
||||
article := r.Context().Value("article").(*Article)
|
||||
|
||||
article, err = dbRemoveArticle(article.ID)
|
||||
if err != nil {
|
||||
render.Render(w, r, ErrInvalidRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
render.Render(w, r, NewArticleResponse(article))
|
||||
}
|
||||
|
||||
// A completely separate router for administrator routes
|
||||
func adminRouter() chi.Router {
|
||||
r := chi.NewRouter()
|
||||
r.Use(AdminOnly)
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("admin: index"))
|
||||
})
|
||||
r.Get("/accounts", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("admin: list accounts.."))
|
||||
})
|
||||
r.Get("/users/{userId}", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(fmt.Sprintf("admin: view user id %v", chi.URLParam(r, "userId"))))
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
// AdminOnly middleware restricts access to just administrators.
|
||||
func AdminOnly(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
isAdmin, ok := r.Context().Value("acl.admin").(bool)
|
||||
if !ok || !isAdmin {
|
||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// paginate is a stub, but very possible to implement middleware logic
|
||||
// to handle the request params for handling a paginated request.
|
||||
func paginate(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// just a stub.. some ideas are to look at URL query params for something like
|
||||
// the page number, or the limit, and send a query cursor down the chain
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// This is entirely optional, but I wanted to demonstrate how you could easily
|
||||
// add your own logic to the render.Respond method.
|
||||
func init() {
|
||||
render.Respond = func(w http.ResponseWriter, r *http.Request, v interface{}) {
|
||||
if err, ok := v.(error); ok {
|
||||
|
||||
// We set a default error status response code if one hasn't been set.
|
||||
if _, ok := r.Context().Value(render.StatusCtxKey).(int); !ok {
|
||||
w.WriteHeader(400)
|
||||
}
|
||||
|
||||
// We log the error
|
||||
fmt.Printf("Logging err: %s\n", err.Error())
|
||||
|
||||
// We change the response to not reveal the actual error message,
|
||||
// instead we can transform the message something more friendly or mapped
|
||||
// to some code / language, etc.
|
||||
render.DefaultResponder(w, r, render.M{"status": "error"})
|
||||
return
|
||||
}
|
||||
|
||||
render.DefaultResponder(w, r, v)
|
||||
}
|
||||
}
|
||||
|
||||
//--
|
||||
// Request and Response payloads for the REST api.
|
||||
//
|
||||
// The payloads embed the data model objects an
|
||||
//
|
||||
// In a real-world project, it would make sense to put these payloads
|
||||
// in another file, or another sub-package.
|
||||
//--
|
||||
|
||||
type UserPayload struct {
|
||||
*User
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
func NewUserPayloadResponse(user *User) *UserPayload {
|
||||
return &UserPayload{User: user}
|
||||
}
|
||||
|
||||
// Bind on UserPayload will run after the unmarshalling is complete, its
|
||||
// a good time to focus some post-processing after a decoding.
|
||||
func (u *UserPayload) Bind(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserPayload) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
u.Role = "collaborator"
|
||||
return nil
|
||||
}
|
||||
|
||||
// ArticleRequest is the request payload for Article data model.
|
||||
//
|
||||
// NOTE: It's good practice to have well defined request and response payloads
|
||||
// so you can manage the specific inputs and outputs for clients, and also gives
|
||||
// you the opportunity to transform data on input or output, for example
|
||||
// on request, we'd like to protect certain fields and on output perhaps
|
||||
// we'd like to include a computed field based on other values that aren't
|
||||
// in the data model. Also, check out this awesome blog post on struct composition:
|
||||
// http://attilaolah.eu/2014/09/10/json-and-struct-composition-in-go/
|
||||
type ArticleRequest struct {
|
||||
*Article
|
||||
|
||||
User *UserPayload `json:"user,omitempty"`
|
||||
|
||||
ProtectedID string `json:"id"` // override 'id' json to have more control
|
||||
}
|
||||
|
||||
func (a *ArticleRequest) Bind(r *http.Request) error {
|
||||
// just a post-process after a decode..
|
||||
a.ProtectedID = "" // unset the protected ID
|
||||
a.Article.Title = strings.ToLower(a.Article.Title) // as an example, we down-case
|
||||
return nil
|
||||
}
|
||||
|
||||
// ArticleResponse is the response payload for the Article data model.
|
||||
// See NOTE above in ArticleRequest as well.
|
||||
//
|
||||
// In the ArticleResponse object, first a Render() is called on itself,
|
||||
// then the next field, and so on, all the way down the tree.
|
||||
// Render is called in top-down order, like a http handler middleware chain.
|
||||
type ArticleResponse struct {
|
||||
*Article
|
||||
|
||||
User *UserPayload `json:"user,omitempty"`
|
||||
|
||||
// We add an additional field to the response here.. such as this
|
||||
// elapsed computed property
|
||||
Elapsed int64 `json:"elapsed"`
|
||||
}
|
||||
|
||||
func NewArticleResponse(article *Article) *ArticleResponse {
|
||||
resp := &ArticleResponse{Article: article}
|
||||
|
||||
if resp.User == nil {
|
||||
if user, _ := dbGetUser(resp.UserID); user != nil {
|
||||
resp.User = NewUserPayloadResponse(user)
|
||||
}
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func (rd *ArticleResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
// Pre-processing before a response is marshalled and sent across the wire
|
||||
rd.Elapsed = 10
|
||||
return nil
|
||||
}
|
||||
|
||||
type ArticleListResponse []*ArticleResponse
|
||||
|
||||
func NewArticleListResponse(articles []*Article) []render.Renderer {
|
||||
list := []render.Renderer{}
|
||||
for _, article := range articles {
|
||||
list = append(list, NewArticleResponse(article))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// NOTE: as a thought, the request and response payloads for an Article could be the
|
||||
// same payload type, perhaps will do an example with it as well.
|
||||
// type ArticlePayload struct {
|
||||
// *Article
|
||||
// }
|
||||
|
||||
//--
|
||||
// Error response payloads & renderers
|
||||
//--
|
||||
|
||||
// ErrResponse renderer type for handling all sorts of errors.
|
||||
//
|
||||
// In the best case scenario, the excellent github.com/pkg/errors package
|
||||
// helps reveal information on the error, setting it on Err, and in the Render()
|
||||
// method, using it to set the application-specific error code in AppCode.
|
||||
type ErrResponse struct {
|
||||
Err error `json:"-"` // low-level runtime error
|
||||
HTTPStatusCode int `json:"-"` // http response status code
|
||||
|
||||
StatusText string `json:"status"` // user-level status message
|
||||
AppCode int64 `json:"code,omitempty"` // application-specific error code
|
||||
ErrorText string `json:"error,omitempty"` // application-level error message, for debugging
|
||||
}
|
||||
|
||||
func (e *ErrResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
render.Status(r, e.HTTPStatusCode)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ErrInvalidRequest(err error) render.Renderer {
|
||||
return &ErrResponse{
|
||||
Err: err,
|
||||
HTTPStatusCode: 400,
|
||||
StatusText: "Invalid request.",
|
||||
ErrorText: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func ErrRender(err error) render.Renderer {
|
||||
return &ErrResponse{
|
||||
Err: err,
|
||||
HTTPStatusCode: 422,
|
||||
StatusText: "Error rendering response.",
|
||||
ErrorText: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
var ErrNotFound = &ErrResponse{HTTPStatusCode: 404, StatusText: "Resource not found."}
|
||||
|
||||
//--
|
||||
// Data model objects and persistence mocks:
|
||||
//--
|
||||
|
||||
// User data model
|
||||
type User struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Article data model. I suggest looking at https://upper.io for an easy
|
||||
// and powerful data persistence adapter.
|
||||
type Article struct {
|
||||
ID string `json:"id"`
|
||||
UserID int64 `json:"user_id"` // the author
|
||||
Title string `json:"title"`
|
||||
Slug string `json:"slug"`
|
||||
}
|
||||
|
||||
// Article fixture data
|
||||
var articles = []*Article{
|
||||
{ID: "1", UserID: 100, Title: "Hi", Slug: "hi"},
|
||||
{ID: "2", UserID: 200, Title: "sup", Slug: "sup"},
|
||||
{ID: "3", UserID: 300, Title: "alo", Slug: "alo"},
|
||||
{ID: "4", UserID: 400, Title: "bonjour", Slug: "bonjour"},
|
||||
{ID: "5", UserID: 500, Title: "whats up", Slug: "whats-up"},
|
||||
}
|
||||
|
||||
// User fixture data
|
||||
var users = []*User{
|
||||
{ID: 100, Name: "Peter"},
|
||||
{ID: 200, Name: "Julia"},
|
||||
}
|
||||
|
||||
func dbNewArticle(article *Article) (string, error) {
|
||||
article.ID = fmt.Sprintf("%d", rand.Intn(100)+10)
|
||||
articles = append(articles, article)
|
||||
return article.ID, nil
|
||||
}
|
||||
|
||||
func dbGetArticle(id string) (*Article, error) {
|
||||
for _, a := range articles {
|
||||
if a.ID == id {
|
||||
return a, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("article not found.")
|
||||
}
|
||||
|
||||
func dbGetArticleBySlug(slug string) (*Article, error) {
|
||||
for _, a := range articles {
|
||||
if a.Slug == slug {
|
||||
return a, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("article not found.")
|
||||
}
|
||||
|
||||
func dbUpdateArticle(id string, article *Article) (*Article, error) {
|
||||
for i, a := range articles {
|
||||
if a.ID == id {
|
||||
articles[i] = article
|
||||
return article, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("article not found.")
|
||||
}
|
||||
|
||||
func dbRemoveArticle(id string) (*Article, error) {
|
||||
for i, a := range articles {
|
||||
if a.ID == id {
|
||||
articles = append((articles)[:i], (articles)[i+1:]...)
|
||||
return a, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("article not found.")
|
||||
}
|
||||
|
||||
func dbGetUser(id int64) (*User, error) {
|
||||
for _, u := range users {
|
||||
if u.ID == id {
|
||||
return u, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("user not found.")
|
||||
}
|
||||
260
vendor/github.com/go-chi/chi/_examples/rest/routes.json
generated
vendored
Normal file
260
vendor/github.com/go-chi/chi/_examples/rest/routes.json
generated
vendored
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
{
|
||||
"router": {
|
||||
"middlewares": [
|
||||
{
|
||||
"pkg": "github.com/go-chi/chi/middleware",
|
||||
"func": "RequestID",
|
||||
"comment": "RequestID is a middleware that injects a request ID into the context of each\nrequest. A request ID is a string of the form \"host.example.com/random-0001\",\nwhere \"random\" is a base62 random string that uniquely identifies this go\nprocess, and where the last number is an atomically incremented request\ncounter.\n",
|
||||
"file": "github.com/go-chi/chi/middleware/request_id.go",
|
||||
"line": 63
|
||||
},
|
||||
{
|
||||
"pkg": "github.com/go-chi/chi/middleware",
|
||||
"func": "Logger",
|
||||
"comment": "Logger is a middleware that logs the start and end of each request, along\nwith some useful data about what was requested, what the response status was,\nand how long it took to return. When standard output is a TTY, Logger will\nprint in color, otherwise it will print in black and white. Logger prints a\nrequest ID if one is provided.\n\nAlternatively, look at https://github.com/pressly/lg and the `lg.RequestLogger`\nmiddleware pkg.\n",
|
||||
"file": "github.com/go-chi/chi/middleware/logger.go",
|
||||
"line": 26
|
||||
},
|
||||
{
|
||||
"pkg": "github.com/go-chi/chi/middleware",
|
||||
"func": "Recoverer",
|
||||
"comment": "Recoverer is a middleware that recovers from panics, logs the panic (and a\nbacktrace), and returns a HTTP 500 (Internal Server Error) status if\npossible. Recoverer prints a request ID if one is provided.\n\nAlternatively, look at https://github.com/pressly/lg middleware pkgs.\n",
|
||||
"file": "github.com/go-chi/chi/middleware/recoverer.go",
|
||||
"line": 18
|
||||
},
|
||||
{
|
||||
"pkg": "github.com/go-chi/chi/middleware",
|
||||
"func": "URLFormat",
|
||||
"comment": "URLFormat is a middleware that parses the url extension from a request path and stores it\non the context as a string under the key `middleware.URLFormatCtxKey`. The middleware will\ntrim the suffix from the routing path and continue routing.\n\nRouters should not include a url parameter for the suffix when using this middleware.\n\nSample usage.. for url paths: `/articles/1`, `/articles/1.json` and `/articles/1.xml`\n\n func routes() http.Handler {\n r := chi.NewRouter()\n r.Use(middleware.URLFormat)\n\n r.Get(\"/articles/{id}\", ListArticles)\n\n return r\n }\n\n func ListArticles(w http.ResponseWriter, r *http.Request) {\n\t urlFormat, _ := r.Context().Value(middleware.URLFormatCtxKey).(string)\n\n\t switch urlFormat {\n\t case \"json\":\n\t \trender.JSON(w, r, articles)\n\t case \"xml:\"\n\t \trender.XML(w, r, articles)\n\t default:\n\t \trender.JSON(w, r, articles)\n\t }\n}\n",
|
||||
"file": "github.com/go-chi/chi/middleware/url_format.go",
|
||||
"line": 45
|
||||
},
|
||||
{
|
||||
"pkg": "github.com/go-chi/render",
|
||||
"func": "SetContentType.func1",
|
||||
"comment": "",
|
||||
"file": "github.com/go-chi/render/content_type.go",
|
||||
"line": 49,
|
||||
"anonymous": true
|
||||
}
|
||||
],
|
||||
"routes": {
|
||||
"/": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.main.func1",
|
||||
"comment": "",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 69,
|
||||
"anonymous": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/admin/*": {
|
||||
"router": {
|
||||
"middlewares": [
|
||||
{
|
||||
"pkg": "",
|
||||
"func": "main.AdminOnly",
|
||||
"comment": "AdminOnly middleware restricts access to just administrators.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 238
|
||||
}
|
||||
],
|
||||
"routes": {
|
||||
"/": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.adminRouter.func1",
|
||||
"comment": "",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 225,
|
||||
"anonymous": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/accounts": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.adminRouter.func2",
|
||||
"comment": "",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 228,
|
||||
"anonymous": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users/{userId}": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.adminRouter.func3",
|
||||
"comment": "",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 231,
|
||||
"anonymous": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/articles/*": {
|
||||
"router": {
|
||||
"middlewares": [],
|
||||
"routes": {
|
||||
"/": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [
|
||||
{
|
||||
"pkg": "",
|
||||
"func": "main.paginate",
|
||||
"comment": "paginate is a stub, but very possible to implement middleware logic\nto handle the request params for handling a paginated request.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 251
|
||||
}
|
||||
],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.ListArticles",
|
||||
"comment": "",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 117
|
||||
},
|
||||
"POST": {
|
||||
"middlewares": [],
|
||||
"method": "POST",
|
||||
"pkg": "",
|
||||
"func": "main.CreateArticle",
|
||||
"comment": "CreateArticle persists the posted Article and returns it\nback to the client as an acknowledgement.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 158
|
||||
}
|
||||
}
|
||||
},
|
||||
"/search": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.SearchArticles",
|
||||
"comment": "SearchArticles searches the Articles data for a matching article.\nIt's just a stub, but you get the idea.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 152
|
||||
}
|
||||
}
|
||||
},
|
||||
"/{articleID}/*": {
|
||||
"router": {
|
||||
"middlewares": [
|
||||
{
|
||||
"pkg": "",
|
||||
"func": "main.ArticleCtx",
|
||||
"comment": "ArticleCtx middleware is used to load an Article object from\nthe URL parameters passed through as the request. In case\nthe Article could not be found, we stop here and return a 404.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 127
|
||||
}
|
||||
],
|
||||
"routes": {
|
||||
"/": {
|
||||
"handlers": {
|
||||
"DELETE": {
|
||||
"middlewares": [],
|
||||
"method": "DELETE",
|
||||
"pkg": "",
|
||||
"func": "main.DeleteArticle",
|
||||
"comment": "DeleteArticle removes an existing Article from our persistent store.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 204
|
||||
},
|
||||
"GET": {
|
||||
"middlewares": [],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.GetArticle",
|
||||
"comment": "GetArticle returns the specific Article. You'll notice it just\nfetches the Article right off the context, as its understood that\nif we made it this far, the Article must be on the context. In case\nits not due to a bug, then it will panic, and our Recoverer will save us.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 176
|
||||
},
|
||||
"PUT": {
|
||||
"middlewares": [],
|
||||
"method": "PUT",
|
||||
"pkg": "",
|
||||
"func": "main.UpdateArticle",
|
||||
"comment": "UpdateArticle updates an existing Article in our persistent store.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 189
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/{articleSlug:[a-z-]+}": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [
|
||||
{
|
||||
"pkg": "",
|
||||
"func": "main.ArticleCtx",
|
||||
"comment": "ArticleCtx middleware is used to load an Article object from\nthe URL parameters passed through as the request. In case\nthe Article could not be found, we stop here and return a 404.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 127
|
||||
}
|
||||
],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.GetArticle",
|
||||
"comment": "GetArticle returns the specific Article. You'll notice it just\nfetches the Article right off the context, as its understood that\nif we made it this far, the Article must be on the context. In case\nits not due to a bug, then it will panic, and our Recoverer will save us.\n",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 176
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/panic": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.main.func3",
|
||||
"comment": "",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 77,
|
||||
"anonymous": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ping": {
|
||||
"handlers": {
|
||||
"GET": {
|
||||
"middlewares": [],
|
||||
"method": "GET",
|
||||
"pkg": "",
|
||||
"func": "main.main.func2",
|
||||
"comment": "",
|
||||
"file": "github.com/go-chi/chi/_examples/rest/main.go",
|
||||
"line": 73,
|
||||
"anonymous": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
159
vendor/github.com/go-chi/chi/_examples/rest/routes.md
generated
vendored
Normal file
159
vendor/github.com/go-chi/chi/_examples/rest/routes.md
generated
vendored
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
# github.com/go-chi/chi
|
||||
|
||||
Welcome to the chi/_examples/rest generated docs.
|
||||
|
||||
## Routes
|
||||
|
||||
<details>
|
||||
<summary>`/`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/**
|
||||
- _GET_
|
||||
- [main.main.func1](/_examples/rest/main.go#L69)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/admin/*`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/admin/***
|
||||
- [main.AdminOnly](/_examples/rest/main.go#L238)
|
||||
- **/**
|
||||
- _GET_
|
||||
- [main.adminRouter.func1](/_examples/rest/main.go#L225)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/admin/*/accounts`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/admin/***
|
||||
- [main.AdminOnly](/_examples/rest/main.go#L238)
|
||||
- **/accounts**
|
||||
- _GET_
|
||||
- [main.adminRouter.func2](/_examples/rest/main.go#L228)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/admin/*/users/{userId}`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/admin/***
|
||||
- [main.AdminOnly](/_examples/rest/main.go#L238)
|
||||
- **/users/{userId}**
|
||||
- _GET_
|
||||
- [main.adminRouter.func3](/_examples/rest/main.go#L231)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/articles/*`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/articles/***
|
||||
- **/**
|
||||
- _GET_
|
||||
- [main.paginate](/_examples/rest/main.go#L251)
|
||||
- [main.ListArticles](/_examples/rest/main.go#L117)
|
||||
- _POST_
|
||||
- [main.CreateArticle](/_examples/rest/main.go#L158)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/articles/*/search`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/articles/***
|
||||
- **/search**
|
||||
- _GET_
|
||||
- [main.SearchArticles](/_examples/rest/main.go#L152)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/articles/*/{articleID}/*`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/articles/***
|
||||
- **/{articleID}/***
|
||||
- [main.ArticleCtx](/_examples/rest/main.go#L127)
|
||||
- **/**
|
||||
- _DELETE_
|
||||
- [main.DeleteArticle](/_examples/rest/main.go#L204)
|
||||
- _GET_
|
||||
- [main.GetArticle](/_examples/rest/main.go#L176)
|
||||
- _PUT_
|
||||
- [main.UpdateArticle](/_examples/rest/main.go#L189)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/articles/*/{articleSlug:[a-z-]+}`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/articles/***
|
||||
- **/{articleSlug:[a-z-]+}**
|
||||
- _GET_
|
||||
- [main.ArticleCtx](/_examples/rest/main.go#L127)
|
||||
- [main.GetArticle](/_examples/rest/main.go#L176)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/panic`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/panic**
|
||||
- _GET_
|
||||
- [main.main.func3](/_examples/rest/main.go#L77)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>`/ping`</summary>
|
||||
|
||||
- [RequestID](/middleware/request_id.go#L63)
|
||||
- [Logger](/middleware/logger.go#L26)
|
||||
- [Recoverer](/middleware/recoverer.go#L18)
|
||||
- [URLFormat](/middleware/url_format.go#L45)
|
||||
- [SetContentType.func1](https://github.com/go-chi/render/content_type.go#L49)
|
||||
- **/ping**
|
||||
- _GET_
|
||||
- [main.main.func2](/_examples/rest/main.go#L73)
|
||||
|
||||
</details>
|
||||
|
||||
Total # of routes: 10
|
||||
|
||||
34
vendor/github.com/go-chi/chi/_examples/todos-resource/main.go
generated
vendored
Normal file
34
vendor/github.com/go-chi/chi/_examples/todos-resource/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// Todos Resource
|
||||
// ==============
|
||||
// This example demonstrates a project structure that defines a subrouter and its
|
||||
// handlers on a struct, and mounting them as subrouters to a parent router.
|
||||
// See also _examples/rest for an in-depth example of a REST service, and apply
|
||||
// those same patterns to this structure.
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.RealIP)
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("."))
|
||||
})
|
||||
|
||||
r.Mount("/users", usersResource{}.Routes())
|
||||
r.Mount("/todos", todosResource{}.Routes())
|
||||
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
53
vendor/github.com/go-chi/chi/_examples/todos-resource/todos.go
generated
vendored
Normal file
53
vendor/github.com/go-chi/chi/_examples/todos-resource/todos.go
generated
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
type todosResource struct{}
|
||||
|
||||
// Routes creates a REST router for the todos resource
|
||||
func (rs todosResource) Routes() chi.Router {
|
||||
r := chi.NewRouter()
|
||||
// r.Use() // some middleware..
|
||||
|
||||
r.Get("/", rs.List) // GET /todos - read a list of todos
|
||||
r.Post("/", rs.Create) // POST /todos - create a new todo and persist it
|
||||
r.Put("/", rs.Delete)
|
||||
|
||||
r.Route("/{id}", func(r chi.Router) {
|
||||
// r.Use(rs.TodoCtx) // lets have a todos map, and lets actually load/manipulate
|
||||
r.Get("/", rs.Get) // GET /todos/{id} - read a single todo by :id
|
||||
r.Put("/", rs.Update) // PUT /todos/{id} - update a single todo by :id
|
||||
r.Delete("/", rs.Delete) // DELETE /todos/{id} - delete a single todo by :id
|
||||
r.Get("/sync", rs.Sync)
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (rs todosResource) List(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("todos list of stuff.."))
|
||||
}
|
||||
|
||||
func (rs todosResource) Create(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("todos create"))
|
||||
}
|
||||
|
||||
func (rs todosResource) Get(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("todo get"))
|
||||
}
|
||||
|
||||
func (rs todosResource) Update(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("todo update"))
|
||||
}
|
||||
|
||||
func (rs todosResource) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("todo delete"))
|
||||
}
|
||||
|
||||
func (rs todosResource) Sync(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("todo sync"))
|
||||
}
|
||||
48
vendor/github.com/go-chi/chi/_examples/todos-resource/users.go
generated
vendored
Normal file
48
vendor/github.com/go-chi/chi/_examples/todos-resource/users.go
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
type usersResource struct{}
|
||||
|
||||
// Routes creates a REST router for the todos resource
|
||||
func (rs usersResource) Routes() chi.Router {
|
||||
r := chi.NewRouter()
|
||||
// r.Use() // some middleware..
|
||||
|
||||
r.Get("/", rs.List) // GET /todos - read a list of todos
|
||||
r.Post("/", rs.Create) // POST /todos - create a new todo and persist it
|
||||
r.Put("/", rs.Delete)
|
||||
|
||||
r.Route("/{id}", func(r chi.Router) {
|
||||
// r.Use(rs.TodoCtx) // lets have a todos map, and lets actually load/manipulate
|
||||
r.Get("/", rs.Get) // GET /todos/{id} - read a single todo by :id
|
||||
r.Put("/", rs.Update) // PUT /todos/{id} - update a single todo by :id
|
||||
r.Delete("/", rs.Delete) // DELETE /todos/{id} - delete a single todo by :id
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (rs usersResource) List(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("aaa list of stuff.."))
|
||||
}
|
||||
|
||||
func (rs usersResource) Create(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("aaa create"))
|
||||
}
|
||||
|
||||
func (rs usersResource) Get(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("aaa get"))
|
||||
}
|
||||
|
||||
func (rs usersResource) Update(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("aaa update"))
|
||||
}
|
||||
|
||||
func (rs usersResource) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("aaa delete"))
|
||||
}
|
||||
9
vendor/github.com/go-chi/chi/_examples/versions/data/article.go
generated
vendored
Normal file
9
vendor/github.com/go-chi/chi/_examples/versions/data/article.go
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package data
|
||||
|
||||
// Article is runtime object, that's not meant to be sent via REST.
|
||||
type Article struct {
|
||||
ID int `db:"id" json:"id" xml:"id"`
|
||||
Title string `db:"title" json:"title" xml:"title"`
|
||||
Data []string `db:"data,stringarray" json:"data" xml:"data"`
|
||||
CustomDataForAuthUsers string `db:"custom_data" json:"-" xml:"-"`
|
||||
}
|
||||
28
vendor/github.com/go-chi/chi/_examples/versions/data/errors.go
generated
vendored
Normal file
28
vendor/github.com/go-chi/chi/_examples/versions/data/errors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnauthorized = errors.New("Unauthorized")
|
||||
ErrForbidden = errors.New("Forbidden")
|
||||
ErrNotFound = errors.New("Resource not found")
|
||||
)
|
||||
|
||||
func PresentError(r *http.Request, err error) (*http.Request, interface{}) {
|
||||
switch err {
|
||||
case ErrUnauthorized:
|
||||
render.Status(r, 401)
|
||||
case ErrForbidden:
|
||||
render.Status(r, 403)
|
||||
case ErrNotFound:
|
||||
render.Status(r, 404)
|
||||
default:
|
||||
render.Status(r, 500)
|
||||
}
|
||||
return r, map[string]string{"error": err.Error()}
|
||||
}
|
||||
158
vendor/github.com/go-chi/chi/_examples/versions/main.go
generated
vendored
Normal file
158
vendor/github.com/go-chi/chi/_examples/versions/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
//
|
||||
// Versions
|
||||
// ========
|
||||
// This example demonstrates the use of the render subpackage, with
|
||||
// a quick concept for how to support multiple api versions.
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/_examples/versions/data"
|
||||
"github.com/go-chi/chi/_examples/versions/presenter/v1"
|
||||
"github.com/go-chi/chi/_examples/versions/presenter/v2"
|
||||
"github.com/go-chi/chi/_examples/versions/presenter/v3"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
// API version 3.
|
||||
r.Route("/v3", func(r chi.Router) {
|
||||
r.Use(apiVersionCtx("v3"))
|
||||
r.Mount("/articles", articleRouter())
|
||||
})
|
||||
|
||||
// API version 2.
|
||||
r.Route("/v2", func(r chi.Router) {
|
||||
r.Use(apiVersionCtx("v2"))
|
||||
r.Mount("/articles", articleRouter())
|
||||
})
|
||||
|
||||
// API version 1.
|
||||
r.Route("/v1", func(r chi.Router) {
|
||||
r.Use(randomErrorMiddleware) // Simulate random error, ie. version 1 is buggy.
|
||||
r.Use(apiVersionCtx("v1"))
|
||||
r.Mount("/articles", articleRouter())
|
||||
})
|
||||
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
|
||||
func apiVersionCtx(version string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
r = r.WithContext(context.WithValue(r.Context(), "api.version", version))
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func articleRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", listArticles)
|
||||
r.Route("/{articleID}", func(r chi.Router) {
|
||||
r.Get("/", getArticle)
|
||||
// r.Put("/", updateArticle)
|
||||
// r.Delete("/", deleteArticle)
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
func listArticles(w http.ResponseWriter, r *http.Request) {
|
||||
articles := make(chan render.Renderer, 5)
|
||||
|
||||
// Load data asynchronously into the channel (simulate slow storage):
|
||||
go func() {
|
||||
for i := 1; i <= 10; i++ {
|
||||
article := &data.Article{
|
||||
ID: i,
|
||||
Title: fmt.Sprintf("Article #%v", i),
|
||||
Data: []string{"one", "two", "three", "four"},
|
||||
CustomDataForAuthUsers: "secret data for auth'd users only",
|
||||
}
|
||||
|
||||
apiVersion := r.Context().Value("api.version").(string)
|
||||
switch apiVersion {
|
||||
case "v1":
|
||||
articles <- v1.NewArticleResponse(article)
|
||||
case "v2":
|
||||
articles <- v2.NewArticleResponse(article)
|
||||
default:
|
||||
articles <- v3.NewArticleResponse(article)
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
close(articles)
|
||||
}()
|
||||
|
||||
// Start streaming data from the channel.
|
||||
render.Respond(w, r, articles)
|
||||
}
|
||||
|
||||
func getArticle(w http.ResponseWriter, r *http.Request) {
|
||||
// Load article.
|
||||
if chi.URLParam(r, "articleID") != "1" {
|
||||
render.Respond(w, r, data.ErrNotFound)
|
||||
return
|
||||
}
|
||||
article := &data.Article{
|
||||
ID: 1,
|
||||
Title: "Article #1",
|
||||
Data: []string{"one", "two", "three", "four"},
|
||||
CustomDataForAuthUsers: "secret data for auth'd users only",
|
||||
}
|
||||
|
||||
// Simulate some context values:
|
||||
// 1. ?auth=true simluates authenticated session/user.
|
||||
// 2. ?error=true simulates random error.
|
||||
if r.URL.Query().Get("auth") != "" {
|
||||
r = r.WithContext(context.WithValue(r.Context(), "auth", true))
|
||||
}
|
||||
if r.URL.Query().Get("error") != "" {
|
||||
render.Respond(w, r, errors.New("error"))
|
||||
return
|
||||
}
|
||||
|
||||
var payload render.Renderer
|
||||
|
||||
apiVersion := r.Context().Value("api.version").(string)
|
||||
switch apiVersion {
|
||||
case "v1":
|
||||
payload = v1.NewArticleResponse(article)
|
||||
case "v2":
|
||||
payload = v2.NewArticleResponse(article)
|
||||
default:
|
||||
payload = v3.NewArticleResponse(article)
|
||||
}
|
||||
|
||||
render.Render(w, r, payload)
|
||||
}
|
||||
|
||||
func randomErrorMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
rand.Seed(time.Now().Unix())
|
||||
|
||||
// One in three chance of random error.
|
||||
if rand.Int31n(3) == 0 {
|
||||
errors := []error{data.ErrUnauthorized, data.ErrForbidden, data.ErrNotFound}
|
||||
render.Respond(w, r, errors[rand.Intn(len(errors))])
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
22
vendor/github.com/go-chi/chi/_examples/versions/presenter/v1/article.go
generated
vendored
Normal file
22
vendor/github.com/go-chi/chi/_examples/versions/presenter/v1/article.go
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/_examples/versions/data"
|
||||
)
|
||||
|
||||
// Article presented in API version 1.
|
||||
type Article struct {
|
||||
*data.Article
|
||||
|
||||
Data map[string]bool `json:"data" xml:"data"`
|
||||
}
|
||||
|
||||
func (a *Article) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewArticleResponse(article *data.Article) *Article {
|
||||
return &Article{Article: article}
|
||||
}
|
||||
30
vendor/github.com/go-chi/chi/_examples/versions/presenter/v2/article.go
generated
vendored
Normal file
30
vendor/github.com/go-chi/chi/_examples/versions/presenter/v2/article.go
generated
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/_examples/versions/data"
|
||||
)
|
||||
|
||||
// Article presented in API version 2.
|
||||
type Article struct {
|
||||
// *v3.Article `json:",inline" xml:",inline"`
|
||||
|
||||
*data.Article
|
||||
|
||||
// Additional fields.
|
||||
SelfURL string `json:"self_url" xml:"self_url"`
|
||||
|
||||
// Omitted fields.
|
||||
URL interface{} `json:"url,omitempty" xml:"url,omitempty"`
|
||||
}
|
||||
|
||||
func (a *Article) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
a.SelfURL = fmt.Sprintf("http://localhost:3333/v2?id=%v", a.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewArticleResponse(article *data.Article) *Article {
|
||||
return &Article{Article: article}
|
||||
}
|
||||
39
vendor/github.com/go-chi/chi/_examples/versions/presenter/v3/article.go
generated
vendored
Normal file
39
vendor/github.com/go-chi/chi/_examples/versions/presenter/v3/article.go
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package v3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/_examples/versions/data"
|
||||
)
|
||||
|
||||
// Article presented in API version 2.
|
||||
type Article struct {
|
||||
*data.Article `json:",inline" xml:",inline"`
|
||||
|
||||
// Additional fields.
|
||||
URL string `json:"url" xml:"url"`
|
||||
ViewsCount int64 `json:"views_count" xml:"views_count"`
|
||||
APIVersion string `json:"api_version" xml:"api_version"`
|
||||
|
||||
// Omitted fields.
|
||||
// Show custom_data explicitly for auth'd users only.
|
||||
CustomDataForAuthUsers interface{} `json:"custom_data,omitempty" xml:"custom_data,omitempty"`
|
||||
}
|
||||
|
||||
func (a *Article) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
a.ViewsCount = rand.Int63n(100000)
|
||||
a.URL = fmt.Sprintf("http://localhost:3333/v3/?id=%v", a.ID)
|
||||
|
||||
// Only show to auth'd user.
|
||||
if _, ok := r.Context().Value("auth").(bool); ok {
|
||||
a.CustomDataForAuthUsers = a.Article.CustomDataForAuthUsers
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewArticleResponse(article *data.Article) *Article {
|
||||
return &Article{Article: article}
|
||||
}
|
||||
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
|
||||
}
|
||||
108
vendor/github.com/go-chi/chi/chi.go
generated
vendored
Normal file
108
vendor/github.com/go-chi/chi/chi.go
generated
vendored
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
//
|
||||
// Package chi is a small, idiomatic and composable router for building HTTP services.
|
||||
//
|
||||
// chi requires Go 1.7 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.
|
||||
//
|
||||
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 of 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
|
||||
161
vendor/github.com/go-chi/chi/context.go
generated
vendored
Normal file
161
vendor/github.com/go-chi/chi/context.go
generated
vendored
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
package chi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// NewRouteContext returns a new routing Context object.
|
||||
func NewRouteContext() *Context {
|
||||
return &Context{}
|
||||
}
|
||||
|
||||
// Reset a routing context to its initial state.
|
||||
func (x *Context) Reset() {
|
||||
x.Routes = nil
|
||||
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 strings.Replace(routePattern, "/*/", "/", -1)
|
||||
}
|
||||
|
||||
// RouteContext returns chi's routing Context object from a
|
||||
// http.Request Context.
|
||||
func RouteContext(ctx context.Context) *Context {
|
||||
return ctx.Value(RouteCtxKey).(*Context)
|
||||
}
|
||||
|
||||
// URLParam returns the url parameter from a http.Request object.
|
||||
func URLParam(r *http.Request, key string) string {
|
||||
if rctx := RouteContext(r.Context()); rctx != nil {
|
||||
return rctx.URLParam(key)
|
||||
}
|
||||
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 ""
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
42
vendor/github.com/go-chi/chi/middleware/closenotify17.go
generated
vendored
Normal file
42
vendor/github.com/go-chi/chi/middleware/closenotify17.go
generated
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// +build go1.7,!go1.8
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// CloseNotify is a middleware that cancels ctx when the underlying
|
||||
// connection has gone away. It can be used to cancel long operations
|
||||
// on the server when the client disconnects before the response is ready.
|
||||
//
|
||||
// Note: this behaviour is standard in Go 1.8+, so the middleware does nothing
|
||||
// on 1.8+ and exists just for backwards compatibility.
|
||||
func CloseNotify(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
cn, ok := w.(http.CloseNotifier)
|
||||
if !ok {
|
||||
panic("chi/middleware: CloseNotify expects http.ResponseWriter to implement http.CloseNotifier interface")
|
||||
}
|
||||
closeNotifyCh := cn.CloseNotify()
|
||||
|
||||
ctx, cancel := context.WithCancel(r.Context())
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-closeNotifyCh:
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
17
vendor/github.com/go-chi/chi/middleware/closenotify18.go
generated
vendored
Normal file
17
vendor/github.com/go-chi/chi/middleware/closenotify18.go
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// +build go1.8
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// CloseNotify is a middleware that cancels ctx when the underlying
|
||||
// connection has gone away. It can be used to cancel long operations
|
||||
// on the server when the client disconnects before the response is ready.
|
||||
//
|
||||
// Note: this behaviour is standard in Go 1.8+, so the middleware does nothing
|
||||
// on 1.8+ and exists just for backwards compatibility.
|
||||
func CloseNotify(next http.Handler) http.Handler {
|
||||
return next
|
||||
}
|
||||
212
vendor/github.com/go-chi/chi/middleware/compress.go
generated
vendored
Normal file
212
vendor/github.com/go-chi/chi/middleware/compress.go
generated
vendored
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type encoding int
|
||||
|
||||
const (
|
||||
encodingNone encoding = iota
|
||||
encodingGzip
|
||||
encodingDeflate
|
||||
)
|
||||
|
||||
var defaultContentTypes = map[string]struct{}{
|
||||
"text/html": struct{}{},
|
||||
"text/css": struct{}{},
|
||||
"text/plain": struct{}{},
|
||||
"text/javascript": struct{}{},
|
||||
"application/javascript": struct{}{},
|
||||
"application/x-javascript": struct{}{},
|
||||
"application/json": struct{}{},
|
||||
"application/atom+xml": struct{}{},
|
||||
"application/rss+xml": struct{}{},
|
||||
}
|
||||
|
||||
// DefaultCompress is a middleware that compresses response
|
||||
// body of predefined content types to a data format based
|
||||
// on Accept-Encoding request header. It uses a default
|
||||
// compression level.
|
||||
func DefaultCompress(next http.Handler) http.Handler {
|
||||
return Compress(flate.DefaultCompression)(next)
|
||||
}
|
||||
|
||||
// Compress is a middleware that compresses response
|
||||
// body of a given content types to a data format based
|
||||
// on Accept-Encoding request header. It uses a given
|
||||
// compression level.
|
||||
func Compress(level int, types ...string) func(next http.Handler) http.Handler {
|
||||
contentTypes := defaultContentTypes
|
||||
if len(types) > 0 {
|
||||
contentTypes = make(map[string]struct{}, len(types))
|
||||
for _, t := range types {
|
||||
contentTypes[t] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
mcw := &maybeCompressResponseWriter{
|
||||
ResponseWriter: w,
|
||||
w: w,
|
||||
contentTypes: contentTypes,
|
||||
encoding: selectEncoding(r.Header),
|
||||
level: level,
|
||||
}
|
||||
defer mcw.Close()
|
||||
|
||||
next.ServeHTTP(mcw, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
func selectEncoding(h http.Header) encoding {
|
||||
enc := h.Get("Accept-Encoding")
|
||||
|
||||
switch {
|
||||
// TODO:
|
||||
// case "br": // Brotli, experimental. Firefox 2016, to-be-in Chromium.
|
||||
// case "lzma": // Opera.
|
||||
// case "sdch": // Chrome, Android. Gzip output + dictionary header.
|
||||
|
||||
case strings.Contains(enc, "gzip"):
|
||||
// TODO: Exception for old MSIE browsers that can't handle non-HTML?
|
||||
// https://zoompf.com/blog/2012/02/lose-the-wait-http-compression
|
||||
return encodingGzip
|
||||
|
||||
case strings.Contains(enc, "deflate"):
|
||||
// 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.
|
||||
return encodingDeflate
|
||||
|
||||
// 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 encodingNone
|
||||
}
|
||||
|
||||
type maybeCompressResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
w io.Writer
|
||||
encoding encoding
|
||||
contentTypes map[string]struct{}
|
||||
level int
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
func (w *maybeCompressResponseWriter) WriteHeader(code int) {
|
||||
if w.wroteHeader {
|
||||
return
|
||||
}
|
||||
w.wroteHeader = true
|
||||
defer w.ResponseWriter.WriteHeader(code)
|
||||
|
||||
// Already compressed data?
|
||||
if w.ResponseWriter.Header().Get("Content-Encoding") != "" {
|
||||
return
|
||||
}
|
||||
// The content-length after compression is unknown
|
||||
w.ResponseWriter.Header().Del("Content-Length")
|
||||
|
||||
// Parse the first part of the Content-Type response header.
|
||||
contentType := ""
|
||||
parts := strings.Split(w.ResponseWriter.Header().Get("Content-Type"), ";")
|
||||
if len(parts) > 0 {
|
||||
contentType = parts[0]
|
||||
}
|
||||
|
||||
// Is the content type compressable?
|
||||
if _, ok := w.contentTypes[contentType]; !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Select the compress writer.
|
||||
switch w.encoding {
|
||||
case encodingGzip:
|
||||
gw, err := gzip.NewWriterLevel(w.ResponseWriter, w.level)
|
||||
if err != nil {
|
||||
w.w = w.ResponseWriter
|
||||
return
|
||||
}
|
||||
w.w = gw
|
||||
w.ResponseWriter.Header().Set("Content-Encoding", "gzip")
|
||||
|
||||
case encodingDeflate:
|
||||
dw, err := flate.NewWriter(w.ResponseWriter, w.level)
|
||||
if err != nil {
|
||||
w.w = w.ResponseWriter
|
||||
return
|
||||
}
|
||||
w.w = dw
|
||||
w.ResponseWriter.Header().Set("Content-Encoding", "deflate")
|
||||
}
|
||||
}
|
||||
|
||||
func (w *maybeCompressResponseWriter) Write(p []byte) (int, error) {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
return w.w.Write(p)
|
||||
}
|
||||
|
||||
func (w *maybeCompressResponseWriter) Flush() {
|
||||
if f, ok := w.w.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *maybeCompressResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hj, ok := w.w.(http.Hijacker); ok {
|
||||
return hj.Hijack()
|
||||
}
|
||||
return nil, nil, errors.New("chi/middleware: http.Hijacker is unavailable on the writer")
|
||||
}
|
||||
|
||||
func (w *maybeCompressResponseWriter) CloseNotify() <-chan bool {
|
||||
if cn, ok := w.w.(http.CloseNotifier); ok {
|
||||
return cn.CloseNotify()
|
||||
}
|
||||
|
||||
// If the underlying writer does not implement http.CloseNotifier, return
|
||||
// a channel that never receives a value. The semantics here is that the
|
||||
// client never disconnnects before the request is processed by the
|
||||
// http.Handler, which is close enough to the default behavior (when
|
||||
// CloseNotify() is not even called).
|
||||
return make(chan bool, 1)
|
||||
}
|
||||
|
||||
func (w *maybeCompressResponseWriter) Close() error {
|
||||
if c, ok := w.w.(io.WriteCloser); ok {
|
||||
return c.Close()
|
||||
}
|
||||
return errors.New("chi/middleware: io.WriteCloser is unavailable on the writer")
|
||||
}
|
||||
15
vendor/github.com/go-chi/chi/middleware/compress18.go
generated
vendored
Normal file
15
vendor/github.com/go-chi/chi/middleware/compress18.go
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// +build go1.8
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (w *maybeCompressResponseWriter) Push(target string, opts *http.PushOptions) error {
|
||||
if ps, ok := w.w.(http.Pusher); ok {
|
||||
return ps.Push(target, opts)
|
||||
}
|
||||
return errors.New("chi/middleware: http.Pusher is unavailable on the writer")
|
||||
}
|
||||
38
vendor/github.com/go-chi/chi/middleware/get_head.go
generated
vendored
Normal file
38
vendor/github.com/go-chi/chi/middleware/get_head.go
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
66
vendor/github.com/go-chi/chi/middleware/get_head_test.go
generated
vendored
Normal file
66
vendor/github.com/go-chi/chi/middleware/get_head_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
func TestGetHead(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
r.Use(GetHead)
|
||||
r.Get("/hi", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Test", "yes")
|
||||
w.Write([]byte("bye"))
|
||||
})
|
||||
r.Route("/articles", func(r chi.Router) {
|
||||
r.Get("/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
w.Header().Set("X-Article", id)
|
||||
w.Write([]byte("article:" + id))
|
||||
})
|
||||
})
|
||||
r.Route("/users", func(r chi.Router) {
|
||||
r.Head("/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-User", "-")
|
||||
w.Write([]byte("user"))
|
||||
})
|
||||
r.Get("/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
w.Header().Set("X-User", id)
|
||||
w.Write([]byte("user:" + id))
|
||||
})
|
||||
})
|
||||
|
||||
ts := httptest.NewServer(r)
|
||||
defer ts.Close()
|
||||
|
||||
if _, body := testRequest(t, ts, "GET", "/hi", nil); body != "bye" {
|
||||
t.Fatalf(body)
|
||||
}
|
||||
if req, body := testRequest(t, ts, "HEAD", "/hi", nil); body != "" || req.Header.Get("X-Test") != "yes" {
|
||||
t.Fatalf(body)
|
||||
}
|
||||
if _, body := testRequest(t, ts, "GET", "/", nil); body != "404 page not found\n" {
|
||||
t.Fatalf(body)
|
||||
}
|
||||
if req, body := testRequest(t, ts, "HEAD", "/", nil); body != "" || req.StatusCode != 404 {
|
||||
t.Fatalf(body)
|
||||
}
|
||||
|
||||
if _, body := testRequest(t, ts, "GET", "/articles/5", nil); body != "article:5" {
|
||||
t.Fatalf(body)
|
||||
}
|
||||
if req, body := testRequest(t, ts, "HEAD", "/articles/5", nil); body != "" || req.Header.Get("X-Article") != "5" {
|
||||
t.Fatalf("expecting X-Article header '5' but got '%s'", req.Header.Get("X-Article"))
|
||||
}
|
||||
|
||||
if _, body := testRequest(t, ts, "GET", "/users/1", nil); body != "user:1" {
|
||||
t.Fatalf(body)
|
||||
}
|
||||
if req, body := testRequest(t, ts, "HEAD", "/users/1", nil); body != "" || req.Header.Get("X-User") != "-" {
|
||||
t.Fatalf("expecting X-User header '-' but got '%s'", req.Header.Get("X-User"))
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
154
vendor/github.com/go-chi/chi/middleware/logger.go
generated
vendored
Normal file
154
vendor/github.com/go-chi/chi/middleware/logger.go
generated
vendored
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
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)})
|
||||
)
|
||||
|
||||
// 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/pressly/lg and the `lg.RequestLogger`
|
||||
// middleware pkg.
|
||||
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(), time.Since(t1))
|
||||
}()
|
||||
|
||||
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, elapsed time.Duration)
|
||||
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
|
||||
}
|
||||
|
||||
// NewLogEntry creates a new LogEntry for the request.
|
||||
func (l *DefaultLogFormatter) NewLogEntry(r *http.Request) LogEntry {
|
||||
entry := &defaultLogEntry{
|
||||
DefaultLogFormatter: l,
|
||||
request: r,
|
||||
buf: &bytes.Buffer{},
|
||||
}
|
||||
|
||||
reqID := GetReqID(r.Context())
|
||||
if reqID != "" {
|
||||
cW(entry.buf, nYellow, "[%s] ", reqID)
|
||||
}
|
||||
cW(entry.buf, nCyan, "\"")
|
||||
cW(entry.buf, bMagenta, "%s ", r.Method)
|
||||
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
cW(entry.buf, 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
|
||||
}
|
||||
|
||||
func (l *defaultLogEntry) Write(status, bytes int, elapsed time.Duration) {
|
||||
switch {
|
||||
case status < 200:
|
||||
cW(l.buf, bBlue, "%03d", status)
|
||||
case status < 300:
|
||||
cW(l.buf, bGreen, "%03d", status)
|
||||
case status < 400:
|
||||
cW(l.buf, bCyan, "%03d", status)
|
||||
case status < 500:
|
||||
cW(l.buf, bYellow, "%03d", status)
|
||||
default:
|
||||
cW(l.buf, bRed, "%03d", status)
|
||||
}
|
||||
|
||||
cW(l.buf, bBlue, " %dB", bytes)
|
||||
|
||||
l.buf.WriteString(" in ")
|
||||
if elapsed < 500*time.Millisecond {
|
||||
cW(l.buf, nGreen, "%s", elapsed)
|
||||
} else if elapsed < 5*time.Second {
|
||||
cW(l.buf, nYellow, "%s", elapsed)
|
||||
} else {
|
||||
cW(l.buf, nRed, "%s", elapsed)
|
||||
}
|
||||
|
||||
l.Logger.Print(l.buf.String())
|
||||
}
|
||||
|
||||
func (l *defaultLogEntry) Panic(v interface{}, stack []byte) {
|
||||
panicEntry := l.NewLogEntry(l.request).(*defaultLogEntry)
|
||||
cW(panicEntry.buf, bRed, "panic: %+v", v)
|
||||
l.Logger.Print(panicEntry.buf.String())
|
||||
l.Logger.Print(string(stack))
|
||||
}
|
||||
12
vendor/github.com/go-chi/chi/middleware/middleware.go
generated
vendored
Normal file
12
vendor/github.com/go-chi/chi/middleware/middleware.go
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package middleware
|
||||
|
||||
// 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
|
||||
}
|
||||
77
vendor/github.com/go-chi/chi/middleware/middleware18_test.go
generated
vendored
Normal file
77
vendor/github.com/go-chi/chi/middleware/middleware18_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// +build go1.8
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
// NOTE: we must import `golang.org/x/net/http2` in order to explicitly enable
|
||||
// http2 transports for certain tests. The runtime pkg does not have this dependency
|
||||
// though as the transport configuration happens under the hood on go 1.7+.
|
||||
|
||||
func TestWrapWriterHTTP2(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, cn := w.(http.CloseNotifier)
|
||||
if !cn {
|
||||
t.Fatal("request should have been a http.CloseNotifier")
|
||||
}
|
||||
_, fl := w.(http.Flusher)
|
||||
if !fl {
|
||||
t.Fatal("request should have been a http.Flusher")
|
||||
}
|
||||
_, hj := w.(http.Hijacker)
|
||||
if hj {
|
||||
t.Fatal("request should not have been a http.Hijacker")
|
||||
}
|
||||
_, rf := w.(io.ReaderFrom)
|
||||
if rf {
|
||||
t.Fatal("request should not have been a io.ReaderFrom")
|
||||
}
|
||||
_, ps := w.(http.Pusher)
|
||||
if !ps {
|
||||
t.Fatal("request should have been a http.Pusher")
|
||||
}
|
||||
|
||||
w.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
wmw := func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
next.ServeHTTP(NewWrapResponseWriter(w, r.ProtoMajor), r)
|
||||
})
|
||||
}
|
||||
|
||||
server := http.Server{
|
||||
Addr: ":7072",
|
||||
Handler: wmw(handler),
|
||||
}
|
||||
// By serving over TLS, we get HTTP2 requests
|
||||
go server.ListenAndServeTLS(testdataDir+"/cert.pem", testdataDir+"/key.pem")
|
||||
defer server.Close()
|
||||
// We need the server to start before making the request
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http2.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
// The certificates we are using are self signed
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := client.Get("https://localhost:7072")
|
||||
if err != nil {
|
||||
t.Fatalf("could not get server: %v", err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("non 200 response: %v", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
64
vendor/github.com/go-chi/chi/middleware/middleware_test.go
generated
vendored
Normal file
64
vendor/github.com/go-chi/chi/middleware/middleware_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// NOTE: we must import `golang.org/x/net/http2` in order to explicitly enable
|
||||
// http2 transports for certain tests. The runtime pkg does not have this dependency
|
||||
// though as the transport configuration happens under the hood on go 1.7+.
|
||||
|
||||
var testdataDir string
|
||||
|
||||
func init() {
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
testdataDir = path.Join(path.Dir(filename), "/../testdata")
|
||||
}
|
||||
|
||||
func testRequest(t *testing.T, ts *httptest.Server, method, path string, body io.Reader) (*http.Response, string) {
|
||||
req, err := http.NewRequest(method, ts.URL+path, body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return resp, string(respBody)
|
||||
}
|
||||
|
||||
func assertNoError(t *testing.T, err error) {
|
||||
if err != nil {
|
||||
t.Fatalf("expecting no error")
|
||||
}
|
||||
}
|
||||
|
||||
func assertError(t *testing.T, err error) {
|
||||
if err == nil {
|
||||
t.Fatalf("expecting error")
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqual(t *testing.T, a, b interface{}) {
|
||||
if !reflect.DeepEqual(a, b) {
|
||||
t.Fatalf("expecting values to be equal but got: '%v' and '%v'", a, b)
|
||||
}
|
||||
}
|
||||
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, 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)
|
||||
}
|
||||
59
vendor/github.com/go-chi/chi/middleware/profiler.go
generated
vendored
Normal file
59
vendor/github.com/go-chi/chi/middleware/profiler.go
generated
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
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.Handle("/pprof/block", pprof.Handler("block"))
|
||||
r.Handle("/pprof/heap", pprof.Handler("heap"))
|
||||
r.Handle("/pprof/goroutine", pprof.Handler("goroutine"))
|
||||
r.Handle("/pprof/threadcreate", pprof.Handler("threadcreate"))
|
||||
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
|
||||
// Goji. 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 xff := r.Header.Get(xForwardedFor); xff != "" {
|
||||
i := strings.Index(xff, ", ")
|
||||
if i == -1 {
|
||||
i = len(xff)
|
||||
}
|
||||
ip = xff[:i]
|
||||
} else if xrip := r.Header.Get(xRealIP); xrip != "" {
|
||||
ip = xrip
|
||||
}
|
||||
|
||||
return ip
|
||||
}
|
||||
57
vendor/github.com/go-chi/chi/middleware/realip_test.go
generated
vendored
Normal file
57
vendor/github.com/go-chi/chi/middleware/realip_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
func TestXRealIP(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
req.Header.Add("X-Real-IP", "100.100.100.100")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Use(RealIP)
|
||||
|
||||
realIP := ""
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
realIP = r.RemoteAddr
|
||||
w.Write([]byte("Hello World"))
|
||||
})
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != 200 {
|
||||
t.Fatal("Response Code should be 200")
|
||||
}
|
||||
|
||||
if realIP != "100.100.100.100" {
|
||||
t.Fatal("Test get real IP error.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestXForwardForIP(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
req.Header.Add("X-Forwarded-For", "100.100.100.100")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Use(RealIP)
|
||||
|
||||
realIP := ""
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
realIP = r.RemoteAddr
|
||||
w.Write([]byte("Hello World"))
|
||||
})
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != 200 {
|
||||
t.Fatal("Response Code should be 200")
|
||||
}
|
||||
|
||||
if realIP != "100.100.100.100" {
|
||||
t.Fatal("Test get real IP error.")
|
||||
}
|
||||
}
|
||||
39
vendor/github.com/go-chi/chi/middleware/recoverer.go
generated
vendored
Normal file
39
vendor/github.com/go-chi/chi/middleware/recoverer.go
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package middleware
|
||||
|
||||
// The original work was derived from Goji's middleware, source:
|
||||
// https://github.com/zenazn/goji/tree/master/web/middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
// Recoverer is a middleware that recovers from panics, logs the panic (and a
|
||||
// backtrace), and returns a HTTP 500 (Internal Server Error) status if
|
||||
// possible. Recoverer prints a request ID if one is provided.
|
||||
//
|
||||
// Alternatively, look at https://github.com/pressly/lg middleware pkgs.
|
||||
func Recoverer(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if rvr := recover(); rvr != nil {
|
||||
|
||||
logEntry := GetLogEntry(r)
|
||||
if logEntry != nil {
|
||||
logEntry.Panic(rvr, debug.Stack())
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Panic: %+v\n", rvr)
|
||||
debug.PrintStack()
|
||||
}
|
||||
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
88
vendor/github.com/go-chi/chi/middleware/request_id.go
generated
vendored
Normal file
88
vendor/github.com/go-chi/chi/middleware/request_id.go
generated
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
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 th unique request ID in a request context.
|
||||
const RequestIDKey ctxKeyRequestID = 0
|
||||
|
||||
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) {
|
||||
myid := atomic.AddUint64(&reqid, 1)
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(ctx, RequestIDKey, fmt.Sprintf("%s-%06d", prefix, myid))
|
||||
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)
|
||||
}
|
||||
48
vendor/github.com/go-chi/chi/middleware/strip.go
generated
vendored
Normal file
48
vendor/github.com/go-chi/chi/middleware/strip.go
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"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.
|
||||
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] == '/' {
|
||||
path = path[:len(path)-1]
|
||||
http.Redirect(w, r, path, 301)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
138
vendor/github.com/go-chi/chi/middleware/strip_test.go
generated
vendored
Normal file
138
vendor/github.com/go-chi/chi/middleware/strip_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
func TestStripSlashes(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
// This middleware must be mounted at the top level of the router, not at the end-handler
|
||||
// because then it'll be too late and will end up in a 404
|
||||
r.Use(StripSlashes)
|
||||
|
||||
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(404)
|
||||
w.Write([]byte("nothing here"))
|
||||
})
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("root"))
|
||||
})
|
||||
|
||||
r.Route("/accounts/{accountID}", func(r chi.Router) {
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
accountID := chi.URLParam(r, "accountID")
|
||||
w.Write([]byte(accountID))
|
||||
})
|
||||
})
|
||||
|
||||
ts := httptest.NewServer(r)
|
||||
defer ts.Close()
|
||||
|
||||
if _, resp := testRequest(t, ts, "GET", "/", nil); resp != "root" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
if _, resp := testRequest(t, ts, "GET", "//", nil); resp != "root" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
if _, resp := testRequest(t, ts, "GET", "/accounts/admin", nil); resp != "admin" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
if _, resp := testRequest(t, ts, "GET", "/accounts/admin/", nil); resp != "admin" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
if _, resp := testRequest(t, ts, "GET", "/nothing-here", nil); resp != "nothing here" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripSlashesInRoute(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(404)
|
||||
w.Write([]byte("nothing here"))
|
||||
})
|
||||
|
||||
r.Get("/hi", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("hi"))
|
||||
})
|
||||
|
||||
r.Route("/accounts/{accountID}", func(r chi.Router) {
|
||||
r.Use(StripSlashes)
|
||||
r.Get("/query", func(w http.ResponseWriter, r *http.Request) {
|
||||
accountID := chi.URLParam(r, "accountID")
|
||||
w.Write([]byte(accountID))
|
||||
})
|
||||
})
|
||||
|
||||
ts := httptest.NewServer(r)
|
||||
defer ts.Close()
|
||||
|
||||
if _, resp := testRequest(t, ts, "GET", "/hi", nil); resp != "hi" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
if _, resp := testRequest(t, ts, "GET", "/hi/", nil); resp != "nothing here" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
if _, resp := testRequest(t, ts, "GET", "/accounts/admin/query", nil); resp != "admin" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
if _, resp := testRequest(t, ts, "GET", "/accounts/admin/query/", nil); resp != "admin" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedirectSlashes(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
// This middleware must be mounted at the top level of the router, not at the end-handler
|
||||
// because then it'll be too late and will end up in a 404
|
||||
r.Use(RedirectSlashes)
|
||||
|
||||
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(404)
|
||||
w.Write([]byte("nothing here"))
|
||||
})
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("root"))
|
||||
})
|
||||
|
||||
r.Route("/accounts/{accountID}", func(r chi.Router) {
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
accountID := chi.URLParam(r, "accountID")
|
||||
w.Write([]byte(accountID))
|
||||
})
|
||||
})
|
||||
|
||||
ts := httptest.NewServer(r)
|
||||
defer ts.Close()
|
||||
|
||||
if req, resp := testRequest(t, ts, "GET", "/", nil); resp != "root" && req.StatusCode != 200 {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
|
||||
// NOTE: the testRequest client will follow the redirection..
|
||||
if req, resp := testRequest(t, ts, "GET", "//", nil); resp != "root" && req.StatusCode != 200 {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
|
||||
if req, resp := testRequest(t, ts, "GET", "/accounts/admin", nil); resp != "admin" && req.StatusCode != 200 {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
|
||||
// NOTE: the testRequest client will follow the redirection..
|
||||
if req, resp := testRequest(t, ts, "GET", "/accounts/admin/", nil); resp != "admin" && req.StatusCode != 200 {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
|
||||
if req, resp := testRequest(t, ts, "GET", "/nothing-here", nil); resp != "nothing here" && req.StatusCode != 200 {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
}
|
||||
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, color []byte, s string, args ...interface{}) {
|
||||
if isTTY {
|
||||
w.Write(color)
|
||||
}
|
||||
fmt.Fprintf(w, s, args...)
|
||||
if isTTY {
|
||||
w.Write(reset)
|
||||
}
|
||||
}
|
||||
101
vendor/github.com/go-chi/chi/middleware/throttle.go
generated
vendored
Normal file
101
vendor/github.com/go-chi/chi/middleware/throttle.go
generated
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
errCapacityExceeded = "Server capacity exceeded."
|
||||
errTimedOut = "Timed out while waiting for a pending request to complete."
|
||||
errContextCanceled = "Context was canceled."
|
||||
)
|
||||
|
||||
var (
|
||||
defaultBacklogTimeout = time.Second * 60
|
||||
)
|
||||
|
||||
// Throttle is a middleware that limits number of currently processed requests
|
||||
// at a time.
|
||||
func Throttle(limit int) func(http.Handler) http.Handler {
|
||||
return ThrottleBacklog(limit, 0, defaultBacklogTimeout)
|
||||
}
|
||||
|
||||
// ThrottleBacklog is a middleware that limits number of currently processed
|
||||
// requests at a time and provides a backlog for holding a finite number of
|
||||
// pending requests.
|
||||
func ThrottleBacklog(limit int, backlogLimit int, backlogTimeout time.Duration) func(http.Handler) http.Handler {
|
||||
if limit < 1 {
|
||||
panic("chi/middleware: Throttle expects limit > 0")
|
||||
}
|
||||
|
||||
if backlogLimit < 0 {
|
||||
panic("chi/middleware: Throttle expects backlogLimit to be positive")
|
||||
}
|
||||
|
||||
t := throttler{
|
||||
tokens: make(chan token, limit),
|
||||
backlogTokens: make(chan token, limit+backlogLimit),
|
||||
backlogTimeout: backlogTimeout,
|
||||
}
|
||||
|
||||
// Filling tokens.
|
||||
for i := 0; i < limit+backlogLimit; i++ {
|
||||
if i < limit {
|
||||
t.tokens <- token{}
|
||||
}
|
||||
t.backlogTokens <- token{}
|
||||
}
|
||||
|
||||
fn := func(h http.Handler) http.Handler {
|
||||
t.h = h
|
||||
return &t
|
||||
}
|
||||
|
||||
return fn
|
||||
}
|
||||
|
||||
// token represents a request that is being processed.
|
||||
type token struct{}
|
||||
|
||||
// throttler limits number of currently processed requests at a time.
|
||||
type throttler struct {
|
||||
h http.Handler
|
||||
tokens chan token
|
||||
backlogTokens chan token
|
||||
backlogTimeout time.Duration
|
||||
}
|
||||
|
||||
// ServeHTTP is the primary throttler request handler
|
||||
func (t *throttler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
http.Error(w, errContextCanceled, http.StatusServiceUnavailable)
|
||||
return
|
||||
case btok := <-t.backlogTokens:
|
||||
timer := time.NewTimer(t.backlogTimeout)
|
||||
|
||||
defer func() {
|
||||
t.backlogTokens <- btok
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-timer.C:
|
||||
http.Error(w, errTimedOut, http.StatusServiceUnavailable)
|
||||
return
|
||||
case <-ctx.Done():
|
||||
http.Error(w, errContextCanceled, http.StatusServiceUnavailable)
|
||||
return
|
||||
case tok := <-t.tokens:
|
||||
defer func() {
|
||||
t.tokens <- tok
|
||||
}()
|
||||
t.h.ServeHTTP(w, r)
|
||||
}
|
||||
return
|
||||
default:
|
||||
http.Error(w, errCapacityExceeded, http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
}
|
||||
204
vendor/github.com/go-chi/chi/middleware/throttle_test.go
generated
vendored
Normal file
204
vendor/github.com/go-chi/chi/middleware/throttle_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
var testContent = []byte("Hello world!")
|
||||
|
||||
func TestThrottleBacklog(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(ThrottleBacklog(10, 50, time.Second*10))
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
time.Sleep(time.Second * 1) // Expensive operation.
|
||||
w.Write(testContent)
|
||||
})
|
||||
|
||||
server := httptest.NewServer(r)
|
||||
defer server.Close()
|
||||
|
||||
client := http.Client{
|
||||
Timeout: time.Second * 5, // Maximum waiting time.
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// The throttler proccesses 10 consecutive requests, each one of those
|
||||
// requests lasts 1s. The maximum number of requests this can possible serve
|
||||
// before the clients time out (5s) is 40.
|
||||
for i := 0; i < 40; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
res, err := client.Get(server.URL)
|
||||
assertNoError(t, err)
|
||||
|
||||
assertEqual(t, http.StatusOK, res.StatusCode)
|
||||
buf, err := ioutil.ReadAll(res.Body)
|
||||
assertNoError(t, err)
|
||||
assertEqual(t, testContent, buf)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestThrottleClientTimeout(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(ThrottleBacklog(10, 50, time.Second*10))
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
time.Sleep(time.Second * 5) // Expensive operation.
|
||||
w.Write(testContent)
|
||||
})
|
||||
|
||||
server := httptest.NewServer(r)
|
||||
defer server.Close()
|
||||
|
||||
client := http.Client{
|
||||
Timeout: time.Second * 3, // Maximum waiting time.
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
_, err := client.Get(server.URL)
|
||||
assertError(t, err)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestThrottleTriggerGatewayTimeout(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(ThrottleBacklog(50, 100, time.Second*5))
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
time.Sleep(time.Second * 10) // Expensive operation.
|
||||
w.Write(testContent)
|
||||
})
|
||||
|
||||
server := httptest.NewServer(r)
|
||||
defer server.Close()
|
||||
|
||||
client := http.Client{
|
||||
Timeout: time.Second * 60, // Maximum waiting time.
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// These requests will be processed normally until they finish.
|
||||
for i := 0; i < 50; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
res, err := client.Get(server.URL)
|
||||
assertNoError(t, err)
|
||||
assertEqual(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
}(i)
|
||||
}
|
||||
|
||||
time.Sleep(time.Second * 1)
|
||||
|
||||
// These requests will wait for the first batch to complete but it will take
|
||||
// too much time, so they will eventually receive a timeout error.
|
||||
for i := 0; i < 50; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
res, err := client.Get(server.URL)
|
||||
assertNoError(t, err)
|
||||
|
||||
buf, err := ioutil.ReadAll(res.Body)
|
||||
assertNoError(t, err)
|
||||
assertEqual(t, http.StatusServiceUnavailable, res.StatusCode)
|
||||
assertEqual(t, errTimedOut, strings.TrimSpace(string(buf)))
|
||||
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestThrottleMaximum(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(ThrottleBacklog(50, 50, time.Second*5))
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
time.Sleep(time.Second * 2) // Expensive operation.
|
||||
w.Write(testContent)
|
||||
})
|
||||
|
||||
server := httptest.NewServer(r)
|
||||
defer server.Close()
|
||||
|
||||
client := http.Client{
|
||||
Timeout: time.Second * 60, // Maximum waiting time.
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
res, err := client.Get(server.URL)
|
||||
assertNoError(t, err)
|
||||
assertEqual(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
buf, err := ioutil.ReadAll(res.Body)
|
||||
assertNoError(t, err)
|
||||
assertEqual(t, testContent, buf)
|
||||
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Wait less time than what the server takes to reply.
|
||||
time.Sleep(time.Second * 1)
|
||||
|
||||
// At this point the server is still processing, all the following request
|
||||
// will be beyond the server capacity.
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
res, err := client.Get(server.URL)
|
||||
assertNoError(t, err)
|
||||
|
||||
buf, err := ioutil.ReadAll(res.Body)
|
||||
assertNoError(t, err)
|
||||
assertEqual(t, http.StatusServiceUnavailable, res.StatusCode)
|
||||
assertEqual(t, errCapacityExceeded, strings.TrimSpace(string(buf)))
|
||||
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
48
vendor/github.com/go-chi/chi/middleware/timeout.go
generated
vendored
Normal file
48
vendor/github.com/go-chi/chi/middleware/timeout.go
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
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(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
148
vendor/github.com/go-chi/chi/middleware/wrap_writer.go
generated
vendored
Normal file
148
vendor/github.com/go-chi/chi/middleware/wrap_writer.go
generated
vendored
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
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"
|
||||
)
|
||||
|
||||
// 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.WriteHeader(http.StatusOK)
|
||||
n, err := b.ResponseWriter.Write(buf)
|
||||
if b.tee != nil {
|
||||
_, err2 := b.tee.Write(buf[:n])
|
||||
// Prefer errors generated by the proxied writer.
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
}
|
||||
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() {
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
|
||||
var _ http.Flusher = &flushWriter{}
|
||||
|
||||
// httpFancyWriter is a HTTP writer that additionally satisfies http.CloseNotifier,
|
||||
// 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) CloseNotify() <-chan bool {
|
||||
cn := f.basicWriter.ResponseWriter.(http.CloseNotifier)
|
||||
return cn.CloseNotify()
|
||||
}
|
||||
func (f *httpFancyWriter) Flush() {
|
||||
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 *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.CloseNotifier = &httpFancyWriter{}
|
||||
var _ http.Flusher = &httpFancyWriter{}
|
||||
var _ http.Hijacker = &httpFancyWriter{}
|
||||
var _ io.ReaderFrom = &httpFancyWriter{}
|
||||
|
||||
// http2FancyWriter is a HTTP2 writer that additionally satisfies http.CloseNotifier,
|
||||
// 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) CloseNotify() <-chan bool {
|
||||
cn := f.basicWriter.ResponseWriter.(http.CloseNotifier)
|
||||
return cn.CloseNotify()
|
||||
}
|
||||
func (f *http2FancyWriter) Flush() {
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
|
||||
var _ http.CloseNotifier = &http2FancyWriter{}
|
||||
var _ http.Flusher = &http2FancyWriter{}
|
||||
34
vendor/github.com/go-chi/chi/middleware/wrap_writer17.go
generated
vendored
Normal file
34
vendor/github.com/go-chi/chi/middleware/wrap_writer17.go
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// +build go1.7,!go1.8
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"io"
|
||||
"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 {
|
||||
_, cn := w.(http.CloseNotifier)
|
||||
_, fl := w.(http.Flusher)
|
||||
|
||||
bw := basicWriter{ResponseWriter: w}
|
||||
|
||||
if protoMajor == 2 {
|
||||
if cn && fl {
|
||||
return &http2FancyWriter{bw}
|
||||
}
|
||||
} else {
|
||||
_, hj := w.(http.Hijacker)
|
||||
_, rf := w.(io.ReaderFrom)
|
||||
if cn && fl && hj && rf {
|
||||
return &httpFancyWriter{bw}
|
||||
}
|
||||
}
|
||||
if fl {
|
||||
return &flushWriter{bw}
|
||||
}
|
||||
|
||||
return &bw
|
||||
}
|
||||
41
vendor/github.com/go-chi/chi/middleware/wrap_writer18.go
generated
vendored
Normal file
41
vendor/github.com/go-chi/chi/middleware/wrap_writer18.go
generated
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// +build go1.8
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"io"
|
||||
"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 {
|
||||
_, cn := w.(http.CloseNotifier)
|
||||
_, fl := w.(http.Flusher)
|
||||
|
||||
bw := basicWriter{ResponseWriter: w}
|
||||
|
||||
if protoMajor == 2 {
|
||||
_, ps := w.(http.Pusher)
|
||||
if cn && fl && ps {
|
||||
return &http2FancyWriter{bw}
|
||||
}
|
||||
} else {
|
||||
_, hj := w.(http.Hijacker)
|
||||
_, rf := w.(io.ReaderFrom)
|
||||
if cn && fl && hj && rf {
|
||||
return &httpFancyWriter{bw}
|
||||
}
|
||||
}
|
||||
if fl {
|
||||
return &flushWriter{bw}
|
||||
}
|
||||
|
||||
return &bw
|
||||
}
|
||||
|
||||
func (f *http2FancyWriter) Push(target string, opts *http.PushOptions) error {
|
||||
return f.basicWriter.ResponseWriter.(http.Pusher).Push(target, opts)
|
||||
}
|
||||
|
||||
var _ http.Pusher = &http2FancyWriter{}
|
||||
462
vendor/github.com/go-chi/chi/mux.go
generated
vendored
Normal file
462
vendor/github.com/go-chi/chi/mux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,462 @@
|
|||
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{}}
|
||||
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 {
|
||||
panic("chi: attempting to route to a mux with no handlers.")
|
||||
}
|
||||
|
||||
// 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
|
||||
r = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx))
|
||||
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 further
|
||||
// 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{inline: true, parent: mx, tree: mx.tree, middlewares: mws}
|
||||
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)
|
||||
}
|
||||
|
||||
// Wrap the sub-router in a handlerFunc to scope the request path for routing.
|
||||
mountHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
rctx := RouteContext(r.Context())
|
||||
rctx.RoutePath = mx.nextRoutePath(rctx)
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
if pattern == "" || pattern[len(pattern)-1] != '/' {
|
||||
notFoundHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
mx.NotFoundHandler().ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
mx.handle(mALL|mSTUB, pattern, mountHandler)
|
||||
mx.handle(mALL|mSTUB, pattern+"/", notFoundHandler)
|
||||
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 final routing handler for this Mux.
|
||||
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)
|
||||
}
|
||||
1626
vendor/github.com/go-chi/chi/mux_test.go
generated
vendored
Normal file
1626
vendor/github.com/go-chi/chi/mux_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
19
vendor/github.com/go-chi/chi/testdata/cert.pem
generated
vendored
Normal file
19
vendor/github.com/go-chi/chi/testdata/cert.pem
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIC/zCCAeegAwIBAgIRANioW0Re7DtpT4qZpJU1iK8wDQYJKoZIhvcNAQELBQAw
|
||||
EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xNjEyMzExNDU0MzBaFw0xNzEyMzExNDU0
|
||||
MzBaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQDpFfOsaXDYlL+ektfsqGYrSAsoTbe7zqjpow9nqUU4PmLRu2YMaaW8
|
||||
fAoneUnJxsJw7ql38+VMpphZUOmOWvsO7uV/lfnTIQfTwllHDdgAR5A11d84Zy/y
|
||||
TiNIFJduuaPtEhQs1dxPhU7TG8sEfFRhBoUDPv473akeGPNkVU756RVBYM6rUc3b
|
||||
YygD0PXGsQ2obrImbYUyyHH5YClCvGl1No57n3ugLqSSfwbgR3/Gw7kkGKy0PMOu
|
||||
TuHuJnTEmofJPkqEyFRVMlIAtfqFqJUfDHTOuQGWIUPnjDg+fqTI9EPJ+pElBqDQ
|
||||
IqW93BY5XePMdrTQc1h6xkduDfuLeA7TAgMBAAGjUDBOMA4GA1UdDwEB/wQEAwIF
|
||||
oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBkGA1UdEQQSMBCC
|
||||
DmxvY2FsaG9zdDo3MDcyMA0GCSqGSIb3DQEBCwUAA4IBAQDnsWmZdf7209A/XHUe
|
||||
xoONCbU8jaYFVoA+CN9J+3CASzrzTQ4fh9RJdm2FZuv4sWnb5c5hDN7H/M/nLcb0
|
||||
+uu7ACBGhd7yACYCQm/z3Pm3CY2BRIo0vCCRioGx+6J3CPGWFm0vHwNBge0iBOKC
|
||||
Wn+/YOlTDth/M3auHYlr7hdFmf57U4V/5iTr4wiKxwM9yMPcVRQF/1XpPd7A0VqM
|
||||
nFSEfDpFjrA7MvT3DrRqQGqF/ZXxDbro2nyki3YG8FwgKlFNVN9w55zNiriQ+WNA
|
||||
uz86lKg1FTc+m/R/0CD//7+7mme28N813EPVdV83TgxWNrfvAIRazkHE7YxETry0
|
||||
BJDg
|
||||
-----END CERTIFICATE-----
|
||||
27
vendor/github.com/go-chi/chi/testdata/key.pem
generated
vendored
Normal file
27
vendor/github.com/go-chi/chi/testdata/key.pem
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA6RXzrGlw2JS/npLX7KhmK0gLKE23u86o6aMPZ6lFOD5i0btm
|
||||
DGmlvHwKJ3lJycbCcO6pd/PlTKaYWVDpjlr7Du7lf5X50yEH08JZRw3YAEeQNdXf
|
||||
OGcv8k4jSBSXbrmj7RIULNXcT4VO0xvLBHxUYQaFAz7+O92pHhjzZFVO+ekVQWDO
|
||||
q1HN22MoA9D1xrENqG6yJm2FMshx+WApQrxpdTaOe597oC6kkn8G4Ed/xsO5JBis
|
||||
tDzDrk7h7iZ0xJqHyT5KhMhUVTJSALX6haiVHwx0zrkBliFD54w4Pn6kyPRDyfqR
|
||||
JQag0CKlvdwWOV3jzHa00HNYesZHbg37i3gO0wIDAQABAoIBAFvqYDE5U1rVLctm
|
||||
tOeKcN/YhS3bl/zjvhCEUOrcAYPwdh+m+tMiRk1RzN9MISEE1GCcfQ/kiiPz/lga
|
||||
ZD/S+PYmlzH8/ouXlvKWzYYLm4ZgsinIsUIYzvuKfLdMB3uOkWpHmtUjcMGbHD57
|
||||
009tiAjK/WEOUkthWfOYe0KxsXczBn3PTAWZuiIkuA3RVWa7pCCFHUENkViP58wl
|
||||
Ky1hYKnunKPApRwuiC6qIT5ZOCSukdCCbkmRnj/x+P8+nsosu+1d85MNZb8uLRi0
|
||||
RzMmuOfOK2poDsrNHQX7itKlu7rzMJQc3+RauqIZovNe/BmSq+tYBLboXvUp18g/
|
||||
+VqKeEECgYEA/LaD1tJepzD/1lhgunFcnDjxsDJqLUpfR5eDMX1qhGJphuPBLOXS
|
||||
ushmVVjbVIn25Wxeoe4RYrZ6Tuu0FEJJgV44Lt42OOFgK2gyrCJpYmlxpRaw+7jc
|
||||
Dbp1Sh3/9VqMZjR/mQIzTnfOtS2n4Fk1Q53hdJn5Pn+uPMmMO4hF87sCgYEA7B4V
|
||||
BACsd6eqVxKkEMc72VLeYb0Ri0bl0FwbvIKXImppwA0tbMDmeA+6yhcRm23dhd5v
|
||||
cfNhJepRIzkM2CkhnazlsAbDoJPqb7/sbNzodtW1P0op7YIFYbrkcX4yOu9O1DNI
|
||||
Ij4PR8H1WcpPjhvr3q+iNO5agQX7bMQ1BnnJg8kCgYBA1tdm090DSrgpl81hqNpZ
|
||||
HucsDRNfAXkG1mIL3aDpzJJE0MTsrx7tW6Od/ElyHF/jp3V0WK/PQwCIpUMz+3n+
|
||||
nl0N8We6GmFhYb+2mLGvVVyaPgM04s5bG18ioCXfHtdtFcUzTfQ6CtVXeRpcnqbi
|
||||
7Ww+TY88sOfUouW/FIzWJwKBgQCsLauJhaw+fOc8I328NmywJzu+7g5TD9oZvHEF
|
||||
X/0xvYNr5rAPNANb3ayKHZRbURxOuEtwPtfCvEF6e+mf3y6COkgrumMBP5ue7cdM
|
||||
AzMJJQHMKxqz9TJTd+OJ10ptq4BCQTsCrVqbKxbs6RhmOnofoteX3Y/lsiULxXAd
|
||||
TsXh8QKBgQDQHosH8VoL7vIK+SqY5uoHAhMytSVNx4IaZZg4ho8oyjw12QXcidgV
|
||||
QJZQMdPEv8cAK78WcQdSthop+O/tu2cKLHyAmWmO3oU7gIQECui0aMXSqraO6Vde
|
||||
C5tqYlyLa7bHZS3AqrjRv9BRfwPKVkmBoYdA652rN/tE/K4UWsghnA==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
818
vendor/github.com/go-chi/chi/tree.go
generated
vendored
Normal file
818
vendor/github.com/go-chi/chi/tree.go
generated
vendored
Normal file
|
|
@ -0,0 +1,818 @@
|
|||
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"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type methodTyp int
|
||||
|
||||
const (
|
||||
mCONNECT methodTyp = 1 << iota
|
||||
mDELETE
|
||||
mGET
|
||||
mHEAD
|
||||
mLINK
|
||||
mOPTIONS
|
||||
mPATCH
|
||||
mPOST
|
||||
mPUT
|
||||
mTRACE
|
||||
mUNLINK
|
||||
mSTUB
|
||||
|
||||
mALL methodTyp = mCONNECT | mDELETE | mGET | mHEAD | mLINK |
|
||||
mOPTIONS | mPATCH | mPOST | mPUT | mTRACE | mUNLINK
|
||||
)
|
||||
|
||||
var methodMap = map[string]methodTyp{
|
||||
"CONNECT": mCONNECT,
|
||||
"DELETE": mDELETE,
|
||||
"GET": mGET,
|
||||
"HEAD": mHEAD,
|
||||
"LINK": mLINK,
|
||||
"OPTIONS": mOPTIONS,
|
||||
"PATCH": mPATCH,
|
||||
"POST": mPOST,
|
||||
"PUT": mPUT,
|
||||
"TRACE": mTRACE,
|
||||
"UNLINK": mUNLINK,
|
||||
}
|
||||
|
||||
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, 0)
|
||||
}
|
||||
|
||||
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.Match([]byte(xsearch[:p])) == false {
|
||||
continue
|
||||
}
|
||||
} else if strings.IndexByte(xsearch[:p], '/') != -1 {
|
||||
// avoid a match across path segments
|
||||
continue
|
||||
}
|
||||
|
||||
rctx.routeParams.Values = append(rctx.routeParams.Values, xsearch[:p])
|
||||
xsearch = xsearch[p:]
|
||||
break
|
||||
}
|
||||
|
||||
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) isEmpty() bool {
|
||||
for _, nds := range n.children {
|
||||
if len(nds) > 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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, 0)
|
||||
|
||||
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, 0)
|
||||
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]
|
||||
}
|
||||
|
||||
return nt, key, rexpat, tail, ps, pe
|
||||
}
|
||||
|
||||
// Wildcard pattern as finale
|
||||
// TODO: should we panic if there is stuff after the * ???
|
||||
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.
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
442
vendor/github.com/go-chi/chi/tree_test.go
generated
vendored
Normal file
442
vendor/github.com/go-chi/chi/tree_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
package chi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTree(t *testing.T) {
|
||||
hStub := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hIndex := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hFavicon := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hArticleList := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hArticleNear := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hArticleShow := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hArticleShowRelated := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hArticleShowOpts := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hArticleSlug := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hArticleByUser := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hUserList := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hUserShow := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hAdminCatchall := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hAdminAppShow := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hAdminAppShowCatchall := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hUserProfile := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hUserSuper := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hUserAll := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hHubView1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hHubView2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hHubView3 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
tr := &node{}
|
||||
|
||||
tr.InsertRoute(mGET, "/", hIndex)
|
||||
tr.InsertRoute(mGET, "/favicon.ico", hFavicon)
|
||||
|
||||
tr.InsertRoute(mGET, "/pages/*", hStub)
|
||||
|
||||
tr.InsertRoute(mGET, "/article", hArticleList)
|
||||
tr.InsertRoute(mGET, "/article/", hArticleList)
|
||||
|
||||
tr.InsertRoute(mGET, "/article/near", hArticleNear)
|
||||
tr.InsertRoute(mGET, "/article/{id}", hStub)
|
||||
tr.InsertRoute(mGET, "/article/{id}", hArticleShow)
|
||||
tr.InsertRoute(mGET, "/article/{id}", hArticleShow) // duplicate will have no effect
|
||||
tr.InsertRoute(mGET, "/article/@{user}", hArticleByUser)
|
||||
|
||||
tr.InsertRoute(mGET, "/article/{sup}/{opts}", hArticleShowOpts)
|
||||
tr.InsertRoute(mGET, "/article/{id}/{opts}", hArticleShowOpts) // overwrite above route, latest wins
|
||||
|
||||
tr.InsertRoute(mGET, "/article/{iffd}/edit", hStub)
|
||||
tr.InsertRoute(mGET, "/article/{id}//related", hArticleShowRelated)
|
||||
tr.InsertRoute(mGET, "/article/slug/{month}/-/{day}/{year}", hArticleSlug)
|
||||
|
||||
tr.InsertRoute(mGET, "/admin/user", hUserList)
|
||||
tr.InsertRoute(mGET, "/admin/user/", hStub) // will get replaced by next route
|
||||
tr.InsertRoute(mGET, "/admin/user/", hUserList)
|
||||
|
||||
tr.InsertRoute(mGET, "/admin/user//{id}", hUserShow)
|
||||
tr.InsertRoute(mGET, "/admin/user/{id}", hUserShow)
|
||||
|
||||
tr.InsertRoute(mGET, "/admin/apps/{id}", hAdminAppShow)
|
||||
tr.InsertRoute(mGET, "/admin/apps/{id}/*ff", hAdminAppShowCatchall) // TODO: ALLOWED...? prob not.. panic..?
|
||||
|
||||
tr.InsertRoute(mGET, "/admin/*ff", hStub) // catchall segment will get replaced by next route
|
||||
tr.InsertRoute(mGET, "/admin/*", hAdminCatchall)
|
||||
|
||||
tr.InsertRoute(mGET, "/users/{userID}/profile", hUserProfile)
|
||||
tr.InsertRoute(mGET, "/users/super/*", hUserSuper)
|
||||
tr.InsertRoute(mGET, "/users/*", hUserAll)
|
||||
|
||||
tr.InsertRoute(mGET, "/hubs/{hubID}/view", hHubView1)
|
||||
tr.InsertRoute(mGET, "/hubs/{hubID}/view/*", hHubView2)
|
||||
sr := NewRouter()
|
||||
sr.Get("/users", hHubView3)
|
||||
tr.InsertRoute(mGET, "/hubs/{hubID}/*", sr)
|
||||
tr.InsertRoute(mGET, "/hubs/{hubID}/users", hHubView3)
|
||||
|
||||
tests := []struct {
|
||||
r string // input request path
|
||||
h http.Handler // output matched handler
|
||||
k []string // output param keys
|
||||
v []string // output param values
|
||||
}{
|
||||
{r: "/", h: hIndex, k: []string{}, v: []string{}},
|
||||
{r: "/favicon.ico", h: hFavicon, k: []string{}, v: []string{}},
|
||||
|
||||
{r: "/pages", h: nil, k: []string{}, v: []string{}},
|
||||
{r: "/pages/", h: hStub, k: []string{"*"}, v: []string{""}},
|
||||
{r: "/pages/yes", h: hStub, k: []string{"*"}, v: []string{"yes"}},
|
||||
|
||||
{r: "/article", h: hArticleList, k: []string{}, v: []string{}},
|
||||
{r: "/article/", h: hArticleList, k: []string{}, v: []string{}},
|
||||
{r: "/article/near", h: hArticleNear, k: []string{}, v: []string{}},
|
||||
{r: "/article/neard", h: hArticleShow, k: []string{"id"}, v: []string{"neard"}},
|
||||
{r: "/article/123", h: hArticleShow, k: []string{"id"}, v: []string{"123"}},
|
||||
{r: "/article/123/456", h: hArticleShowOpts, k: []string{"id", "opts"}, v: []string{"123", "456"}},
|
||||
{r: "/article/@peter", h: hArticleByUser, k: []string{"user"}, v: []string{"peter"}},
|
||||
{r: "/article/22//related", h: hArticleShowRelated, k: []string{"id"}, v: []string{"22"}},
|
||||
{r: "/article/111/edit", h: hStub, k: []string{"iffd"}, v: []string{"111"}},
|
||||
{r: "/article/slug/sept/-/4/2015", h: hArticleSlug, k: []string{"month", "day", "year"}, v: []string{"sept", "4", "2015"}},
|
||||
{r: "/article/:id", h: hArticleShow, k: []string{"id"}, v: []string{":id"}},
|
||||
|
||||
{r: "/admin/user", h: hUserList, k: []string{}, v: []string{}},
|
||||
{r: "/admin/user/", h: hUserList, k: []string{}, v: []string{}},
|
||||
{r: "/admin/user/1", h: hUserShow, k: []string{"id"}, v: []string{"1"}},
|
||||
{r: "/admin/user//1", h: hUserShow, k: []string{"id"}, v: []string{"1"}},
|
||||
{r: "/admin/hi", h: hAdminCatchall, k: []string{"*"}, v: []string{"hi"}},
|
||||
{r: "/admin/lots/of/:fun", h: hAdminCatchall, k: []string{"*"}, v: []string{"lots/of/:fun"}},
|
||||
{r: "/admin/apps/333", h: hAdminAppShow, k: []string{"id"}, v: []string{"333"}},
|
||||
{r: "/admin/apps/333/woot", h: hAdminAppShowCatchall, k: []string{"id", "*"}, v: []string{"333", "woot"}},
|
||||
|
||||
{r: "/hubs/123/view", h: hHubView1, k: []string{"hubID"}, v: []string{"123"}},
|
||||
{r: "/hubs/123/view/index.html", h: hHubView2, k: []string{"hubID", "*"}, v: []string{"123", "index.html"}},
|
||||
{r: "/hubs/123/users", h: hHubView3, k: []string{"hubID"}, v: []string{"123"}},
|
||||
|
||||
{r: "/users/123/profile", h: hUserProfile, k: []string{"userID"}, v: []string{"123"}},
|
||||
{r: "/users/super/123/okay/yes", h: hUserSuper, k: []string{"*"}, v: []string{"123/okay/yes"}},
|
||||
{r: "/users/123/okay/yes", h: hUserAll, k: []string{"*"}, v: []string{"123/okay/yes"}},
|
||||
}
|
||||
|
||||
// log.Println("~~~~~~~~~")
|
||||
// log.Println("~~~~~~~~~")
|
||||
// debugPrintTree(0, 0, tr, 0)
|
||||
// log.Println("~~~~~~~~~")
|
||||
// log.Println("~~~~~~~~~")
|
||||
|
||||
for i, tt := range tests {
|
||||
rctx := NewRouteContext()
|
||||
|
||||
_, handlers, _ := tr.FindRoute(rctx, mGET, tt.r)
|
||||
|
||||
var handler http.Handler
|
||||
if methodHandler, ok := handlers[mGET]; ok {
|
||||
handler = methodHandler.handler
|
||||
}
|
||||
|
||||
paramKeys := rctx.routeParams.Keys
|
||||
paramValues := rctx.routeParams.Values
|
||||
|
||||
if fmt.Sprintf("%v", tt.h) != fmt.Sprintf("%v", handler) {
|
||||
t.Errorf("input [%d]: find '%s' expecting handler:%v , got:%v", i, tt.r, tt.h, handler)
|
||||
}
|
||||
if !stringSliceEqual(tt.k, paramKeys) {
|
||||
t.Errorf("input [%d]: find '%s' expecting paramKeys:(%d)%v , got:(%d)%v", i, tt.r, len(tt.k), tt.k, len(paramKeys), paramKeys)
|
||||
}
|
||||
if !stringSliceEqual(tt.v, paramValues) {
|
||||
t.Errorf("input [%d]: find '%s' expecting paramValues:(%d)%v , got:(%d)%v", i, tt.r, len(tt.v), tt.v, len(paramValues), paramValues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeMoar(t *testing.T) {
|
||||
hStub := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub3 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub4 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub5 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub6 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub7 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub8 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub9 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub10 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub11 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub12 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub13 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub14 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub15 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub16 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
// TODO: panic if we see {id}{x} because we're missing a delimiter, its not possible.
|
||||
// also {:id}* is not possible.
|
||||
|
||||
tr := &node{}
|
||||
|
||||
tr.InsertRoute(mGET, "/articlefun", hStub5)
|
||||
tr.InsertRoute(mGET, "/articles/{id}", hStub)
|
||||
tr.InsertRoute(mDELETE, "/articles/{slug}", hStub8)
|
||||
tr.InsertRoute(mGET, "/articles/search", hStub1)
|
||||
tr.InsertRoute(mGET, "/articles/{id}:delete", hStub8)
|
||||
tr.InsertRoute(mGET, "/articles/{iidd}!sup", hStub4)
|
||||
tr.InsertRoute(mGET, "/articles/{id}:{op}", hStub3)
|
||||
tr.InsertRoute(mGET, "/articles/{id}:{op}", hStub2) // this route sets a new handler for the above route
|
||||
tr.InsertRoute(mGET, "/articles/{slug:^[a-z]+}/posts", hStub) // up to tail '/' will only match if contents match the rex
|
||||
tr.InsertRoute(mGET, "/articles/{id}/posts/{pid}", hStub6) // /articles/123/posts/1
|
||||
tr.InsertRoute(mGET, "/articles/{id}/posts/{month}/{day}/{year}/{slug}", hStub7) // /articles/123/posts/09/04/1984/juice
|
||||
tr.InsertRoute(mGET, "/articles/{id}.json", hStub10)
|
||||
tr.InsertRoute(mGET, "/articles/{id}/data.json", hStub11)
|
||||
tr.InsertRoute(mGET, "/articles/files/{file}.{ext}", hStub12)
|
||||
tr.InsertRoute(mPUT, "/articles/me", hStub13)
|
||||
|
||||
// TODO: make a separate test case for this one..
|
||||
// tr.InsertRoute(mGET, "/articles/{id}/{id}", hStub1) // panic expected, we're duplicating param keys
|
||||
|
||||
tr.InsertRoute(mGET, "/pages/*ff", hStub) // TODO: panic, allow it..?
|
||||
tr.InsertRoute(mGET, "/pages/*", hStub9)
|
||||
|
||||
tr.InsertRoute(mGET, "/users/{id}", hStub14)
|
||||
tr.InsertRoute(mGET, "/users/{id}/settings/{key}", hStub15)
|
||||
tr.InsertRoute(mGET, "/users/{id}/settings/*", hStub16)
|
||||
|
||||
tests := []struct {
|
||||
m methodTyp // input request http method
|
||||
r string // input request path
|
||||
h http.Handler // output matched handler
|
||||
k []string // output param keys
|
||||
v []string // output param values
|
||||
}{
|
||||
{m: mGET, r: "/articles/search", h: hStub1, k: []string{}, v: []string{}},
|
||||
{m: mGET, r: "/articlefun", h: hStub5, k: []string{}, v: []string{}},
|
||||
{m: mGET, r: "/articles/123", h: hStub, k: []string{"id"}, v: []string{"123"}},
|
||||
{m: mDELETE, r: "/articles/123mm", h: hStub8, k: []string{"slug"}, v: []string{"123mm"}},
|
||||
{m: mGET, r: "/articles/789:delete", h: hStub8, k: []string{"id"}, v: []string{"789"}},
|
||||
{m: mGET, r: "/articles/789!sup", h: hStub4, k: []string{"iidd"}, v: []string{"789"}},
|
||||
{m: mGET, r: "/articles/123:sync", h: hStub2, k: []string{"id", "op"}, v: []string{"123", "sync"}},
|
||||
{m: mGET, r: "/articles/456/posts/1", h: hStub6, k: []string{"id", "pid"}, v: []string{"456", "1"}},
|
||||
{m: mGET, r: "/articles/456/posts/09/04/1984/juice", h: hStub7, k: []string{"id", "month", "day", "year", "slug"}, v: []string{"456", "09", "04", "1984", "juice"}},
|
||||
{m: mGET, r: "/articles/456.json", h: hStub10, k: []string{"id"}, v: []string{"456"}},
|
||||
{m: mGET, r: "/articles/456/data.json", h: hStub11, k: []string{"id"}, v: []string{"456"}},
|
||||
|
||||
{m: mGET, r: "/articles/files/file.zip", h: hStub12, k: []string{"file", "ext"}, v: []string{"file", "zip"}},
|
||||
{m: mGET, r: "/articles/files/photos.tar.gz", h: hStub12, k: []string{"file", "ext"}, v: []string{"photos", "tar.gz"}},
|
||||
{m: mGET, r: "/articles/files/photos.tar.gz", h: hStub12, k: []string{"file", "ext"}, v: []string{"photos", "tar.gz"}},
|
||||
|
||||
{m: mPUT, r: "/articles/me", h: hStub13, k: []string{}, v: []string{}},
|
||||
{m: mGET, r: "/articles/me", h: hStub, k: []string{"id"}, v: []string{"me"}},
|
||||
{m: mGET, r: "/pages", h: nil, k: []string{}, v: []string{}},
|
||||
{m: mGET, r: "/pages/", h: hStub9, k: []string{"*"}, v: []string{""}},
|
||||
{m: mGET, r: "/pages/yes", h: hStub9, k: []string{"*"}, v: []string{"yes"}},
|
||||
|
||||
{m: mGET, r: "/users/1", h: hStub14, k: []string{"id"}, v: []string{"1"}},
|
||||
{m: mGET, r: "/users/", h: nil, k: []string{}, v: []string{}},
|
||||
{m: mGET, r: "/users/2/settings/password", h: hStub15, k: []string{"id", "key"}, v: []string{"2", "password"}},
|
||||
{m: mGET, r: "/users/2/settings/", h: hStub16, k: []string{"id", "*"}, v: []string{"2", ""}},
|
||||
}
|
||||
|
||||
// log.Println("~~~~~~~~~")
|
||||
// log.Println("~~~~~~~~~")
|
||||
// debugPrintTree(0, 0, tr, 0)
|
||||
// log.Println("~~~~~~~~~")
|
||||
// log.Println("~~~~~~~~~")
|
||||
|
||||
for i, tt := range tests {
|
||||
rctx := NewRouteContext()
|
||||
|
||||
_, handlers, _ := tr.FindRoute(rctx, tt.m, tt.r)
|
||||
|
||||
var handler http.Handler
|
||||
if methodHandler, ok := handlers[tt.m]; ok {
|
||||
handler = methodHandler.handler
|
||||
}
|
||||
|
||||
paramKeys := rctx.routeParams.Keys
|
||||
paramValues := rctx.routeParams.Values
|
||||
|
||||
if fmt.Sprintf("%v", tt.h) != fmt.Sprintf("%v", handler) {
|
||||
t.Errorf("input [%d]: find '%s' expecting handler:%v , got:%v", i, tt.r, tt.h, handler)
|
||||
}
|
||||
if !stringSliceEqual(tt.k, paramKeys) {
|
||||
t.Errorf("input [%d]: find '%s' expecting paramKeys:(%d)%v , got:(%d)%v", i, tt.r, len(tt.k), tt.k, len(paramKeys), paramKeys)
|
||||
}
|
||||
if !stringSliceEqual(tt.v, paramValues) {
|
||||
t.Errorf("input [%d]: find '%s' expecting paramValues:(%d)%v , got:(%d)%v", i, tt.r, len(tt.v), tt.v, len(paramValues), paramValues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeRegexp(t *testing.T) {
|
||||
hStub1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub3 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub4 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub5 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub6 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub7 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
tr := &node{}
|
||||
tr.InsertRoute(mGET, "/articles/{rid:^[0-9]{5,6}}", hStub7)
|
||||
tr.InsertRoute(mGET, "/articles/{zid:^0[0-9]+}", hStub3)
|
||||
tr.InsertRoute(mGET, "/articles/{name:^@[a-z]+}/posts", hStub4)
|
||||
tr.InsertRoute(mGET, "/articles/{op:^[0-9]+}/run", hStub5)
|
||||
tr.InsertRoute(mGET, "/articles/{id:^[0-9]+}", hStub1)
|
||||
tr.InsertRoute(mGET, "/articles/{id:^[1-9]+}-{aux}", hStub6)
|
||||
tr.InsertRoute(mGET, "/articles/{slug}", hStub2)
|
||||
|
||||
// log.Println("~~~~~~~~~")
|
||||
// log.Println("~~~~~~~~~")
|
||||
// debugPrintTree(0, 0, tr, 0)
|
||||
// log.Println("~~~~~~~~~")
|
||||
// log.Println("~~~~~~~~~")
|
||||
|
||||
tests := []struct {
|
||||
r string // input request path
|
||||
h http.Handler // output matched handler
|
||||
k []string // output param keys
|
||||
v []string // output param values
|
||||
}{
|
||||
{r: "/articles", h: nil, k: []string{}, v: []string{}},
|
||||
{r: "/articles/12345", h: hStub7, k: []string{"rid"}, v: []string{"12345"}},
|
||||
{r: "/articles/123", h: hStub1, k: []string{"id"}, v: []string{"123"}},
|
||||
{r: "/articles/how-to-build-a-router", h: hStub2, k: []string{"slug"}, v: []string{"how-to-build-a-router"}},
|
||||
{r: "/articles/0456", h: hStub3, k: []string{"zid"}, v: []string{"0456"}},
|
||||
{r: "/articles/@pk/posts", h: hStub4, k: []string{"name"}, v: []string{"@pk"}},
|
||||
{r: "/articles/1/run", h: hStub5, k: []string{"op"}, v: []string{"1"}},
|
||||
{r: "/articles/1122", h: hStub1, k: []string{"id"}, v: []string{"1122"}},
|
||||
{r: "/articles/1122-yes", h: hStub6, k: []string{"id", "aux"}, v: []string{"1122", "yes"}},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
rctx := NewRouteContext()
|
||||
|
||||
_, handlers, _ := tr.FindRoute(rctx, mGET, tt.r)
|
||||
|
||||
var handler http.Handler
|
||||
if methodHandler, ok := handlers[mGET]; ok {
|
||||
handler = methodHandler.handler
|
||||
}
|
||||
|
||||
paramKeys := rctx.routeParams.Keys
|
||||
paramValues := rctx.routeParams.Values
|
||||
|
||||
if fmt.Sprintf("%v", tt.h) != fmt.Sprintf("%v", handler) {
|
||||
t.Errorf("input [%d]: find '%s' expecting handler:%v , got:%v", i, tt.r, tt.h, handler)
|
||||
}
|
||||
if !stringSliceEqual(tt.k, paramKeys) {
|
||||
t.Errorf("input [%d]: find '%s' expecting paramKeys:(%d)%v , got:(%d)%v", i, tt.r, len(tt.k), tt.k, len(paramKeys), paramKeys)
|
||||
}
|
||||
if !stringSliceEqual(tt.v, paramValues) {
|
||||
t.Errorf("input [%d]: find '%s' expecting paramValues:(%d)%v , got:(%d)%v", i, tt.r, len(tt.v), tt.v, len(paramValues), paramValues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeFindPattern(t *testing.T) {
|
||||
hStub1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub3 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
tr := &node{}
|
||||
tr.InsertRoute(mGET, "/pages/*", hStub1)
|
||||
tr.InsertRoute(mGET, "/articles/{id}/*", hStub2)
|
||||
tr.InsertRoute(mGET, "/articles/{slug}/{uid}/*", hStub3)
|
||||
|
||||
if tr.findPattern("/pages") != false {
|
||||
t.Errorf("find /pages failed")
|
||||
}
|
||||
if tr.findPattern("/pages*") != false {
|
||||
t.Errorf("find /pages* failed - should be nil")
|
||||
}
|
||||
if tr.findPattern("/pages/*") == false {
|
||||
t.Errorf("find /pages/* failed")
|
||||
}
|
||||
if tr.findPattern("/articles/{id}/*") == false {
|
||||
t.Errorf("find /articles/{id}/* failed")
|
||||
}
|
||||
if tr.findPattern("/articles/{something}/*") == false {
|
||||
t.Errorf("find /articles/{something}/* failed")
|
||||
}
|
||||
if tr.findPattern("/articles/{slug}/{uid}/*") == false {
|
||||
t.Errorf("find /articles/{slug}/{uid}/* failed")
|
||||
}
|
||||
}
|
||||
|
||||
func debugPrintTree(parent int, i int, n *node, label byte) bool {
|
||||
numEdges := 0
|
||||
for _, nds := range n.children {
|
||||
numEdges += len(nds)
|
||||
}
|
||||
|
||||
// if n.handlers != nil {
|
||||
// log.Printf("[node %d parent:%d] typ:%d prefix:%s label:%s tail:%s numEdges:%d isLeaf:%v handler:%v pat:%s keys:%v\n", i, parent, n.typ, n.prefix, string(label), string(n.tail), numEdges, n.isLeaf(), n.handlers, n.pattern, n.paramKeys)
|
||||
// } else {
|
||||
// log.Printf("[node %d parent:%d] typ:%d prefix:%s label:%s tail:%s numEdges:%d isLeaf:%v pat:%s keys:%v\n", i, parent, n.typ, n.prefix, string(label), string(n.tail), numEdges, n.isLeaf(), n.pattern, n.paramKeys)
|
||||
// }
|
||||
if n.endpoints != nil {
|
||||
log.Printf("[node %d parent:%d] typ:%d prefix:%s label:%s tail:%s numEdges:%d isLeaf:%v handler:%v\n", i, parent, n.typ, n.prefix, string(label), string(n.tail), numEdges, n.isLeaf(), n.endpoints)
|
||||
} else {
|
||||
log.Printf("[node %d parent:%d] typ:%d prefix:%s label:%s tail:%s numEdges:%d isLeaf:%v\n", i, parent, n.typ, n.prefix, string(label), string(n.tail), numEdges, n.isLeaf())
|
||||
}
|
||||
parent = i
|
||||
for _, nds := range n.children {
|
||||
for _, e := range nds {
|
||||
i++
|
||||
if debugPrintTree(parent, i, e, e.label) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func stringSliceEqual(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if b[i] != a[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func BenchmarkTreeGet(b *testing.B) {
|
||||
h1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
h2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
tr := &node{}
|
||||
tr.InsertRoute(mGET, "/", h1)
|
||||
tr.InsertRoute(mGET, "/ping", h2)
|
||||
tr.InsertRoute(mGET, "/pingall", h2)
|
||||
tr.InsertRoute(mGET, "/ping/{id}", h2)
|
||||
tr.InsertRoute(mGET, "/ping/{id}/woop", h2)
|
||||
tr.InsertRoute(mGET, "/ping/{id}/{opt}", h2)
|
||||
tr.InsertRoute(mGET, "/pinggggg", h2)
|
||||
tr.InsertRoute(mGET, "/hello", h1)
|
||||
|
||||
mctx := NewRouteContext()
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
mctx.Reset()
|
||||
tr.FindRoute(mctx, mGET, "/ping/123/456")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalker(t *testing.T) {
|
||||
r := bigMux()
|
||||
|
||||
// Walk the muxBig router tree.
|
||||
if err := Walk(r, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
|
||||
t.Logf("%v %v", method, route)
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue