diff --git a/context.go b/context.go index c603030..9015159 100644 --- a/context.go +++ b/context.go @@ -54,7 +54,11 @@ func (c *Context) Next() { } func (c *Context) prepare(ctx *Context) { - ctx.Request.httpRequest.ParseMultipartForm(int64(app.Config.Request.MaxUploadFileSize)) + // Only parse multipart form if it hasn't been parsed already + // This prevents race conditions when multiple goroutines might access the same request + if ctx.Request.httpRequest.MultipartForm == nil { + ctx.Request.httpRequest.ParseMultipartForm(int64(app.Config.Request.MaxUploadFileSize)) + } } func (c *Context) GetPathParam(key string) interface{} { diff --git a/core.go b/core.go index ca8e747..9afef81 100644 --- a/core.go +++ b/core.go @@ -6,6 +6,7 @@ package core import ( + "context" "embed" "fmt" "log" @@ -51,12 +52,18 @@ type configContainer struct { // App is a struct representing the main application container and its core components. type App struct { - t int // for tracking hooks + t int // for tracking hooks (deprecated - maintained for backward compatibility) chain *chain hooks *Hooks Config *configContainer } +// requestContext holds request-specific chain execution state +type requestContext struct { + chainIndex int + chainNodes []interface{} +} + // app is a global instance of the App structure used to manage application configuration and lifecycle. var app *App @@ -231,10 +238,25 @@ func (app *App) RegisterRoutes(routes []Route, router *httprouter.Router) *httpr // makeHTTPRouterHandlerFunc creates an httprouter.Handle that handles HTTP requests with the specified controller and hooks. func (app *App) makeHTTPRouterHandlerFunc(h Controller, ms []Hook) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - + // Create request-specific chain state + reqCtx := &requestContext{ + chainIndex: 0, + } + + // Prepare the chain nodes for this request + mw := app.hooks.GetHooks() + for _, v := range mw { + reqCtx.chainNodes = append(reqCtx.chainNodes, v) + } + rhs := app.combHandlers(h, ms) + for _, v := range rhs { + reqCtx.chainNodes = append(reqCtx.chainNodes, v) + } + + // Store chain state in request context ctx := &Context{ Request: &Request{ - httpRequest: r, + httpRequest: r.WithContext(context.WithValue(r.Context(), "goffeeChain", reqCtx)), httpPathParams: ps, }, Response: &Response{ @@ -257,10 +279,16 @@ func (app *App) makeHTTPRouterHandlerFunc(h Controller, ms []Hook) httprouter.Ha } ctx.prepare(ctx) - rhs := app.combHandlers(h, ms) - app.prepareChain(rhs) - app.t = 0 - app.chain.execute(ctx) + + // Execute the first handler in the chain + if len(reqCtx.chainNodes) > 0 { + n := reqCtx.chainNodes[0] + if f, ok := n.(Hook); ok { + f(ctx) + } else if ff, ok := n.(Controller); ok { + ff(ctx) + } + } for _, header := range ctx.Response.headers { w.Header().Add(header.key, header.val) @@ -363,18 +391,32 @@ func UseHook(mw Hook) { // Next advances to the next middleware or controller in the chain and invokes it with the given context if available. func (app *App) Next(c *Context) { - app.t = app.t + 1 - n := app.chain.getByIndex(app.t) - if n != nil { - f, ok := n.(Hook) - if ok { - f(c) - } else { - ff, ok := n.(Controller) - if ok { + // Get request-specific chain state from context + if reqCtx, ok := c.Request.httpRequest.Context().Value("goffeeChain").(*requestContext); ok { + reqCtx.chainIndex++ + if reqCtx.chainIndex < len(reqCtx.chainNodes) { + n := reqCtx.chainNodes[reqCtx.chainIndex] + if f, ok := n.(Hook); ok { + f(c) + } else if ff, ok := n.(Controller); ok { ff(c) } } + } else { + // Fallback to old behavior (not thread-safe) + app.t = app.t + 1 + n := app.chain.getByIndex(app.t) + if n != nil { + f, ok := n.(Hook) + if ok { + f(c) + } else { + ff, ok := n.(Controller) + if ok { + ff(c) + } + } + } } } @@ -410,6 +452,17 @@ func (app *App) prepareChain(hs []interface{}) { } } +// prepareRequestChain prepares a request-specific chain to avoid race conditions +func (app *App) prepareRequestChain(requestChain *chain, hs []interface{}) { + mw := app.hooks.GetHooks() + for _, v := range mw { + requestChain.nodes = append(requestChain.nodes, v) + } + for _, v := range hs { + requestChain.nodes = append(requestChain.nodes, v) + } +} + // execute executes the first node in the chain, invoking it as either a Hook or Controller if applicable. func (cn *chain) execute(ctx *Context) { i := cn.getByIndex(0)