Skip to content

Commit

Permalink
remove type based context values. It was neat, but slow
Browse files Browse the repository at this point in the history
  • Loading branch information
RobertWHurst committed Jan 25, 2024
1 parent e73c8ac commit 2bdc468
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 132 deletions.
6 changes: 2 additions & 4 deletions context-finalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ func (c *Context) finalize() {
case Redirect:
redirect = &body
case string:
finalBodyReader = bytes.NewBufferString(body)
finalBodyReader = strings.NewReader(body)
case []byte:
finalBodyReader = bytes.NewBuffer(body)
finalBodyReader = bytes.NewReader(body)
default:
marshalledReader, err := c.marshallResponseBody()
if err != nil {
Expand Down Expand Up @@ -111,8 +111,6 @@ func (c *Context) finalize() {
}
}

delete(contextData, c)

c.FinalError = c.Error
c.FinalErrorStack = c.ErrorStack
for _, doneHandler := range c.doneHandlers {
Expand Down
2 changes: 1 addition & 1 deletion context-next.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (c *Context) next() {
// until we find a matching handler node.
if c.matchingHandlerNode == nil {
for c.currentHandlerNode != nil {
if c.tryMatchHandlerNode(c.currentHandlerNode) {
if c.currentHandlerNode.tryMatch(c) {
c.matchingHandlerNode = c.currentHandlerNode
break
}
Expand Down
64 changes: 0 additions & 64 deletions context-utils.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package navaros

import (
"fmt"
"reflect"
"time"
)

Expand All @@ -19,65 +17,3 @@ func CtxFinalize(ctx *Context) {
func CtxSetDeadline(ctx *Context, deadline time.Time) {
ctx.deadline = &deadline
}

// CtxSet associates a value by it's type with a context. This is for handlers
// and middleware to share data with other handlers and middleware associated
// with the context.
func CtxSet(ctx *Context, value any) {
for ctx.parentContext != nil {
ctx = ctx.parentContext
}

valueType := reflect.TypeOf(value).String()

if contextData[ctx] == nil {
contextData[ctx] = make(map[string]any)
}
contextData[ctx][valueType] = value
}

// CtxGet retrieves a value by it's type from a context. This is for handlers
// and middleware to retrieve data set in association with the context by
// other handlers and middleware.
func CtxGet[V any](ctx *Context) (V, bool) {
for ctx.parentContext != nil {
ctx = ctx.parentContext
}

var v V
targetType := reflect.TypeOf(v).String()

var target V
contextData, ok := contextData[ctx]
if !ok {
return target, false
}
value, ok := contextData[targetType]
if !ok {
return target, false
}

return value.(V), true
}

// CtxMustGet like CtxGet retrieves a value by it's type from a context, but
// unlike CtxGet it panics if the value is not found.
func CtxMustGet[V any](ctx *Context) V {
for ctx.parentContext != nil {
ctx = ctx.parentContext
}

var v V
targetType := reflect.TypeOf(v).String()

contextData, ok := contextData[ctx]
if !ok {
panic("Context data not found for context")
}
value, ok := contextData[targetType]
if !ok {
panic(fmt.Sprintf("Context data not found for type: %s", targetType))
}

return value.(V)
}
40 changes: 14 additions & 26 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ type Context struct {
currentHandlerOrTransformerIndex int
currentHandlerOrTransformer any

associatedValues map[string]any

deadline *time.Time
doneHandlers []func()
}

var contextData = make(map[*Context]map[string]any)

// NewContext creates a new Context from go's http.ResponseWriter and
// http.Request. It also takes a variadic list of handlers. This is useful for
// creating a new Context outside of a router, and can be used by libraries
Expand Down Expand Up @@ -83,11 +83,12 @@ func NewContextWithNode(res http.ResponseWriter, req *http.Request, firstHandler
bodyWriter: res,

currentHandlerNode: &HandlerNode{
Method: All,
HandlersAndTransformers: []any{},
Next: firstHandlerNode,
Method: All,
Next: firstHandlerNode,
},

associatedValues: map[string]any{},

doneHandlers: []func(){},
}
}
Expand Down Expand Up @@ -127,6 +128,14 @@ func (c *Context) Next() {
c.next()
}

func (s Context) Set(key string, value any) {
s.associatedValues[key] = value
}

func (s Context) Get(key string) any {
return s.associatedValues[key]
}

// Method returns the HTTP method of the request.
func (c *Context) Method() HTTPMethod {
return c.method
Expand Down Expand Up @@ -359,24 +368,3 @@ func (c *Context) tryUpdateParent() {
c.parentContext.hasWrittenHeaders = c.hasWrittenHeaders
c.parentContext.hasWrittenBody = c.hasWrittenBody
}

// tryMatchHandlerNode attempts to match a handler node's route pattern and http
// method to the current context. It will return true if the handler node
// matches, and false if it does not.
func (c *Context) tryMatchHandlerNode(node *HandlerNode) bool {
if node.Method != All && node.Method != c.method {
return false
}

if node.Pattern != nil {
params, ok := node.Pattern.Match(c.path)
if !ok {
return false
}
c.params = params
} else {
c.params = make(map[string]string)
}

return true
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/RobertWHurst/navaros

go 1.20

require github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db h1:7aN5cccjIqCLTzedH7MZzRZt5/lsAHch6Z3L2ZGn5FA=
github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A=
24 changes: 24 additions & 0 deletions handler-node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package navaros

// HandlerNode is used to build the handler chains used by the context. The
// router builds a chain from these objects then attaches them to the context.
// It then calls Next on the context to execute the chain.
type HandlerNode struct {
Method HTTPMethod
Pattern *Pattern
HandlersAndTransformers []any
Next *HandlerNode
}

// tryMatch attempts to match the handler node's route pattern and http
// method to the a context. It will return true if the handler node
// matches, and false if it does not.
func (n *HandlerNode) tryMatch(ctx *Context) bool {
if n.Method != All && n.Method != ctx.method {
return false
}
if n.Pattern == nil {
return true
}
return n.Pattern.MatchInto(ctx.path, &ctx.params)
}
19 changes: 0 additions & 19 deletions handler.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,5 @@
package navaros

// Transformer is a special type of handler object that can be used to
// transform the context before and after handlers have processed the request.
// This is most useful for modifying or re-encoding the request and response
// bodies.
type Transformer interface {
TransformRequest(ctx *Context)
TransformResponse(ctx *Context)
}

// HandlerNode is used to build the handler chains used by the context. The
// router builds a chain from these objects then attaches them to the context.
// It then calls Next on the context to execute the chain.
type HandlerNode struct {
Method HTTPMethod
Pattern *Pattern
HandlersAndTransformers []any
Next *HandlerNode
}

// Handler is a handler object interface. Any object that implements this
// interface can be used as a handler in a handler chain.
type Handler interface {
Expand Down
31 changes: 28 additions & 3 deletions pattern.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package navaros

import (
"errors"
"regexp"

"github.com/grafana/regexp"
)

// Pattern is used to compare and match request paths to route patterns.
Expand Down Expand Up @@ -32,15 +33,15 @@ func NewPattern(patternStr string) (*Pattern, error) {
// extracted from the path as per the pattern. If the path matches the pattern,
// the second return value will be true. If the path does not match the pattern,
// the second return value will be false.
func (p *Pattern) Match(path string) (map[string]string, bool) {
func (p *Pattern) Match(path string) (RequestParams, bool) {
matches := p.regExp.FindStringSubmatch(path)
if len(matches) == 0 {
return nil, false
}

keys := p.regExp.SubexpNames()

params := make(map[string]string)
params := make(RequestParams, len(keys))
for i := 1; i < len(keys); i += 1 {
if keys[i] != "" {
params[keys[i]] = matches[i]
Expand All @@ -50,6 +51,30 @@ func (p *Pattern) Match(path string) (map[string]string, bool) {
return params, true
}

func (p *Pattern) MatchInto(path string, params *RequestParams) bool {
matches := p.regExp.FindStringSubmatch(path)
if len(matches) == 0 {
return false
}

keys := p.regExp.SubexpNames()

if *params == nil {
*params = make(map[string]string, len(keys))
}

for key := range *params {
delete(*params, key)
}
for i := 1; i < len(keys); i += 1 {
if keys[i] != "" {
(*params)[keys[i]] = matches[i]
}
}

return true
}

// String returns the string representation of the pattern.
func (p *Pattern) String() string {
return p.str
Expand Down
Loading

0 comments on commit 2bdc468

Please sign in to comment.