vendor dependencies with dep

This commit is contained in:
dhax 2017-09-25 20:20:52 +02:00
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
View file

@ -0,0 +1,3 @@
.idea
*.sw?
.vscode

19
vendor/github.com/go-chi/chi/.travis.yml generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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

View 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
}

View file

@ -0,0 +1 @@
Notessszzz

View 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
View 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()
}

View 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
View 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
View 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
View 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
View 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
View 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

View 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)
}

View 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"))
}

View 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"))
}

View 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:"-"`
}

View 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
View 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)
})
}

View 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}
}

View 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}
}

View 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
View 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
View 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
View 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
}

View 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)
}

View 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
View 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
View 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
View 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)
})
}

View 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
View 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
View 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
View 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
}

View 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)
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}
}

View 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
View 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
View 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
View 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
View 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{}

View 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
}

View 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
View 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

File diff suppressed because it is too large Load diff

19
vendor/github.com/go-chi/chi/testdata/cert.pem generated vendored Normal file
View 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
View 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
View 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
View 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)
}
}