188 lines
4.7 KiB
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))
|
|
}
|