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)) }