revendor with fresh chi and cache
This commit is contained in:
+31
-1
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,4 +1,4 @@
|
||||
// +build go1.8
|
||||
// +build go1.8 appengine
|
||||
|
||||
package middleware
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build go1.8
|
||||
// +build go1.8 appengine
|
||||
|
||||
package middleware
|
||||
|
||||
|
||||
+51
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Generated
Vendored
+6
@@ -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
@@ -1,4 +1,4 @@
|
||||
// +build go1.8
|
||||
// +build go1.8 appengine
|
||||
|
||||
package middleware
|
||||
|
||||
|
||||
+54
-32
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
Vendored
+9
-9
@@ -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=",
|
||||
|
||||
Reference in New Issue
Block a user