Browse Source
Introduce go chi web framework as frontend of macaron, so that we can move routes from macaron to chi step by step (#7420)
Introduce go chi web framework as frontend of macaron, so that we can move routes from macaron to chi step by step (#7420)
* When route cannot be found on chi, go to macaron * Stick chi version to 1.5.0 * Follow router log settingmj-v1.14.3
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 4796 additions and 249 deletions
-
2.golangci.yml
-
16cmd/web.go
-
6contrib/pr/checkout.go
-
1go.mod
-
7go.sum
-
8integrations/create_no_session_test.go
-
2integrations/git_helper_for_declarative_test.go
-
12integrations/integration_test.go
-
12integrations/oauth_test.go
-
6modules/public/dynamic.go
-
74modules/public/public.go
-
5modules/public/static.go
-
260routers/routes/chi.go
-
187routers/routes/macaron.go
-
3vendor/github.com/go-chi/chi/.gitignore
-
20vendor/github.com/go-chi/chi/.travis.yml
-
190vendor/github.com/go-chi/chi/CHANGELOG.md
-
31vendor/github.com/go-chi/chi/CONTRIBUTING.md
-
20vendor/github.com/go-chi/chi/LICENSE
-
496vendor/github.com/go-chi/chi/README.md
-
49vendor/github.com/go-chi/chi/chain.go
-
134vendor/github.com/go-chi/chi/chi.go
-
172vendor/github.com/go-chi/chi/context.go
-
3vendor/github.com/go-chi/chi/go.mod
-
32vendor/github.com/go-chi/chi/middleware/basic_auth.go
-
399vendor/github.com/go-chi/chi/middleware/compress.go
-
51vendor/github.com/go-chi/chi/middleware/content_charset.go
-
34vendor/github.com/go-chi/chi/middleware/content_encoding.go
-
51vendor/github.com/go-chi/chi/middleware/content_type.go
-
39vendor/github.com/go-chi/chi/middleware/get_head.go
-
26vendor/github.com/go-chi/chi/middleware/heartbeat.go
-
155vendor/github.com/go-chi/chi/middleware/logger.go
-
23vendor/github.com/go-chi/chi/middleware/middleware.go
-
58vendor/github.com/go-chi/chi/middleware/nocache.go
-
55vendor/github.com/go-chi/chi/middleware/profiler.go
-
54vendor/github.com/go-chi/chi/middleware/realip.go
-
192vendor/github.com/go-chi/chi/middleware/recoverer.go
-
96vendor/github.com/go-chi/chi/middleware/request_id.go
-
160vendor/github.com/go-chi/chi/middleware/route_headers.go
-
56vendor/github.com/go-chi/chi/middleware/strip.go
-
63vendor/github.com/go-chi/chi/middleware/terminal.go
-
132vendor/github.com/go-chi/chi/middleware/throttle.go
-
49vendor/github.com/go-chi/chi/middleware/timeout.go
-
72vendor/github.com/go-chi/chi/middleware/url_format.go
-
17vendor/github.com/go-chi/chi/middleware/value.go
-
180vendor/github.com/go-chi/chi/middleware/wrap_writer.go
-
466vendor/github.com/go-chi/chi/mux.go
-
865vendor/github.com/go-chi/chi/tree.go
-
4vendor/modules.txt
@ -0,0 +1,260 @@ |
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|||
// Use of this source code is governed by a MIT-style
|
|||
// license that can be found in the LICENSE file.
|
|||
|
|||
package routes |
|||
|
|||
import ( |
|||
"bytes" |
|||
"errors" |
|||
"fmt" |
|||
"io" |
|||
"net/http" |
|||
"os" |
|||
"path" |
|||
"strings" |
|||
"text/template" |
|||
"time" |
|||
|
|||
"code.gitea.io/gitea/modules/log" |
|||
"code.gitea.io/gitea/modules/public" |
|||
"code.gitea.io/gitea/modules/setting" |
|||
"code.gitea.io/gitea/modules/storage" |
|||
|
|||
"github.com/go-chi/chi" |
|||
"github.com/go-chi/chi/middleware" |
|||
) |
|||
|
|||
type routerLoggerOptions struct { |
|||
req *http.Request |
|||
Identity *string |
|||
Start *time.Time |
|||
ResponseWriter http.ResponseWriter |
|||
} |
|||
|
|||
// SignedUserName returns signed user's name via context
|
|||
// FIXME currently no any data stored on gin.Context but macaron.Context, so this will
|
|||
// return "" before we remove macaron totally
|
|||
func SignedUserName(req *http.Request) string { |
|||
if v, ok := req.Context().Value("SignedUserName").(string); ok { |
|||
return v |
|||
} |
|||
return "" |
|||
} |
|||
|
|||
func setupAccessLogger(c chi.Router) { |
|||
logger := log.GetLogger("access") |
|||
|
|||
logTemplate, _ := template.New("log").Parse(setting.AccessLogTemplate) |
|||
c.Use(func(next http.Handler) http.Handler { |
|||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { |
|||
start := time.Now() |
|||
next.ServeHTTP(w, req) |
|||
identity := "-" |
|||
if val := SignedUserName(req); val != "" { |
|||
identity = val |
|||
} |
|||
rw := w |
|||
|
|||
buf := bytes.NewBuffer([]byte{}) |
|||
err := logTemplate.Execute(buf, routerLoggerOptions{ |
|||
req: req, |
|||
Identity: &identity, |
|||
Start: &start, |
|||
ResponseWriter: rw, |
|||
}) |
|||
if err != nil { |
|||
log.Error("Could not set up macaron access logger: %v", err.Error()) |
|||
} |
|||
|
|||
err = logger.SendLog(log.INFO, "", "", 0, buf.String(), "") |
|||
if err != nil { |
|||
log.Error("Could not set up macaron access logger: %v", err.Error()) |
|||
} |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
// LoggerHandler is a handler that will log the routing to the default gitea log
|
|||
func LoggerHandler(level log.Level) func(next http.Handler) http.Handler { |
|||
return func(next http.Handler) http.Handler { |
|||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { |
|||
start := time.Now() |
|||
|
|||
_ = log.GetLogger("router").Log(0, level, "Started %s %s for %s", log.ColoredMethod(req.Method), req.RequestURI, req.RemoteAddr) |
|||
|
|||
next.ServeHTTP(w, req) |
|||
|
|||
ww := middleware.NewWrapResponseWriter(w, req.ProtoMajor) |
|||
|
|||
status := ww.Status() |
|||
_ = log.GetLogger("router").Log(0, level, "Completed %s %s %v %s in %v", log.ColoredMethod(req.Method), req.RequestURI, log.ColoredStatus(status), log.ColoredStatus(status, http.StatusText(status)), log.ColoredTime(time.Since(start))) |
|||
}) |
|||
} |
|||
} |
|||
|
|||
// Recovery returns a middleware that recovers from any panics and writes a 500 and a log if so.
|
|||
// Although similar to macaron.Recovery() the main difference is that this error will be created
|
|||
// with the gitea 500 page.
|
|||
func Recovery() func(next http.Handler) http.Handler { |
|||
return func(next http.Handler) http.Handler { |
|||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { |
|||
defer func() { |
|||
if err := recover(); err != nil { |
|||
combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, string(log.Stack(2))) |
|||
http.Error(w, combinedErr, 500) |
|||
} |
|||
}() |
|||
|
|||
next.ServeHTTP(w, req) |
|||
}) |
|||
} |
|||
} |
|||
|
|||
func storageHandler(storageSetting setting.Storage, prefix string, objStore storage.ObjectStorage) func(next http.Handler) http.Handler { |
|||
return func(next http.Handler) http.Handler { |
|||
if storageSetting.ServeDirect { |
|||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { |
|||
if req.Method != "GET" && req.Method != "HEAD" { |
|||
next.ServeHTTP(w, req) |
|||
return |
|||
} |
|||
|
|||
if !strings.HasPrefix(req.RequestURI, "/"+prefix) { |
|||
next.ServeHTTP(w, req) |
|||
return |
|||
} |
|||
|
|||
rPath := strings.TrimPrefix(req.RequestURI, "/"+prefix) |
|||
u, err := objStore.URL(rPath, path.Base(rPath)) |
|||
if err != nil { |
|||
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) { |
|||
log.Warn("Unable to find %s %s", prefix, rPath) |
|||
http.Error(w, "file not found", 404) |
|||
return |
|||
} |
|||
log.Error("Error whilst getting URL for %s %s. Error: %v", prefix, rPath, err) |
|||
http.Error(w, fmt.Sprintf("Error whilst getting URL for %s %s", prefix, rPath), 500) |
|||
return |
|||
} |
|||
http.Redirect( |
|||
w, |
|||
req, |
|||
u.String(), |
|||
301, |
|||
) |
|||
}) |
|||
} |
|||
|
|||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { |
|||
if req.Method != "GET" && req.Method != "HEAD" { |
|||
next.ServeHTTP(w, req) |
|||
return |
|||
} |
|||
|
|||
if !strings.HasPrefix(req.RequestURI, "/"+prefix) { |
|||
next.ServeHTTP(w, req) |
|||
return |
|||
} |
|||
|
|||
rPath := strings.TrimPrefix(req.RequestURI, "/"+prefix) |
|||
rPath = strings.TrimPrefix(rPath, "/") |
|||
//If we have matched and access to release or issue
|
|||
fr, err := objStore.Open(rPath) |
|||
if err != nil { |
|||
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) { |
|||
log.Warn("Unable to find %s %s", prefix, rPath) |
|||
http.Error(w, "file not found", 404) |
|||
return |
|||
} |
|||
log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err) |
|||
http.Error(w, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath), 500) |
|||
return |
|||
} |
|||
defer fr.Close() |
|||
|
|||
_, err = io.Copy(w, fr) |
|||
if err != nil { |
|||
log.Error("Error whilst rendering %s %s. Error: %v", prefix, rPath, err) |
|||
http.Error(w, fmt.Sprintf("Error whilst rendering %s %s", prefix, rPath), 500) |
|||
return |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
// NewChi creates a chi Router
|
|||
func NewChi() chi.Router { |
|||
c := chi.NewRouter() |
|||
if !setting.DisableRouterLog && setting.RouterLogLevel != log.NONE { |
|||
if log.GetLogger("router").GetLevel() <= setting.RouterLogLevel { |
|||
c.Use(LoggerHandler(setting.RouterLogLevel)) |
|||
} |
|||
} |
|||
c.Use(Recovery()) |
|||
if setting.EnableAccessLog { |
|||
setupAccessLogger(c) |
|||
} |
|||
if setting.ProdMode { |
|||
log.Warn("ProdMode ignored") |
|||
} |
|||
|
|||
c.Use(public.Custom( |
|||
&public.Options{ |
|||
SkipLogging: setting.DisableRouterLog, |
|||
ExpiresAfter: time.Hour * 6, |
|||
}, |
|||
)) |
|||
c.Use(public.Static( |
|||
&public.Options{ |
|||
Directory: path.Join(setting.StaticRootPath, "public"), |
|||
SkipLogging: setting.DisableRouterLog, |
|||
ExpiresAfter: time.Hour * 6, |
|||
}, |
|||
)) |
|||
|
|||
c.Use(storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars)) |
|||
c.Use(storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars)) |
|||
|
|||
return c |
|||
} |
|||
|
|||
// RegisterInstallRoute registers the install routes
|
|||
func RegisterInstallRoute(c chi.Router) { |
|||
m := NewMacaron() |
|||
RegisterMacaronInstallRoute(m) |
|||
|
|||
c.NotFound(func(w http.ResponseWriter, req *http.Request) { |
|||
m.ServeHTTP(w, req) |
|||
}) |
|||
|
|||
c.MethodNotAllowed(func(w http.ResponseWriter, req *http.Request) { |
|||
m.ServeHTTP(w, req) |
|||
}) |
|||
} |
|||
|
|||
// RegisterRoutes registers gin routes
|
|||
func RegisterRoutes(c chi.Router) { |
|||
// for health check
|
|||
c.Head("/", func(w http.ResponseWriter, req *http.Request) { |
|||
w.WriteHeader(http.StatusOK) |
|||
}) |
|||
|
|||
// robots.txt
|
|||
if setting.HasRobotsTxt { |
|||
c.Get("/robots.txt", func(w http.ResponseWriter, req *http.Request) { |
|||
http.ServeFile(w, req, path.Join(setting.CustomPath, "robots.txt")) |
|||
}) |
|||
} |
|||
|
|||
m := NewMacaron() |
|||
RegisterMacaronRoutes(m) |
|||
|
|||
c.NotFound(func(w http.ResponseWriter, req *http.Request) { |
|||
m.ServeHTTP(w, req) |
|||
}) |
|||
|
|||
c.MethodNotAllowed(func(w http.ResponseWriter, req *http.Request) { |
|||
m.ServeHTTP(w, req) |
|||
}) |
|||
} |
@ -0,0 +1,3 @@ |
|||
.idea |
|||
*.sw? |
|||
.vscode |
@ -0,0 +1,20 @@ |
|||
language: go |
|||
|
|||
go: |
|||
- 1.10.x |
|||
- 1.11.x |
|||
- 1.12.x |
|||
- 1.13.x |
|||
- 1.14.x |
|||
|
|||
script: |
|||
- go get -d -t ./... |
|||
- go vet ./... |
|||
- go test ./... |
|||
- > |
|||
go_version=$(go version); |
|||
if [ ${go_version:13:4} = "1.12" ]; then |
|||
go get -u golang.org/x/tools/cmd/goimports; |
|||
goimports -d -e ./ | grep '.*' && { echo; echo "Aborting due to non-empty goimports output."; exit 1; } || :; |
|||
fi |
|||
|
@ -0,0 +1,190 @@ |
|||
# Changelog |
|||
|
|||
## v4.1.2 (2020-06-02) |
|||
|
|||
- fix that handles MethodNotAllowed with path variables, thank you @caseyhadden for your contribution |
|||
- fix to replace nested wildcards correctly in RoutePattern, thank you @@unmultimedio for your contribution |
|||
- History of changes: see https://github.com/go-chi/chi/compare/v4.1.1...v4.1.2 |
|||
|
|||
|
|||
## v4.1.1 (2020-04-16) |
|||
|
|||
- fix for issue https://github.com/go-chi/chi/issues/411 which allows for overlapping regexp |
|||
route to the correct handler through a recursive tree search, thanks to @Jahaja for the PR/fix! |
|||
- new middleware.RouteHeaders as a simple router for request headers with wildcard support |
|||
- History of changes: see https://github.com/go-chi/chi/compare/v4.1.0...v4.1.1 |
|||
|
|||
|
|||
## v4.1.0 (2020-04-1) |
|||
|
|||
- middleware.LogEntry: Write method on interface now passes the response header |
|||
and an extra interface type useful for custom logger implementations. |
|||
- middleware.WrapResponseWriter: minor fix |
|||
- middleware.Recoverer: a bit prettier |
|||
- History of changes: see https://github.com/go-chi/chi/compare/v4.0.4...v4.1.0 |
|||
|
|||
|
|||
## v4.0.4 (2020-03-24) |
|||
|
|||
- middleware.Recoverer: new pretty stack trace printing (https://github.com/go-chi/chi/pull/496) |
|||
- a few minor improvements and fixes |
|||
- History of changes: see https://github.com/go-chi/chi/compare/v4.0.3...v4.0.4 |
|||
|
|||
|
|||
## v4.0.3 (2020-01-09) |
|||
|
|||
- core: fix regexp routing to include default value when param is not matched |
|||
- middleware: rewrite of middleware.Compress |
|||
- middleware: suppress http.ErrAbortHandler in middleware.Recoverer |
|||
- History of changes: see https://github.com/go-chi/chi/compare/v4.0.2...v4.0.3 |
|||
|
|||
|
|||
## v4.0.2 (2019-02-26) |
|||
|
|||
- Minor fixes |
|||
- History of changes: see https://github.com/go-chi/chi/compare/v4.0.1...v4.0.2 |
|||
|
|||
|
|||
## v4.0.1 (2019-01-21) |
|||
|
|||
- Fixes issue with compress middleware: #382 #385 |
|||
- History of changes: see https://github.com/go-chi/chi/compare/v4.0.0...v4.0.1 |
|||
|
|||
|
|||
## v4.0.0 (2019-01-10) |
|||
|
|||
- chi v4 requires Go 1.10.3+ (or Go 1.9.7+) - we have deprecated support for Go 1.7 and 1.8 |
|||
- router: respond with 404 on router with no routes (#362) |
|||
- router: additional check to ensure wildcard is at the end of a url pattern (#333) |
|||
- middleware: deprecate use of http.CloseNotifier (#347) |
|||
- middleware: fix RedirectSlashes to include query params on redirect (#334) |
|||
- History of changes: see https://github.com/go-chi/chi/compare/v3.3.4...v4.0.0 |
|||
|
|||
|
|||
## v3.3.4 (2019-01-07) |
|||
|
|||
- Minor middleware improvements. No changes to core library/router. Moving v3 into its |
|||
- own branch as a version of chi for Go 1.7, 1.8, 1.9, 1.10, 1.11 |
|||
- History of changes: see https://github.com/go-chi/chi/compare/v3.3.3...v3.3.4 |
|||
|
|||
|
|||
## v3.3.3 (2018-08-27) |
|||
|
|||
- Minor release |
|||
- See https://github.com/go-chi/chi/compare/v3.3.2...v3.3.3 |
|||
|
|||
|
|||
## v3.3.2 (2017-12-22) |
|||
|
|||
- Support to route trailing slashes on mounted sub-routers (#281) |
|||
- middleware: new `ContentCharset` to check matching charsets. Thank you |
|||
@csucu for your community contribution! |
|||
|
|||
|
|||
## v3.3.1 (2017-11-20) |
|||
|
|||
- middleware: new `AllowContentType` handler for explicit whitelist of accepted request Content-Types |
|||
- middleware: new `SetHeader` handler for short-hand middleware to set a response header key/value |
|||
- Minor bug fixes |
|||
|
|||
|
|||
## v3.3.0 (2017-10-10) |
|||
|
|||
- New chi.RegisterMethod(method) to add support for custom HTTP methods, see _examples/custom-method for usage |
|||
- Deprecated LINK and UNLINK methods from the default list, please use `chi.RegisterMethod("LINK")` and `chi.RegisterMethod("UNLINK")` in an `init()` function |
|||
|
|||
|
|||
## v3.2.1 (2017-08-31) |
|||
|
|||
- Add new `Match(rctx *Context, method, path string) bool` method to `Routes` interface |
|||
and `Mux`. Match searches the mux's routing tree for a handler that matches the method/path |
|||
- Add new `RouteMethod` to `*Context` |
|||
- Add new `Routes` pointer to `*Context` |
|||
- Add new `middleware.GetHead` to route missing HEAD requests to GET handler |
|||
- Updated benchmarks (see README) |
|||
|
|||
|
|||
## v3.1.5 (2017-08-02) |
|||
|
|||
- Setup golint and go vet for the project |
|||
- As per golint, we've redefined `func ServerBaseContext(h http.Handler, baseCtx context.Context) http.Handler` |
|||
to `func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler` |
|||
|
|||
|
|||
## v3.1.0 (2017-07-10) |
|||
|
|||
- Fix a few minor issues after v3 release |
|||
- Move `docgen` sub-pkg to https://github.com/go-chi/docgen |
|||
- Move `render` sub-pkg to https://github.com/go-chi/render |
|||
- Add new `URLFormat` handler to chi/middleware sub-pkg to make working with url mime |
|||
suffixes easier, ie. parsing `/articles/1.json` and `/articles/1.xml`. See comments in |
|||
https://github.com/go-chi/chi/blob/master/middleware/url_format.go for example usage. |
|||
|
|||
|
|||
## v3.0.0 (2017-06-21) |
|||
|
|||
- Major update to chi library with many exciting updates, but also some *breaking changes* |
|||
- URL parameter syntax changed from `/:id` to `/{id}` for even more flexible routing, such as |
|||
`/articles/{month}-{day}-{year}-{slug}`, `/articles/{id}`, and `/articles/{id}.{ext}` on the |
|||
same router |
|||
- Support for regexp for routing patterns, in the form of `/{paramKey:regExp}` for example: |
|||
`r.Get("/articles/{name:[a-z]+}", h)` and `chi.URLParam(r, "name")` |
|||
- Add `Method` and `MethodFunc` to `chi.Router` to allow routing definitions such as |
|||
`r.Method("GET", "/", h)` which provides a cleaner interface for custom handlers like |
|||
in `_examples/custom-handler` |
|||
- Deprecating `mux#FileServer` helper function. Instead, we encourage users to create their |
|||
own using file handler with the stdlib, see `_examples/fileserver` for an example |
|||
- Add support for LINK/UNLINK http methods via `r.Method()` and `r.MethodFunc()` |
|||
- Moved the chi project to its own organization, to allow chi-related community packages to |
|||
be easily discovered and supported, at: https://github.com/go-chi |
|||
- *NOTE:* please update your import paths to `"github.com/go-chi/chi"` |
|||
- *NOTE:* chi v2 is still available at https://github.com/go-chi/chi/tree/v2 |
|||
|
|||
|
|||
## v2.1.0 (2017-03-30) |
|||
|
|||
- Minor improvements and update to the chi core library |
|||
- Introduced a brand new `chi/render` sub-package to complete the story of building |
|||
APIs to offer a pattern for managing well-defined request / response payloads. Please |
|||
check out the updated `_examples/rest` example for how it works. |
|||
- Added `MethodNotAllowed(h http.HandlerFunc)` to chi.Router interface |
|||
|
|||
|
|||
## v2.0.0 (2017-01-06) |
|||
|
|||
- After many months of v2 being in an RC state with many companies and users running it in |
|||
production, the inclusion of some improvements to the middlewares, we are very pleased to |
|||
announce v2.0.0 of chi. |
|||
|
|||
|
|||
## v2.0.0-rc1 (2016-07-26) |
|||
|
|||
- Huge update! chi v2 is a large refactor targetting Go 1.7+. As of Go 1.7, the popular |
|||
community `"net/context"` package has been included in the standard library as `"context"` and |
|||
utilized by `"net/http"` and `http.Request` to managing deadlines, cancelation signals and other |
|||
request-scoped values. We're very excited about the new context addition and are proud to |
|||
introduce chi v2, a minimal and powerful routing package for building large HTTP services, |
|||
with zero external dependencies. Chi focuses on idiomatic design and encourages the use of |
|||
stdlib HTTP handlers and middlwares. |
|||
- chi v2 deprecates its `chi.Handler` interface and requires `http.Handler` or `http.HandlerFunc` |
|||
- chi v2 stores URL routing parameters and patterns in the standard request context: `r.Context()` |
|||
- chi v2 lower-level routing context is accessible by `chi.RouteContext(r.Context()) *chi.Context`, |
|||
which provides direct access to URL routing parameters, the routing path and the matching |
|||
routing patterns. |
|||
- Users upgrading from chi v1 to v2, need to: |
|||
1. Update the old chi.Handler signature, `func(ctx context.Context, w http.ResponseWriter, r *http.Request)` to |
|||
the standard http.Handler: `func(w http.ResponseWriter, r *http.Request)` |
|||
2. Use `chi.URLParam(r *http.Request, paramKey string) string` |
|||
or `URLParamFromCtx(ctx context.Context, paramKey string) string` to access a url parameter value |
|||
|
|||
|
|||
## v1.0.0 (2016-07-01) |
|||
|
|||
- Released chi v1 stable https://github.com/go-chi/chi/tree/v1.0.0 for Go 1.6 and older. |
|||
|
|||
|
|||
## v0.9.0 (2016-03-31) |
|||
|
|||
- Reuse context objects via sync.Pool for zero-allocation routing [#33](https://github.com/go-chi/chi/pull/33) |
|||
- BREAKING NOTE: due to subtle API changes, previously `chi.URLParams(ctx)["id"]` used to access url parameters |
|||
has changed to: `chi.URLParam(ctx, "id")` |
@ -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 |
@ -0,0 +1,20 @@ |
|||
Copyright (c) 2015-present Peter Kieltyka (https://github.com/pkieltyka), Google Inc. |
|||
|
|||
MIT License |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy of |
|||
this software and associated documentation files (the "Software"), to deal in |
|||
the Software without restriction, including without limitation the rights to |
|||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of |
|||
the Software, and to permit persons to whom the Software is furnished to do so, |
|||
subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
|||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
|||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
|||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
|||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,496 @@ |
|||
# <img alt="chi" src="https://cdn.rawgit.com/go-chi/chi/master/_examples/chi.svg" width="220" /> |
|||
|
|||
|
|||
[![GoDoc Widget]][GoDoc] [![Travis Widget]][Travis] |
|||
|
|||
`chi` is a lightweight, idiomatic and composable router for building Go HTTP services. It's |
|||
especially good at helping you write large REST API services that are kept maintainable as your |
|||
project grows and changes. `chi` is built on the new `context` package introduced in Go 1.7 to |
|||
handle signaling, cancelation and request-scoped values across a handler chain. |
|||
|
|||
The focus of the project has been to seek out an elegant and comfortable design for writing |
|||
REST API servers, written during the development of the Pressly API service that powers our |
|||
public API service, which in turn powers all of our client-side applications. |
|||
|
|||
The key considerations of chi's design are: project structure, maintainability, standard http |
|||
handlers (stdlib-only), developer productivity, and deconstructing a large system into many small |
|||
parts. The core router `github.com/go-chi/chi` is quite small (less than 1000 LOC), but we've also |
|||
included some useful/optional subpackages: [middleware](/middleware), [render](https://github.com/go-chi/render) and [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too! |
|||
|
|||
## Install |
|||
|
|||
`go get -u github.com/go-chi/chi` |
|||
|
|||
|
|||
## Features |
|||
|
|||
* **Lightweight** - cloc'd in ~1000 LOC for the chi router |
|||
* **Fast** - yes, see [benchmarks](#benchmarks) |
|||
* **100% compatible with net/http** - use any http or middleware pkg in the ecosystem that is also compatible with `net/http` |
|||
* **Designed for modular/composable APIs** - middlewares, inline middlewares, route groups and subrouter mounting |
|||
* **Context control** - built on new `context` package, providing value chaining, cancellations and timeouts |
|||
* **Robust** - in production at Pressly, CloudFlare, Heroku, 99Designs, and many others (see [discussion](https://github.com/go-chi/chi/issues/91)) |
|||
* **Doc generation** - `docgen` auto-generates routing documentation from your source to JSON or Markdown |
|||
* **No external dependencies** - plain ol' Go stdlib + net/http |
|||
|
|||
|
|||
## Examples |
|||
|
|||
See [_examples/](https://github.com/go-chi/chi/blob/master/_examples/) for a variety of examples. |
|||
|
|||
|
|||
**As easy as:** |
|||
|
|||
```go |
|||
package main |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
"github.com/go-chi/chi" |
|||
"github.com/go-chi/chi/middleware" |
|||
) |
|||
|
|||
func main() { |
|||
r := chi.NewRouter() |
|||
r.Use(middleware.Logger) |
|||
r.Get("/", func(w http.ResponseWriter, r *http.Request) { |
|||
w.Write([]byte("welcome")) |
|||
}) |
|||
http.ListenAndServe(":3000", r) |
|||
} |
|||
``` |
|||
|
|||
**REST Preview:** |
|||
|
|||
Here is a little preview of how routing looks like with chi. Also take a look at the generated routing docs |
|||
in JSON ([routes.json](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.json)) and in |
|||
Markdown ([routes.md](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.md)). |
|||
|
|||
I highly recommend reading the source of the [examples](https://github.com/go-chi/chi/blob/master/_examples/) listed |
|||
above, they will show you all the features of chi and serve as a good form of documentation. |
|||
|
|||
```go |
|||
import ( |
|||
//... |
|||
"context" |
|||
"github.com/go-chi/chi" |
|||
"github.com/go-chi/chi/middleware" |
|||
) |
|||
|
|||
func main() { |
|||
r := chi.NewRouter() |
|||
|
|||
// A good base middleware stack |
|||
r.Use(middleware.RequestID) |
|||
r.Use(middleware.RealIP) |
|||
r.Use(middleware.Logger) |
|||
r.Use(middleware.Recoverer) |
|||
|
|||
// Set a timeout value on the request context (ctx), that will signal |
|||
// through ctx.Done() that the request has timed out and further |
|||
// processing should be stopped. |
|||
r.Use(middleware.Timeout(60 * time.Second)) |
|||
|
|||
r.Get("/", func(w http.ResponseWriter, r *http.Request) { |
|||
w.Write([]byte("hi")) |
|||
}) |
|||
|
|||
// RESTy routes for "articles" resource |
|||
r.Route("/articles", func(r chi.Router) { |
|||
r.With(paginate).Get("/", listArticles) // GET /articles |
|||
r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate) // GET /articles/01-16-2017 |
|||
|
|||
r.Post("/", createArticle) // POST /articles |
|||
r.Get("/search", searchArticles) // GET /articles/search |
|||
|
|||
// Regexp url parameters: |
|||
r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug) // GET /articles/home-is-toronto |
|||
|
|||
// Subrouters: |
|||
r.Route("/{articleID}", func(r chi.Router) { |
|||
r.Use(ArticleCtx) |
|||
r.Get("/", getArticle) // GET /articles/123 |
|||
r.Put("/", updateArticle) // PUT /articles/123 |
|||
r.Delete("/", deleteArticle) // DELETE /articles/123 |
|||
}) |
|||
}) |
|||
|
|||
// Mount the admin sub-router |
|||
r.Mount("/admin", adminRouter()) |
|||
|
|||
http.ListenAndServe(":3333", r) |
|||
} |
|||
|
|||
func ArticleCtx(next http.Handler) http.Handler { |
|||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
|||
articleID := chi.URLParam(r, "articleID") |
|||
article, err := dbGetArticle(articleID) |
|||
if err != nil { |
|||
http.Error(w, http.StatusText(404), 404) |
|||
return |
|||
} |
|||
ctx := context.WithValue(r.Context(), "article", article) |
|||
next.ServeHTTP(w, r.WithContext(ctx)) |
|||
}) |
|||
} |
|||
|
|||
func getArticle(w http.ResponseWriter, r *http.Request) { |
|||
ctx := r.Context() |
|||
article, ok := ctx.Value("article").(*Article) |
|||
if !ok { |
|||
http.Error(w, http.StatusText(422), 422) |
|||
return |
|||
} |
|||
w.Write([]byte(fmt.Sprintf("title:%s", article.Title))) |
|||
} |
|||
|
|||
// A completely separate router for administrator routes |
|||
func adminRouter() http.Handler { |
|||
r := chi.NewRouter() |
|||
r.Use(AdminOnly) |
|||
r.Get("/", adminIndex) |
|||
r.Get("/accounts", adminListAccounts) |
|||
return r |
|||
} |
|||
|
|||
func AdminOnly(next http.Handler) http.Handler { |
|||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
|||
ctx := r.Context() |
|||
perm, ok := ctx.Value("acl.permission").(YourPermissionType) |
|||
if !ok || !perm.IsAdmin() { |
|||
http.Error(w, http.StatusText(403), 403) |
|||
return |
|||
} |
|||
next.ServeHTTP(w, r) |
|||
}) |
|||
} |
|||
``` |
|||
|
|||
|
|||
## Router interface |
|||
|
|||
chi's router is based on a kind of [Patricia Radix trie](https://en.wikipedia.org/wiki/Radix_tree). |
|||
The router is fully compatible with `net/http`. |
|||
|
|||
Built on top of the tree is the `Router` interface: |
|||
|
|||
```go |
|||
// Router consisting of the core routing methods used by chi's Mux, |
|||
// using only the standard net/http. |
|||
type Router interface { |
|||
http.Handler |
|||
Routes |
|||
|
|||
// Use appends one or more middlewares onto the Router stack. |
|||
Use(middlewares ...func(http.Handler) http.Handler) |
|||
|
|||
// With adds inline middlewares for an endpoint handler. |
|||
With(middlewares ...func(http.Handler) http.Handler) Router |
|||
|
|||
// Group adds a new inline-Router along the current routing |
|||
// path, with a fresh middleware stack for the inline-Router. |
|||
Group(fn func(r Router)) Router |
|||
|
|||
// Route mounts a sub-Router along a `pattern`` string. |
|||
Route(pattern string, fn func(r Router)) Router |
|||
|
|||
// Mount attaches another http.Handler along ./pattern/* |
|||
Mount(pattern string, h http.Handler) |
|||
|
|||
// Handle and HandleFunc adds routes for `pattern` that matches |
|||
// all HTTP methods. |
|||
Handle(pattern string, h http.Handler) |
|||
HandleFunc(pattern string, h http.HandlerFunc) |
|||
|
|||
// Method and MethodFunc adds routes for `pattern` that matches |
|||
// the `method` HTTP method. |
|||
Method(method, pattern string, h http.Handler) |
|||
MethodFunc(method, pattern string, h http.HandlerFunc) |
|||
|
|||
// HTTP-method routing along `pattern` |
|||
Connect(pattern string, h http.HandlerFunc) |
|||
Delete(pattern string, h http.HandlerFunc) |
|||
Get(pattern string, h http.HandlerFunc) |
|||
Head(pattern string, h http.HandlerFunc) |
|||
Options(pattern string, h http.HandlerFunc) |
|||
Patch(pattern string, h http.HandlerFunc) |
|||
Post(pattern string, h http.HandlerFunc) |
|||
Put(pattern string, h http.HandlerFunc) |
|||
Trace(pattern string, h http.HandlerFunc) |
|||
|
|||
// NotFound defines a handler to respond whenever a route could |
|||
// not be found. |
|||
NotFound(h http.HandlerFunc) |
|||
|
|||
// MethodNotAllowed defines a handler to respond whenever a method is |
|||
// not allowed. |
|||
MethodNotAllowed(h http.HandlerFunc) |
|||
} |
|||
|
|||
// Routes interface adds two methods for router traversal, which is also |
|||
// used by the github.com/go-chi/docgen package to generate documentation for Routers. |
|||
type Routes interface { |
|||
// Routes returns the routing tree in an easily traversable structure. |
|||
Routes() []Route |
|||
|
|||
// Middlewares returns the list of middlewares in use by the router. |
|||
Middlewares() Middlewares |
|||
|
|||
// Match searches the routing tree for a handler that matches |
|||
// the method/path - similar to routing a http request, but without |
|||
// executing the handler thereafter. |
|||
Match(rctx *Context, method, path string) bool |
|||
} |
|||
``` |
|||
|
|||
Each routing method accepts a URL `pattern` and chain of `handlers`. The URL pattern |
|||
supports named params (ie. `/users/{userID}`) and wildcards (ie. `/admin/*`). URL parameters |
|||
can be fetched at runtime by calling `chi.URLParam(r, "userID")` for named parameters |
|||
and `chi.URLParam(r, "*")` for a wildcard parameter. |
|||
|
|||
|
|||
### Middleware handlers |
|||
|
|||
chi's middlewares are just stdlib net/http middleware handlers. There is nothing special |
|||
about them, which means the router and all the tooling is designed to be compatible and |
|||
friendly with any middleware in the community. This offers much better extensibility and reuse |
|||
of packages and is at the heart of chi's purpose. |
|||
|
|||
Here is an example of a standard net/http middleware where we assign a context key `"user"` |
|||
the value of `"123"`. This middleware sets a hypothetical user identifier on the request |
|||
context and calls the next handler in the chain. |
|||
|
|||
```go |
|||
// HTTP middleware setting a value on the request context |
|||
func MyMiddleware(next http.Handler) http.Handler { |
|||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
|||
// create new context from `r` request context, and assign key `"user"` |
|||
// to value of `"123"` |
|||
ctx := context.WithValue(r.Context(), "user", "123") |
|||
|
|||
// call the next handler in the chain, passing the response writer and |
|||
// the updated request object with the new context value. |
|||
// |
|||
// note: context.Context values are nested, so any previously set |