diff --git a/context.go b/context.go index 4d74322..ee4e479 100644 --- a/context.go +++ b/context.go @@ -86,8 +86,8 @@ type Context interface { Tree() *Tree // Fox returns the Router instance. Fox() *Router - // Reset resets the Context to its initial state, attaching the provided ResponseWriter and http.Request. - Reset(w ResponseWriter, r *http.Request) + // Reset resets the Context to its initial state, attaching the provided Router, ResponseWriter and http.Request. + Reset(fox *Router, w ResponseWriter, r *http.Request) } // cTx holds request-related information and allows interaction with the ResponseWriter. @@ -99,8 +99,7 @@ type cTx struct { skipNds *skippedNodes // tree at allocation (read-only, no reset) - tree *Tree - // router at allocation (read-only, no reset) + tree *Tree fox *Router cachedQuery url.Values path string @@ -109,25 +108,27 @@ type cTx struct { } // Reset resets the Context to its initial state, attaching the provided ResponseWriter and http.Request. -func (c *cTx) Reset(w ResponseWriter, r *http.Request) { +func (c *cTx) Reset(fox *Router, w ResponseWriter, r *http.Request) { c.req = r c.w = w c.path = "" c.tsr = false c.cachedQuery = nil *c.params = (*c.params)[:0] + c.fox = fox } // reset resets the Context to its initial state, attaching the provided http.ResponseWriter and http.Request. // Caution: You should always pass the original http.ResponseWriter to this method, not the ResponseWriter itself, to // avoid wrapping the ResponseWriter within itself. Use wisely! -func (c *cTx) reset(w http.ResponseWriter, r *http.Request) { +func (c *cTx) reset(fox *Router, w http.ResponseWriter, r *http.Request) { c.rec.reset(w) c.req = r c.w = &c.rec c.path = "" c.cachedQuery = nil *c.params = (*c.params)[:0] + c.fox = fox } func (c *cTx) resetNil() { @@ -136,6 +137,7 @@ func (c *cTx) resetNil() { c.path = "" c.cachedQuery = nil *c.params = (*c.params)[:0] + c.fox = nil } // Request returns the *http.Request. @@ -313,6 +315,7 @@ func (c *cTx) CloneWith(w ResponseWriter, r *http.Request) ContextCloser { cp.w = w cp.path = c.path cp.cachedQuery = nil + cp.fox = c.fox if cap(*c.params) > cap(*cp.params) { // Grow cp.params to a least cap(c.params) *cp.params = slices.Grow(*cp.params, cap(*c.params)) diff --git a/fox.go b/fox.go index b115604..c1dc01e 100644 --- a/fox.go +++ b/fox.go @@ -201,7 +201,9 @@ func (fox *Router) ClientIPStrategyEnabled() bool { func (fox *Router) NewTree() *Tree { tree := new(Tree) tree.mws = fox.mws - tree.fox = fox + tree.ipStrategy = fox.ipStrategy + tree.ignoreTrailingSlash = fox.ignoreTrailingSlash + tree.redirectTrailingSlash = fox.redirectTrailingSlash // Pre instantiate nodes for common http verb nds := make([]*node, len(commonVerbs)) @@ -283,7 +285,7 @@ func (fox *Router) Remove(method, path string) error { // This API is EXPERIMENTAL and is likely to change in future release. func (fox *Router) Lookup(w ResponseWriter, r *http.Request) (handler HandlerFunc, cc ContextCloser, tsr bool) { tree := fox.tree.Load() - return tree.Lookup(w, r) + return tree.Lookup(fox, w, r) } // SkipMethod is used as a return value from WalkFunc to indicate that @@ -380,7 +382,7 @@ func (fox *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { tree := fox.tree.Load() c := tree.ctx.Get().(*cTx) - c.reset(w, r) + c.reset(fox, w, r) nds := *tree.nodes.Load() index := findRootNode(r.Method, nds) @@ -402,7 +404,7 @@ func (fox *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if r.Method != http.MethodConnect && r.URL.Path != "/" && tsr { - if fox.ignoreTrailingSlash { + if n.route.ignoreTrailingSlash { c.path = n.route.path c.tsr = tsr n.route.handler(c) @@ -410,7 +412,7 @@ func (fox *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - if fox.redirectTrailingSlash && target == CleanPath(target) { + if n.route.redirectTrailingSlash && target == CleanPath(target) { // Reset params as it may have recorded wildcard segment (the context may still be used in a middleware) *c.params = (*c.params)[:0] c.tsr = false @@ -442,7 +444,7 @@ NoMethodFallback: } else { // Since different method and route may match (e.g. GET /foo/bar & POST /foo/{name}), we cannot set the path and params. for i := 0; i < len(nds); i++ { - if n, tsr := tree.lookup(nds[i], target, c, true); n != nil && (!tsr || fox.ignoreTrailingSlash) { + if n, tsr := tree.lookup(nds[i], target, c, true); n != nil && (!tsr || n.route.ignoreTrailingSlash) { if sb.Len() > 0 { sb.WriteString(", ") } @@ -462,7 +464,7 @@ NoMethodFallback: var sb strings.Builder for i := 0; i < len(nds); i++ { if nds[i].key != r.Method { - if n, tsr := tree.lookup(nds[i], target, c, true); n != nil && (!tsr || fox.ignoreTrailingSlash) { + if n, tsr := tree.lookup(nds[i], target, c, true); n != nil && (!tsr || n.route.ignoreTrailingSlash) { if sb.Len() > 0 { sb.WriteString(", ") } diff --git a/helpers.go b/helpers.go index df46a14..03363cc 100644 --- a/helpers.go +++ b/helpers.go @@ -27,6 +27,7 @@ func newTextContextOnly(fox *Router, w http.ResponseWriter, r *http.Request) *cT c.req = r c.rec.reset(w) c.w = &c.rec + c.fox = fox return c } diff --git a/tree.go b/tree.go index 384cec6..c4ad8a7 100644 --- a/tree.go +++ b/tree.go @@ -31,13 +31,15 @@ import ( // fox.Tree().Lock() // defer fox.Tree().Unlock() type Tree struct { - ctx sync.Pool - nodes atomic.Pointer[[]*node] - fox *Router // TODO tree should be agnostic to the router - mws []middleware + ctx sync.Pool + ipStrategy ClientIPStrategy + nodes atomic.Pointer[[]*node] + mws []middleware sync.Mutex - maxParams atomic.Uint32 - maxDepth atomic.Uint32 + maxParams atomic.Uint32 + maxDepth atomic.Uint32 + redirectTrailingSlash bool + ignoreTrailingSlash bool } // Handle registers a new handler for the given method and path. This function return an error if the route @@ -162,7 +164,7 @@ func (t *Tree) Methods(path string) []string { c.resetNil() for i := range nds { n, tsr := t.lookup(nds[i], path, c, true) - if n != nil && (!tsr || t.fox.redirectTrailingSlash || t.fox.ignoreTrailingSlash) { + if n != nil && (!tsr || n.route.redirectTrailingSlash || n.route.ignoreTrailingSlash) { if methods == nil { methods = make([]string, 0) } @@ -183,7 +185,7 @@ func (t *Tree) Methods(path string) []string { // use by multiple goroutine and while mutation on Tree are ongoing. If there is a direct match or a tsr is possible, // Lookup always return a HandlerFunc and a ContextCloser. // This API is EXPERIMENTAL and is likely to change in future release. -func (t *Tree) Lookup(w ResponseWriter, r *http.Request) (handler HandlerFunc, cc ContextCloser, tsr bool) { +func (t *Tree) Lookup(fox *Router, w ResponseWriter, r *http.Request) (handler HandlerFunc, cc ContextCloser, tsr bool) { nds := *t.nodes.Load() index := findRootNode(r.Method, nds) @@ -192,7 +194,7 @@ func (t *Tree) Lookup(w ResponseWriter, r *http.Request) (handler HandlerFunc, c } c := t.ctx.Get().(*cTx) - c.Reset(w, r) + c.Reset(fox, w, r) target := r.URL.Path if len(r.URL.RawPath) > 0 { @@ -858,8 +860,6 @@ func (t *Tree) allocateContext() *cTx { // This is a read only value, no reset, it's always the // owner of the pool. tree: t, - // This is a read only value, no reset. - fox: t.fox, } } @@ -928,12 +928,12 @@ func (t *Tree) updateMaxDepth(max uint32) { // newRoute create a new route, apply path options and apply middleware on the handler. func (t *Tree) newRoute(path string, handler HandlerFunc, opts ...PathOption) *Route { rte := &Route{ - ipStrategy: t.fox.ipStrategy, + ipStrategy: t.ipStrategy, base: handler, path: path, mws: t.mws, - redirectTrailingSlash: t.fox.redirectTrailingSlash, - ignoreTrailingSlash: t.fox.ignoreTrailingSlash, + redirectTrailingSlash: t.redirectTrailingSlash, + ignoreTrailingSlash: t.ignoreTrailingSlash, } for _, opt := range opts {