Skip to content

Commit

Permalink
more optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
RobertWHurst committed Jan 26, 2024
1 parent ab423a2 commit c58bc7b
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 78 deletions.
204 changes: 132 additions & 72 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"net/http"
"net/url"
"sync"
"time"
)

Expand Down Expand Up @@ -73,74 +74,162 @@ func NewContext(res http.ResponseWriter, req *http.Request, handlers ...any) *Co
// used by libraries which wish to extend or encapsulate the functionality of
// Navaros. For example, implementing a custom router.
func NewContextWithNode(res http.ResponseWriter, req *http.Request, firstHandlerNode *HandlerNode) *Context {
return &Context{
request: req,
ctx := contextFromPool()

method: HTTPMethod(req.Method),
path: req.URL.Path,
ctx.request = req

Headers: http.Header{},
bodyWriter: res,
ctx.method = HTTPMethodFromString(req.Method)
ctx.path = req.URL.Path

currentHandlerNode: &HandlerNode{
Method: All,
Next: firstHandlerNode,
},
ctx.bodyWriter = res

associatedValues: map[string]any{},
ctx.currentHandlerNode = firstHandlerNode

doneHandlers: []func(){},
}
return ctx
}

// NewSubContextWithNode creates a new Context from an existing Context. This
// is useful when you want to create a new Context from an existing one, but
// with a different handler chain. Note that when the end of the sub context's
// handler chain is reached, the parent context's handler chain will continue.
func NewSubContextWithNode(ctx *Context, firstHandlerNode *HandlerNode) *Context {
subContext := &Context{
parentContext: ctx,

request: ctx.request,
subContext := contextFromPool()

method: ctx.method,
path: ctx.path,
params: ctx.params,
subContext.parentContext = ctx

Status: ctx.Status,
Headers: ctx.Headers,
Cookies: ctx.Cookies,
Body: ctx.Body,
bodyWriter: ctx.bodyWriter,
hasWrittenHeaders: ctx.hasWrittenHeaders,
hasWrittenBody: ctx.hasWrittenBody,
subContext.request = ctx.request

MaxRequestBodySize: ctx.MaxRequestBodySize,
subContext.method = ctx.method
subContext.path = ctx.path
for k, v := range ctx.params {
subContext.params[k] = v
}

Error: ctx.Error,
ErrorStack: ctx.ErrorStack,
FinalError: ctx.FinalError,
FinalErrorStack: ctx.FinalErrorStack,
subContext.Status = ctx.Status
for k, v := range ctx.Headers {
subContext.Headers[k] = v
}
subContext.Cookies = ctx.Cookies
subContext.Body = ctx.Body
subContext.bodyWriter = ctx.bodyWriter
subContext.hasWrittenHeaders = ctx.hasWrittenHeaders
subContext.hasWrittenBody = ctx.hasWrittenBody

requestBodyUnmarshaller: ctx.requestBodyUnmarshaller,
responseBodyMarshaller: ctx.responseBodyMarshaller,
subContext.MaxRequestBodySize = ctx.MaxRequestBodySize

associatedValues: ctx.associatedValues,
subContext.Error = ctx.Error
subContext.ErrorStack = ctx.ErrorStack
subContext.FinalError = ctx.FinalError
subContext.FinalErrorStack = ctx.FinalErrorStack

deadline: ctx.deadline,
doneHandlers: ctx.doneHandlers,
subContext.requestBodyUnmarshaller = ctx.requestBodyUnmarshaller
subContext.responseBodyMarshaller = ctx.responseBodyMarshaller

currentHandlerNode: &HandlerNode{
Method: All,
Pattern: nil,
HandlersAndTransformers: []any{},
Next: firstHandlerNode,
},
for k, v := range ctx.associatedValues {
subContext.associatedValues[k] = v
}

subContext.deadline = ctx.deadline
subContext.doneHandlers = append(subContext.doneHandlers[:0], ctx.doneHandlers...)

subContext.currentHandlerNode = firstHandlerNode

return subContext
}

var contextPool = sync.Pool{
New: func() any {
return &Context{
params: RequestParams{},
Headers: http.Header{},
Cookies: []*http.Cookie{},
associatedValues: map[string]any{},
doneHandlers: []func(){},
}
},
}

func contextFromPool() *Context {
ctx := contextPool.Get().(*Context)

ctx.parentContext = nil

ctx.request = nil

ctx.method = All
ctx.path = ""
for k := range ctx.params {
delete(ctx.params, k)
}

ctx.Status = 0
for k := range ctx.Headers {
delete(ctx.Headers, k)
}
ctx.Cookies = ctx.Cookies[:0]
ctx.Body = nil
ctx.bodyWriter = nil
ctx.hasWrittenHeaders = false
ctx.hasWrittenBody = false

ctx.MaxRequestBodySize = 0

ctx.Error = nil
ctx.ErrorStack = ""
ctx.FinalError = nil
ctx.FinalErrorStack = ""

ctx.requestBodyUnmarshaller = nil
ctx.responseBodyMarshaller = nil

ctx.currentHandlerNode = nil
ctx.matchingHandlerNode = nil
ctx.currentHandlerOrTransformerIndex = 0
ctx.currentHandlerOrTransformer = nil

for k := range ctx.associatedValues {
delete(ctx.associatedValues, k)
}

ctx.deadline = nil
ctx.doneHandlers = ctx.doneHandlers[:0]

return ctx
}

func (c *Context) free() {
contextPool.Put(c)
}

// tryUpdateParent updates the parent context with the current context's
// state. This is called by Next() when the current context is a sub context.
func (c *Context) tryUpdateParent() {
if c.parentContext == nil {
return
}

c.parentContext.Status = c.Status
c.parentContext.Headers = c.Headers
c.parentContext.Cookies = c.Cookies
c.parentContext.Body = c.Body
c.parentContext.hasWrittenHeaders = c.hasWrittenHeaders
c.parentContext.hasWrittenBody = c.hasWrittenBody

c.parentContext.MaxRequestBodySize = c.MaxRequestBodySize

c.parentContext.Error = c.Error
c.parentContext.ErrorStack = c.ErrorStack
c.parentContext.FinalError = c.FinalError
c.parentContext.FinalErrorStack = c.FinalErrorStack

c.parentContext.requestBodyUnmarshaller = c.requestBodyUnmarshaller
c.parentContext.responseBodyMarshaller = c.responseBodyMarshaller

c.parentContext.associatedValues = c.associatedValues
c.parentContext.deadline = c.deadline
c.parentContext.doneHandlers = c.doneHandlers
}

// Next calls the next handler in the chain. This is useful for creating
// middleware style handlers that work on the context before and/or after the
// responding handler.
Expand Down Expand Up @@ -372,32 +461,3 @@ func (c *Context) marshallResponseBody() (io.Reader, error) {
}
return c.responseBodyMarshaller(c.Body)
}

// tryUpdateParent updates the parent context with the current context's
// state. This is called by Next() when the current context is a sub context.
func (c *Context) tryUpdateParent() {
if c.parentContext == nil {
return
}

c.parentContext.Status = c.Status
c.parentContext.Headers = c.Headers
c.parentContext.Cookies = c.Cookies
c.parentContext.Body = c.Body
c.parentContext.hasWrittenHeaders = c.hasWrittenHeaders
c.parentContext.hasWrittenBody = c.hasWrittenBody

c.parentContext.MaxRequestBodySize = c.MaxRequestBodySize

c.parentContext.Error = c.Error
c.parentContext.ErrorStack = c.ErrorStack
c.parentContext.FinalError = c.FinalError
c.parentContext.FinalErrorStack = c.FinalErrorStack

c.parentContext.requestBodyUnmarshaller = c.requestBodyUnmarshaller
c.parentContext.responseBodyMarshaller = c.responseBodyMarshaller

c.parentContext.associatedValues = c.associatedValues
c.parentContext.deadline = c.deadline
c.parentContext.doneHandlers = c.doneHandlers
}
2 changes: 1 addition & 1 deletion context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func TestContextMethod(t *testing.T) {

ctx := navaros.NewContext(res, req, nil)

if ctx.Method() != "GET" {
if ctx.Method() != navaros.Get {
t.Error("expected method to be GET")
}
}
Expand Down
12 changes: 7 additions & 5 deletions pattern.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ func NewPattern(patternStr string) (*Pattern, error) {
return nil, err
}

return &Pattern{
pattern := &Pattern{
str: patternStr,
regExp: patternRegExp,
}, nil
}

return pattern, nil
}

// Match compares a path to the pattern and returns a map of named parameters
Expand All @@ -52,8 +54,8 @@ func (p *Pattern) Match(path string) (RequestParams, bool) {
}

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

Expand All @@ -68,7 +70,7 @@ func (p *Pattern) MatchInto(path string, params *RequestParams) bool {
}
for i := 1; i < len(keys); i += 1 {
if keys[i] != "" {
(*params)[keys[i]] = matches[i]
(*params)[keys[i]] = path[matchIndices[i*2]:matchIndices[i*2+1]]
}
}

Expand Down
2 changes: 2 additions & 0 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func (r *Router) ServeHTTP(res http.ResponseWriter, req *http.Request) {
ctx := NewContextWithNode(res, req, r.firstHandlerNode)
ctx.Next()
ctx.finalize()
ctx.free()
}

// Handle is for the purpose of taking an existing context, and running it
Expand All @@ -40,6 +41,7 @@ func (r *Router) ServeHTTP(res http.ResponseWriter, req *http.Request) {
func (r *Router) Handle(ctx *Context) {
subCtx := NewSubContextWithNode(ctx, r.firstHandlerNode)
subCtx.Next()
subCtx.free()
if subCtx.currentHandlerNode == nil {
ctx.Next()
}
Expand Down

0 comments on commit c58bc7b

Please sign in to comment.