Moonshark/router/match.go

188 lines
4.7 KiB
Go

package router
import (
"os"
"strings"
)
// Match finds a handler for the given method and path (URL path, excludes groups)
func (r *LuaRouter) Match(method, path string, params *Params) (*node, bool) {
params.Count = 0
r.mu.RLock()
root, exists := r.routes[method]
r.mu.RUnlock()
if !exists {
return nil, false
}
segments := strings.Split(strings.Trim(path, "/"), "/")
return r.matchPath(root, segments, params, 0)
}
// matchPath recursively matches a path against the routing tree
func (r *LuaRouter) matchPath(current *node, segments []string, params *Params, depth int) (*node, bool) {
// Filter empty segments
filteredSegments := segments[:0]
for _, segment := range segments {
if segment != "" {
filteredSegments = append(filteredSegments, segment)
}
}
segments = filteredSegments
if len(segments) == 0 {
if current.handler != "" {
return current, true
}
if current.indexFile != "" {
return current, true
}
return nil, false
}
segment := segments[0]
remaining := segments[1:]
// Try static child first
if child, exists := current.staticChild[segment]; exists {
if node, found := r.matchPath(child, remaining, params, depth+1); found {
return node, true
}
}
// Try parameter child
if current.paramChild != nil {
if params.Count < maxParams {
params.Keys[params.Count] = current.paramChild.paramName
params.Values[params.Count] = segment
params.Count++
}
if node, found := r.matchPath(current.paramChild, remaining, params, depth+1); found {
return node, true
}
params.Count--
}
// Fall back to index.lua
if current.indexFile != "" {
return current, true
}
return nil, false
}
// GetRouteInfo returns bytecode, script path, error, params, and found status
func (r *LuaRouter) GetRouteInfo(method, path []byte) ([]byte, string, error, *Params, bool) {
// Convert to string for internal processing
methodStr := string(method)
pathStr := string(path)
routeCacheKey := getCacheKey(methodStr, pathStr)
routeCacheData := r.routeCache.Get(nil, routeCacheKey)
// Fast path: found in cache
if len(routeCacheData) > 0 {
handlerPath := string(routeCacheData[8:])
bytecodeKey := routeCacheData[:8]
bytecode := r.bytecodeCache.Get(nil, bytecodeKey)
n, exists := r.nodeForHandler(handlerPath)
if !exists {
r.routeCache.Del(routeCacheKey)
return nil, "", nil, nil, false
}
// Check if recompilation needed
if len(bytecode) > 0 {
// For cached routes, we need to re-match to get params
params := &Params{}
r.Match(methodStr, pathStr, params)
return bytecode, handlerPath, n.err, params, true
}
// Recompile if needed
fileInfo, err := os.Stat(handlerPath)
if err != nil || fileInfo.ModTime().After(n.modTime) {
scriptPath := n.handler
if scriptPath == "" {
scriptPath = n.indexFile
}
fsPath := n.fsPath
if fsPath == "" {
fsPath = "/"
}
if err := r.compileWithMiddleware(n, fsPath, scriptPath); err != nil {
params := &Params{}
r.Match(methodStr, pathStr, params)
return nil, handlerPath, n.err, params, true
}
newBytecodeKey := getBytecodeKey(handlerPath)
bytecode = r.bytecodeCache.Get(nil, newBytecodeKey)
newCacheData := make([]byte, 8+len(handlerPath))
copy(newCacheData[:8], newBytecodeKey)
copy(newCacheData[8:], handlerPath)
r.routeCache.Set(routeCacheKey, newCacheData)
params := &Params{}
r.Match(methodStr, pathStr, params)
return bytecode, handlerPath, n.err, params, true
}
params := &Params{}
r.Match(methodStr, pathStr, params)
return bytecode, handlerPath, n.err, params, true
}
// Slow path: lookup and compile
params := &Params{}
node, found := r.Match(methodStr, pathStr, params)
if !found {
return nil, "", nil, nil, false
}
scriptPath := node.handler
if scriptPath == "" && node.indexFile != "" {
scriptPath = node.indexFile
}
if scriptPath == "" {
return nil, "", nil, nil, false
}
bytecodeKey := getBytecodeKey(scriptPath)
bytecode := r.bytecodeCache.Get(nil, bytecodeKey)
if len(bytecode) == 0 {
fsPath := node.fsPath
if fsPath == "" {
fsPath = "/"
}
if err := r.compileWithMiddleware(node, fsPath, scriptPath); err != nil {
return nil, scriptPath, node.err, params, true
}
bytecode = r.bytecodeCache.Get(nil, bytecodeKey)
}
// Cache the route
cacheData := make([]byte, 8+len(scriptPath))
copy(cacheData[:8], bytecodeKey)
copy(cacheData[8:], scriptPath)
r.routeCache.Set(routeCacheKey, cacheData)
return bytecode, scriptPath, node.err, params, true
}
// GetRouteInfoString is a convenience method that accepts strings
func (r *LuaRouter) GetRouteInfoString(method, path string) ([]byte, string, error, *Params, bool) {
return r.GetRouteInfo([]byte(method), []byte(path))
}