revendor with fresh chi and cache

This commit is contained in:
Umputun
2018-02-06 01:01:47 -06:00
parent 26d67328c7
commit fc96a3b1fe
23 changed files with 534 additions and 219 deletions
+31 -1
View File
@@ -1,6 +1,36 @@
# Changelog
## v3.1.5
## 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`
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2015-present Peter Kieltyka (https://github.com/pkieltyka)
Copyright (c) 2015-present Peter Kieltyka (https://github.com/pkieltyka), Google Inc.
MIT License
+63 -42
View File
@@ -24,7 +24,7 @@ included some useful/optional subpackages: [middleware](/middleware), [render](h
## Features
* **Lightweight** - cloc'd in <1000 LOC for the chi router
* **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
@@ -109,7 +109,7 @@ func main() {
// 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)
@@ -239,6 +239,11 @@ type Routes interface {
// 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
}
```
@@ -310,32 +315,46 @@ chi comes equipped with an optional `middleware` package, providing a suite of s
`net/http` middlewares. Please note, any middleware in the ecosystem that is also compatible
with `net/http` can be used with chi's mux.
----------------------------------------------------------------------------------------------------------
| Middleware | Description |
|:---------------------|:---------------------------------------------------------------------------------
| RequestID | Injects a request ID into the context of each request. |
| RealIP | Sets a http.Request's RemoteAddr to either X-Forwarded-For or X-Real-IP. |
| Logger | Logs the start and end of each request with the elapsed processing time. |
| Recoverer | Gracefully absorb panics and prints the stack trace. |
| NoCache | Sets response headers to prevent clients from caching. |
| Timeout | Signals to the request context when the timeout deadline is reached. |
| Throttle | Puts a ceiling on the number of concurrent requests. |
| Compress | Gzip compression for clients that accept compressed responses. |
| Profiler | Easily attach net/http/pprof to your routers. |
| StripSlashes | Strip slashes on routing paths. |
| RedirectSlashes | Redirect slashes on routing paths. |
| WithValue | Short-hand middleware to set a key/value on the request context. |
| Heartbeat | Monitoring endpoint to check the servers pulse. |
----------------------------------------------------------------------------------------------------------
### Core middlewares
Other cool community net/http middlewares:
-----------------------------------------------------------------------------------------------------------
| chi/middleware Handler | description |
|:----------------------|:---------------------------------------------------------------------------------
| AllowContentType | Explicit whitelist of accepted request Content-Types |
| Compress | Gzip compression for clients that accept compressed responses |
| GetHead | Automatically route undefined HEAD requests to GET handlers |
| Heartbeat | Monitoring endpoint to check the servers pulse |
| Logger | Logs the start and end of each request with the elapsed processing time |
| NoCache | Sets response headers to prevent clients from caching |
| Profiler | Easily attach net/http/pprof to your routers |
| RealIP | Sets a http.Request's RemoteAddr to either X-Forwarded-For or X-Real-IP |
| Recoverer | Gracefully absorb panics and prints the stack trace |
| RequestID | Injects a request ID into the context of each request |
| RedirectSlashes | Redirect slashes on routing paths |
| SetHeader | Short-hand middleware to set a response header key/value |
| StripSlashes | Strip slashes on routing paths |
| Throttle | Puts a ceiling on the number of concurrent requests |
| Timeout | Signals to the request context when the timeout deadline is reached |
| URLFormat | Parse extension from url and put it on request context |
| WithValue | Short-hand middleware to set a key/value on the request context |
-----------------------------------------------------------------------------------------------------------
* [jwtauth](https://github.com/goware/jwtauth) - JWT authenticator
* [cors](https://github.com/goware/cors) - CORS middleware
* [httpcoala](https://github.com/goware/httpcoala) - Request coalescer
* [chi-authz](https://github.com/casbin/chi-authz) - Authorization middleware built on https://github.com/hsluoyz/casbin
### Auxiliary middlewares & packages
please [submit a PR](./CONTRIBUTING.md) if you'd like to include a link to a chi middleware
Please see https://github.com/go-chi for additional packages.
--------------------------------------------------------------------------------------------------------------------
| package | description |
|:---------------------------------------------------|:-------------------------------------------------------------
| [cors](https://github.com/go-chi/cors) | Cross-origin resource sharing (CORS) |
| [jwtauth](https://github.com/go-chi/jwtauth) | JWT authentication |
| [hostrouter](https://github.com/go-chi/hostrouter) | Domain/host based request routing |
| [httpcoala](https://github.com/go-chi/httpcoala) | HTTP request coalescer |
| [chi-authz](https://github.com/casbin/chi-authz) | Request ACL via https://github.com/hsluoyz/casbin |
| [phi](https://github.com/fate-lovely/phi) | Port chi to [fasthttp](https://github.com/valyala/fasthttp) |
--------------------------------------------------------------------------------------------------------------------
please [submit a PR](./CONTRIBUTING.md) if you'd like to include a link to a chi-compatible middleware
## context?
@@ -355,27 +374,29 @@ and..
The benchmark suite: https://github.com/pkieltyka/go-http-routing-benchmark
Comparison with other routers (as of June 21, 2017): https://gist.github.com/pkieltyka/c089f309abeb179cfc4deaa519956d8c
Results as of Aug 31, 2017 on Go 1.9.0
```shell
BenchmarkChi_Param 3000000 427 ns/op 304 B/op 2 allocs/op
BenchmarkChi_Param5 2000000 631 ns/op 304 B/op 2 allocs/op
BenchmarkChi_Param20 1000000 1343 ns/op 304 B/op 2 allocs/op
BenchmarkChi_ParamWrite 3000000 477 ns/op 304 B/op 2 allocs/op
BenchmarkChi_GithubStatic 3000000 452 ns/op 304 B/op 2 allocs/op
BenchmarkChi_GithubParam 2000000 616 ns/op 304 B/op 2 allocs/op
BenchmarkChi_GithubAll 10000 130637 ns/op 61716 B/op 406 allocs/op
BenchmarkChi_GPlusStatic 3000000 415 ns/op 304 B/op 2 allocs/op
BenchmarkChi_GPlusParam 3000000 465 ns/op 304 B/op 2 allocs/op
BenchmarkChi_GPlus2Params 3000000 548 ns/op 304 B/op 2 allocs/op
BenchmarkChi_GPlusAll 200000 6895 ns/op 3952 B/op 26 allocs/op
BenchmarkChi_ParseStatic 3000000 407 ns/op 304 B/op 2 allocs/op
BenchmarkChi_ParseParam 3000000 451 ns/op 304 B/op 2 allocs/op
BenchmarkChi_Parse2Params 3000000 504 ns/op 304 B/op 2 allocs/op
BenchmarkChi_ParseAll 100000 13221 ns/op 7904 B/op 52 allocs/op
BenchmarkChi_StaticAll 20000 84327 ns/op 47731 B/op 314 allocs/op
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
+31
View File
@@ -27,6 +27,32 @@
//
// See github.com/go-chi/chi/_examples/ for more in-depth examples.
//
// URL patterns allow for easy matching of path components in HTTP
// requests. The matching components can then be accessed using
// chi.URLParam(). All patterns must begin with a slash.
//
// A simple named placeholder {name} matches any sequence of characters
// up to the next / or the end of the URL. Trailing slashes on paths must
// be handled explicitly.
//
// A placeholder with a name followed by a colon allows a regular
// expression match, for example {number:\\d+}. The regular expression
// syntax is Go's normal regexp RE2 syntax, except that regular expressions
// including { or } are not supported, and / will never be
// matched. An anonymous regexp pattern is allowed, using an empty string
// before the colon in the placeholder, such as {:\\d+}
//
// The special placeholder of asterisk matches the rest of the requested
// URL. Any trailing characters in the pattern are ignored. This is the only
// placeholder which will match / characters.
//
// Examples:
// "/user/{name}" matches "/user/jsmith" but not "/user/jsmith/info" or "/user/jsmith/"
// "/user/{name}/info" matches "/user/jsmith/info"
// "/page/*" matches "/page/intro/latest"
// "/page/*/index" also matches "/page/intro/latest"
// "/date/{yyyy:\\d\\d\\d\\d}/{mm:\\d\\d}/{dd:\\d\\d}" matches "/date/2017/04/01"
//
package chi
import "net/http"
@@ -96,6 +122,11 @@ type Routes interface {
// 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
+9 -4
View File
@@ -16,9 +16,12 @@ var (
// request context to track route patterns, URL parameters and
// an optional routing path.
type Context struct {
// Routing path override used during the route search.
Routes Routes
// Routing path/method override used during the route search.
// See Mux#routeHTTP method.
RoutePath string
RoutePath string
RouteMethod string
// Routing pattern stack throughout the lifecycle of the request,
// across all connected routers. It is a record of all matching
@@ -48,9 +51,11 @@ func NewRouteContext() *Context {
return &Context{}
}
// reset a routing context to its initial state.
func (x *Context) reset() {
// 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]
+1 -1
View File
@@ -1,4 +1,4 @@
// +build go1.8
// +build go1.8 appengine
package middleware
+1 -1
View File
@@ -1,4 +1,4 @@
// +build go1.8
// +build go1.8 appengine
package middleware
+51
View File
@@ -0,0 +1,51 @@
package middleware
import (
"net/http"
"strings"
)
// ContentCharset generates a handler that writes a 415 Unsupported Media Type response if none of the charsets match.
// An empty charset will allow requests with no Content-Type header or no specified charset.
func ContentCharset(charsets ...string) func(next http.Handler) http.Handler {
for i, c := range charsets {
charsets[i] = strings.ToLower(c)
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !contentEncoding(r.Header.Get("Content-Type"), charsets...) {
w.WriteHeader(http.StatusUnsupportedMediaType)
return
}
next.ServeHTTP(w, r)
})
}
}
// Check the content encoding against a list of acceptable values.
func contentEncoding(ce string, charsets ...string) bool {
_, ce = split(strings.ToLower(ce), ";")
_, ce = split(ce, "charset=")
ce, _ = split(ce, ";")
for _, c := range charsets {
if ce == c {
return true
}
}
return false
}
// Split a string in two parts, cleaning any whitespace.
func split(str, sep string) (string, string) {
var a, b string
var parts = strings.SplitN(str, sep, 2)
a = strings.TrimSpace(parts[0])
if len(parts) == 2 {
b = strings.TrimSpace(parts[1])
}
return a, b
}
+45
View File
@@ -0,0 +1,45 @@
package middleware
import (
"net/http"
"strings"
)
// SetHeader is a convenience handler to set a response header key/value
func SetHeader(key, value string) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(key, value)
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
}
// AllowContentType enforces a whitelist of request Content-Types otherwise responds
// with a 415 Unsupported Media Type status.
func AllowContentType(contentTypes ...string) func(next http.Handler) http.Handler {
cT := []string{}
for _, t := range contentTypes {
cT = append(cT, strings.ToLower(t))
}
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
s := strings.ToLower(strings.TrimSpace(r.Header.Get("Content-Type")))
if i := strings.Index(s, ";"); i > -1 {
s = s[0:i]
}
for _, t := range cT {
if t == s {
next.ServeHTTP(w, r)
return
}
}
w.WriteHeader(http.StatusUnsupportedMediaType)
}
return http.HandlerFunc(fn)
}
}
+39
View File
@@ -0,0 +1,39 @@
package middleware
import (
"net/http"
"github.com/go-chi/chi"
)
// GetHead automatically route undefined HEAD requests to GET handlers.
func GetHead(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == "HEAD" {
rctx := chi.RouteContext(r.Context())
routePath := rctx.RoutePath
if routePath == "" {
if r.URL.RawPath != "" {
routePath = r.URL.RawPath
} else {
routePath = r.URL.Path
}
}
// Temporary routing context to look-ahead before routing the request
tctx := chi.NewRouteContext()
// Attempt to find a HEAD handler for the routing path, if not found, traverse
// the router as through its a GET route, but proceed with the request
// with the HEAD method.
if !rctx.Routes.Match(tctx, "HEAD", routePath) {
rctx.RouteMethod = "GET"
rctx.RoutePath = routePath
next.ServeHTTP(w, r)
return
}
}
next.ServeHTTP(w, r)
})
}
+27 -18
View File
@@ -16,7 +16,7 @@ var (
// DefaultLogger is called by the Logger middleware handler to log each request.
// Its made a package-level variable so that it can be reconfigured for custom
// logging configurations.
DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags)})
DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags), NoColor: false})
)
// Logger is a middleware that logs the start and end of each request, along
@@ -74,31 +74,39 @@ func WithLogEntry(r *http.Request, entry LogEntry) *http.Request {
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 *log.Logger
Logger LoggerInterface
NoColor bool
}
// NewLogEntry creates a new LogEntry for the request.
func (l *DefaultLogFormatter) NewLogEntry(r *http.Request) LogEntry {
useColor := !l.NoColor
entry := &defaultLogEntry{
DefaultLogFormatter: l,
request: r,
buf: &bytes.Buffer{},
useColor: useColor,
}
reqID := GetReqID(r.Context())
if reqID != "" {
cW(entry.buf, nYellow, "[%s] ", reqID)
cW(entry.buf, useColor, nYellow, "[%s] ", reqID)
}
cW(entry.buf, nCyan, "\"")
cW(entry.buf, bMagenta, "%s ", r.Method)
cW(entry.buf, useColor, nCyan, "\"")
cW(entry.buf, useColor, 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)
cW(entry.buf, useColor, nCyan, "%s://%s%s %s\" ", scheme, r.Host, r.RequestURI, r.Proto)
entry.buf.WriteString("from ")
entry.buf.WriteString(r.RemoteAddr)
@@ -109,33 +117,34 @@ func (l *DefaultLogFormatter) NewLogEntry(r *http.Request) LogEntry {
type defaultLogEntry struct {
*DefaultLogFormatter
request *http.Request
buf *bytes.Buffer
request *http.Request
buf *bytes.Buffer
useColor bool
}
func (l *defaultLogEntry) Write(status, bytes int, elapsed time.Duration) {
switch {
case status < 200:
cW(l.buf, bBlue, "%03d", status)
cW(l.buf, l.useColor, bBlue, "%03d", status)
case status < 300:
cW(l.buf, bGreen, "%03d", status)
cW(l.buf, l.useColor, bGreen, "%03d", status)
case status < 400:
cW(l.buf, bCyan, "%03d", status)
cW(l.buf, l.useColor, bCyan, "%03d", status)
case status < 500:
cW(l.buf, bYellow, "%03d", status)
cW(l.buf, l.useColor, bYellow, "%03d", status)
default:
cW(l.buf, bRed, "%03d", status)
cW(l.buf, l.useColor, bRed, "%03d", status)
}
cW(l.buf, bBlue, " %dB", bytes)
cW(l.buf, l.useColor, bBlue, " %dB", bytes)
l.buf.WriteString(" in ")
if elapsed < 500*time.Millisecond {
cW(l.buf, nGreen, "%s", elapsed)
cW(l.buf, l.useColor, nGreen, "%s", elapsed)
} else if elapsed < 5*time.Second {
cW(l.buf, nYellow, "%s", elapsed)
cW(l.buf, l.useColor, nYellow, "%s", elapsed)
} else {
cW(l.buf, nRed, "%s", elapsed)
cW(l.buf, l.useColor, nRed, "%s", elapsed)
}
l.Logger.Print(l.buf.String())
@@ -143,7 +152,7 @@ func (l *defaultLogEntry) Write(status, bytes int, elapsed time.Duration) {
func (l *defaultLogEntry) Panic(v interface{}, stack []byte) {
panicEntry := l.NewLogEntry(l.request).(*defaultLogEntry)
cW(panicEntry.buf, bRed, "panic: %+v", v)
cW(panicEntry.buf, l.useColor, bRed, "panic: %+v", v)
l.Logger.Print(panicEntry.buf.String())
l.Logger.Print(string(stack))
}
+1 -5
View File
@@ -29,15 +29,11 @@ func Profiler() http.Handler {
http.Redirect(w, r, r.RequestURI+"/", 301)
})
r.HandleFunc("/pprof/", pprof.Index)
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
+3 -3
View File
@@ -52,12 +52,12 @@ func init() {
}
// colorWrite
func cW(w io.Writer, color []byte, s string, args ...interface{}) {
if isTTY {
func cW(w io.Writer, useColor bool, color []byte, s string, args ...interface{}) {
if isTTY && useColor {
w.Write(color)
}
fmt.Fprintf(w, s, args...)
if isTTY {
if isTTY && useColor {
w.Write(reset)
}
}
+6
View File
@@ -83,6 +83,8 @@ type flushWriter struct {
}
func (f *flushWriter) Flush() {
f.wroteHeader = true
fl := f.basicWriter.ResponseWriter.(http.Flusher)
fl.Flush()
}
@@ -102,6 +104,8 @@ func (f *httpFancyWriter) CloseNotify() <-chan bool {
return cn.CloseNotify()
}
func (f *httpFancyWriter) Flush() {
f.wroteHeader = true
fl := f.basicWriter.ResponseWriter.(http.Flusher)
fl.Flush()
}
@@ -140,6 +144,8 @@ func (f *http2FancyWriter) CloseNotify() <-chan bool {
return cn.CloseNotify()
}
func (f *http2FancyWriter) Flush() {
f.wroteHeader = true
fl := f.basicWriter.ResponseWriter.(http.Flusher)
fl.Flush()
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build go1.8
// +build go1.8 appengine
package middleware
+54 -32
View File
@@ -35,7 +35,7 @@ type Mux struct {
handler http.Handler
// Routing context pool
pool sync.Pool
pool *sync.Pool
// Custom route not found handler
notFoundHandler http.HandlerFunc
@@ -47,7 +47,7 @@ type Mux struct {
// NewMux returns a newly initialized Mux object that implements the Router
// interface.
func NewMux() *Mux {
mux := &Mux{tree: &node{}}
mux := &Mux{tree: &node{}, pool: &sync.Pool{}}
mux.pool.New = func() interface{} {
return NewRouteContext()
}
@@ -75,7 +75,8 @@ func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 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.Reset()
rctx.Routes = mx
r = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx))
mx.handler.ServeHTTP(w, r)
mx.pool.Put(rctx)
@@ -232,7 +233,8 @@ func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router {
}
mws = append(mws, middlewares...)
im := &Mux{inline: true, parent: mx, tree: mx.tree, middlewares: mws}
im := &Mux{pool: mx.pool, inline: true, parent: mx, tree: mx.tree, middlewares: mws}
return im
}
@@ -269,7 +271,7 @@ func (mx *Mux) Route(pattern string, fn func(r Router)) Router {
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.matchPattern(pattern+"*") || mx.tree.matchPattern(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))
}
@@ -285,23 +287,13 @@ func (mx *Mux) Mount(pattern string, handler http.Handler) {
// 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 = "/"
nx := len(rctx.routeParams.Keys) - 1 // index of last param in list
if nx >= 0 && rctx.routeParams.Keys[nx] == "*" && len(rctx.routeParams.Values) > nx {
rctx.RoutePath += rctx.routeParams.Values[nx]
}
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)
mx.handle(mALL|mSTUB, pattern+"/", mountHandler)
pattern += "/"
}
@@ -317,15 +309,37 @@ func (mx *Mux) Mount(pattern string, handler http.Handler) {
}
}
// 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
}
// 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()
// 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
@@ -396,26 +410,34 @@ func (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) {
}
// Check if method is supported by chi
method, ok := methodMap[r.Method]
if rctx.RouteMethod == "" {
rctx.RouteMethod = r.Method
}
method, ok := methodMap[rctx.RouteMethod]
if !ok {
mx.MethodNotAllowedHandler().ServeHTTP(w, r)
return
}
// Find the route
hs := mx.tree.FindRoute(rctx, method, routePath)
if hs == nil {
if rctx.methodNotAllowed {
mx.MethodNotAllowedHandler().ServeHTTP(w, r)
} else {
mx.NotFoundHandler().ServeHTTP(w, r)
}
if _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil {
h.ServeHTTP(w, r)
return
}
h, _ := hs[method]
if rctx.methodNotAllowed {
mx.MethodNotAllowedHandler().ServeHTTP(w, r)
} else {
mx.NotFoundHandler().ServeHTTP(w, r)
}
}
// Serve it up
h.handler.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.
+57 -17
View File
@@ -6,44 +6,61 @@ package chi
import (
"fmt"
"math"
"net/http"
"regexp"
"sort"
"strconv"
"strings"
)
type methodTyp int
const (
mCONNECT methodTyp = 1 << iota
mSTUB methodTyp = 1 << iota
mCONNECT
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 mALL = mCONNECT | mDELETE | mGET | mHEAD |
mOPTIONS | mPATCH | mPOST | mPUT | mTRACE
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,
}
// RegisterMethod adds support for custom HTTP method handlers, available
// via Router#Method and Router#MethodFunc
func RegisterMethod(method string) {
if method == "" {
return
}
method = strings.ToUpper(method)
if _, ok := methodMap[method]; ok {
return
}
n := len(methodMap)
if n > strconv.IntSize {
panic(fmt.Sprintf("chi: max number of methods reached (%d)", strconv.IntSize))
}
mt := methodTyp(math.Exp2(float64(n)))
methodMap[method] = mt
mALL |= mt
}
type nodeTyp uint8
@@ -341,7 +358,7 @@ func (n *node) setEndpoint(method methodTyp, handler http.Handler, pattern strin
}
}
func (n *node) FindRoute(rctx *Context, method methodTyp, path string) endpoints {
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]
@@ -350,7 +367,7 @@ func (n *node) FindRoute(rctx *Context, method methodTyp, path string) endpoints
// Find the routing handlers for the path
rn := n.findRoute(rctx, method, path)
if rn == nil {
return nil
return nil, nil, nil
}
// Record the routing params in the request lifecycle
@@ -363,7 +380,7 @@ func (n *node) FindRoute(rctx *Context, method methodTyp, path string) endpoints
rctx.RoutePatterns = append(rctx.RoutePatterns, rctx.routePattern)
}
return rn.endpoints
return rn, rn.endpoints, rn.endpoints[method].handler
}
// Recursive edge traversal by checking all nodeTyp groups along the way.
@@ -407,7 +424,7 @@ func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node {
// label for param nodes is the delimiter byte
p := strings.IndexByte(xsearch, xn.tail)
if p <= 0 {
if p < 0 {
if xn.tail == '/' {
p = len(xsearch)
} else {
@@ -514,7 +531,7 @@ func (n *node) isLeaf() bool {
return n.endpoints != nil
}
func (n *node) matchPattern(pattern string) bool {
func (n *node) findPattern(pattern string) bool {
nn := n
for _, nds := range nn.children {
if len(nds) == 0 {
@@ -551,7 +568,7 @@ func (n *node) matchPattern(pattern string) bool {
return true
}
return n.matchPattern(xpattern)
return n.findPattern(xpattern)
}
return false
}
@@ -643,8 +660,22 @@ func patNextSegment(pattern string) (nodeTyp, string, string, byte, int, int) {
if ps >= 0 {
// Param/Regexp pattern is next
nt := ntParam
pe := strings.Index(pattern, "}")
if pe < 0 {
// 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")
}
@@ -662,6 +693,15 @@ func patNextSegment(pattern string) (nodeTyp, string, string, byte, int, int) {
key = key[:idx]
}
if len(rexpat) > 0 {
if rexpat[0] != '^' {
rexpat = "^" + rexpat
}
if rexpat[len(rexpat)-1] != '$' {
rexpat = rexpat + "$"
}
}
return nt, key, rexpat, tail, ps, pe
}
+1
View File
@@ -6,3 +6,4 @@ code was contributed.)
Dustin Sallings <dustin@spy.net>
Jason Mooberry <jasonmoo@me.com>
Sergey Shepelev <temotor@gmail.com>
Alex Edwards <ajmedwards@gmail.com>
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2012-2015 Patrick Mylund Nielsen and the go-cache contributors
Copyright (c) 2012-2017 Patrick Mylund Nielsen and the go-cache contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+51 -75
View File
@@ -20,86 +20,62 @@ one) to recover from downtime quickly. (See the docs for `NewFrom()` for caveats
### Usage
```go
import (
"fmt"
"github.com/patrickmn/go-cache"
"time"
)
import (
"fmt"
"github.com/patrickmn/go-cache"
"time"
)
func main() {
func main() {
// Create a cache with a default expiration time of 5 minutes, and which
// purges expired items every 10 minutes
c := cache.New(5*time.Minute, 10*time.Minute)
// Create a cache with a default expiration time of 5 minutes, and which
// purges expired items every 30 seconds
c := cache.New(5*time.Minute, 30*time.Second)
// Set the value of the key "foo" to "bar", with the default expiration time
c.Set("foo", "bar", cache.DefaultExpiration)
// Set the value of the key "foo" to "bar", with the default expiration time
c.Set("foo", "bar", cache.DefaultExpiration)
// Set the value of the key "baz" to 42, with no expiration time
// (the item won't be removed until it is re-set, or removed using
// c.Delete("baz")
c.Set("baz", 42, cache.NoExpiration)
// Get the string associated with the key "foo" from the cache
foo, found := c.Get("foo")
if found {
fmt.Println(foo)
}
// Since Go is statically typed, and cache values can be anything, type
// assertion is needed when values are being passed to functions that don't
// take arbitrary types, (i.e. interface{}). The simplest way to do this for
// values which will only be used once--e.g. for passing to another
// function--is:
foo, found := c.Get("foo")
if found {
MyFunction(foo.(string))
}
// This gets tedious if the value is used several times in the same function.
// You might do either of the following instead:
if x, found := c.Get("foo"); found {
foo := x.(string)
// ...
}
// or
var foo string
if x, found := c.Get("foo"); found {
foo = x.(string)
}
// ...
// foo can then be passed around freely as a string
// Want performance? Store pointers!
c.Set("foo", &MyStruct, cache.DefaultExpiration)
if x, found := c.Get("foo"); found {
foo := x.(*MyStruct)
// ...
}
// If you store a reference type like a pointer, slice, map or channel, you
// do not need to run Set if you modify the underlying data. The cached
// reference points to the same memory, so if you modify a struct whose
// pointer you've stored in the cache, retrieving that pointer with Get will
// point you to the same data:
foo := &MyStruct{Num: 1}
c.Set("foo", foo, cache.DefaultExpiration)
// ...
x, _ := c.Get("foo")
foo := x.(*MyStruct)
fmt.Println(foo.Num)
// ...
foo.Num++
// ...
x, _ := c.Get("foo")
foo := x.(*MyStruct)
foo.Println(foo.Num)
// will print:
// 1
// 2
// Set the value of the key "baz" to 42, with no expiration time
// (the item won't be removed until it is re-set, or removed using
// c.Delete("baz")
c.Set("baz", 42, cache.NoExpiration)
// Get the string associated with the key "foo" from the cache
foo, found := c.Get("foo")
if found {
fmt.Println(foo)
}
// Since Go is statically typed, and cache values can be anything, type
// assertion is needed when values are being passed to functions that don't
// take arbitrary types, (i.e. interface{}). The simplest way to do this for
// values which will only be used once--e.g. for passing to another
// function--is:
foo, found := c.Get("foo")
if found {
MyFunction(foo.(string))
}
// This gets tedious if the value is used several times in the same function.
// You might do either of the following instead:
if x, found := c.Get("foo"); found {
foo := x.(string)
// ...
}
// or
var foo string
if x, found := c.Get("foo"); found {
foo = x.(string)
}
// ...
// foo can then be passed around freely as a string
// Want performance? Store pointers!
c.Set("foo", &MyStruct, cache.DefaultExpiration)
if x, found := c.Get("foo"); found {
foo := x.(*MyStruct)
// ...
}
}
```
### Reference
+51 -8
View File
@@ -81,6 +81,12 @@ func (c *cache) set(k string, x interface{}, d time.Duration) {
}
}
// Add an item to the cache, replacing any existing item, using the default
// expiration.
func (c *cache) SetDefault(k string, x interface{}) {
c.Set(k, x, DefaultExpiration)
}
// Add an item to the cache only if an item doesn't already exist for the given
// key, or if the existing item has expired. Returns an error otherwise.
func (c *cache) Add(k string, x interface{}, d time.Duration) error {
@@ -129,6 +135,36 @@ func (c *cache) Get(k string) (interface{}, bool) {
return item.Object, true
}
// GetWithExpiration returns an item and its expiration time from the cache.
// It returns the item or nil, the expiration time if one is set (if the item
// never expires a zero value for time.Time is returned), and a bool indicating
// whether the key was found.
func (c *cache) GetWithExpiration(k string) (interface{}, time.Time, bool) {
c.mu.RLock()
// "Inlining" of get and Expired
item, found := c.items[k]
if !found {
c.mu.RUnlock()
return nil, time.Time{}, false
}
if item.Expiration > 0 {
if time.Now().UnixNano() > item.Expiration {
c.mu.RUnlock()
return nil, time.Time{}, false
}
// Return the item and the expiration time
c.mu.RUnlock()
return item.Object, time.Unix(0, item.Expiration), true
}
// If expiration <= 0 (i.e. no expiration time set) then return the item
// and a zeroed time.Time
c.mu.RUnlock()
return item.Object, time.Time{}, true
}
func (c *cache) get(k string) (interface{}, bool) {
item, found := c.items[k]
if !found {
@@ -998,19 +1034,26 @@ func (c *cache) LoadFile(fname string) error {
return fp.Close()
}
// Returns the items in the cache. This may include items that have expired,
// but have not yet been cleaned up. If this is significant, the Expiration
// fields of the items should be checked. Note that explicit synchronization
// is needed to use a cache and its corresponding Items() return value at
// the same time, as the map is shared.
// Copies all unexpired items in the cache into a new map and returns it.
func (c *cache) Items() map[string]Item {
c.mu.RLock()
defer c.mu.RUnlock()
return c.items
m := make(map[string]Item, len(c.items))
now := time.Now().UnixNano()
for k, v := range c.items {
// "Inlining" of Expired
if v.Expiration > 0 {
if now > v.Expiration {
continue
}
}
m[k] = v
}
return m
}
// Returns the number of items in the cache. This may include items that have
// expired, but have not yet been cleaned up. Equivalent to len(c.Items()).
// expired, but have not yet been cleaned up.
func (c *cache) ItemCount() int {
c.mu.RLock()
n := len(c.items)
@@ -1031,7 +1074,6 @@ type janitor struct {
}
func (j *janitor) Run(c *cache) {
j.stop = make(chan bool)
ticker := time.NewTicker(j.Interval)
for {
select {
@@ -1051,6 +1093,7 @@ func stopJanitor(c *Cache) {
func runJanitor(c *cache, ci time.Duration) {
j := &janitor{
Interval: ci,
stop: make(chan bool),
}
c.janitor = j
go j.Run(c)
+9 -9
View File
@@ -46,16 +46,16 @@
"revisionTime": "2016-05-15T16:26:28Z"
},
{
"checksumSHA1": "Eg449rulPIiIoW2+Aoum/Af/wiM=",
"checksumSHA1": "YODbzvhUr0Tzp2/MkXflXph2hdY=",
"path": "github.com/go-chi/chi",
"revision": "25354a53cca531cb2efd3f1d3c565d90ff04d802",
"revisionTime": "2017-08-02T21:27:05Z"
"revision": "e223a795a06a5598451cf022558c17c779f5a7ab",
"revisionTime": "2018-02-02T19:41:35Z"
},
{
"checksumSHA1": "OpRUup8sYz/4ywe48bp0vPs+tHU=",
"checksumSHA1": "1Xlo4sCR+zeOzjslXc/NAOmkgXg=",
"path": "github.com/go-chi/chi/middleware",
"revision": "25354a53cca531cb2efd3f1d3c565d90ff04d802",
"revisionTime": "2017-08-02T21:27:05Z"
"revision": "e223a795a06a5598451cf022558c17c779f5a7ab",
"revisionTime": "2018-02-02T19:41:35Z"
},
{
"checksumSHA1": "KlhjFlnkWjrgYdOiUCgD+uuiCRU=",
@@ -106,10 +106,10 @@
"revisionTime": "2017-12-22T15:26:07Z"
},
{
"checksumSHA1": "vNL7xL08iaySNAxSUz+AoXGu3DQ=",
"checksumSHA1": "JVGDxPn66bpe6xEiexs1r+y6jF0=",
"path": "github.com/patrickmn/go-cache",
"revision": "1881a9bccb818787f68c52bfba648c6cf34c34fa",
"revisionTime": "2016-01-27T17:00:04Z"
"revision": "a3647f8e31d79543b2d0f0ae2fe5c379d72cedc0",
"revisionTime": "2017-07-22T04:01:10Z"
},
{
"checksumSHA1": "rJab1YdNhQooDiBWNnt7TLWPyBU=",