191 lines
4.4 KiB
Go
191 lines
4.4 KiB
Go
package router
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// node represents a node in the radix trie
|
|
type node struct {
|
|
// Static children mapped by path segment
|
|
staticChild map[string]*node
|
|
|
|
// Parameter child for dynamic segments (e.g., :id)
|
|
paramChild *node
|
|
paramName string
|
|
|
|
// Handler information
|
|
handler string // Path to the handler file
|
|
indexFile string // Path to index.lua if exists
|
|
modTime time.Time // Modification time of the handler
|
|
fsPath string // Filesystem path (includes groups)
|
|
|
|
// Compilation error if any
|
|
err error
|
|
}
|
|
|
|
// pathInfo holds both URL path and filesystem path
|
|
type pathInfo struct {
|
|
urlPath string // URL path without groups (e.g., /users)
|
|
fsPath string // Filesystem path with groups (e.g., /(admin)/users)
|
|
}
|
|
|
|
// parsePathWithGroups parses a filesystem path, extracting groups
|
|
func parsePathWithGroups(fsPath string) *pathInfo {
|
|
segments := strings.Split(strings.Trim(fsPath, "/"), "/")
|
|
var urlSegments []string
|
|
|
|
for _, segment := range segments {
|
|
if segment == "" {
|
|
continue
|
|
}
|
|
|
|
// Skip group segments (enclosed in parentheses)
|
|
if strings.HasPrefix(segment, "(") && strings.HasSuffix(segment, ")") {
|
|
continue
|
|
}
|
|
|
|
urlSegments = append(urlSegments, segment)
|
|
}
|
|
|
|
urlPath := "/"
|
|
if len(urlSegments) > 0 {
|
|
urlPath = "/" + strings.Join(urlSegments, "/")
|
|
}
|
|
|
|
return &pathInfo{
|
|
urlPath: urlPath,
|
|
fsPath: fsPath,
|
|
}
|
|
}
|
|
|
|
// findOrCreateNode finds or creates a node at the given URL path
|
|
func (r *LuaRouter) findOrCreateNode(root *node, urlPath string) *node {
|
|
segments := strings.Split(strings.Trim(urlPath, "/"), "/")
|
|
if len(segments) == 1 && segments[0] == "" {
|
|
return root
|
|
}
|
|
|
|
current := root
|
|
for _, segment := range segments {
|
|
if segment == "" {
|
|
continue
|
|
}
|
|
|
|
// Check if it's a parameter
|
|
if strings.HasPrefix(segment, ":") {
|
|
paramName := segment[1:]
|
|
if current.paramChild == nil {
|
|
current.paramChild = &node{
|
|
staticChild: make(map[string]*node),
|
|
paramName: paramName,
|
|
}
|
|
}
|
|
current = current.paramChild
|
|
} else {
|
|
// Static segment
|
|
if _, exists := current.staticChild[segment]; !exists {
|
|
current.staticChild[segment] = &node{
|
|
staticChild: make(map[string]*node),
|
|
}
|
|
}
|
|
current = current.staticChild[segment]
|
|
}
|
|
}
|
|
|
|
return current
|
|
}
|
|
|
|
// addRoute adds a route to the tree
|
|
func (r *LuaRouter) addRoute(root *node, pathInfo *pathInfo, handlerPath string, modTime time.Time) {
|
|
node := r.findOrCreateNode(root, pathInfo.urlPath)
|
|
node.handler = handlerPath
|
|
node.modTime = modTime
|
|
node.fsPath = pathInfo.fsPath
|
|
|
|
// Compile the route with middleware
|
|
r.compileWithMiddleware(node, pathInfo.fsPath, handlerPath)
|
|
|
|
// Track failed routes
|
|
if node.err != nil {
|
|
key := filepath.Base(handlerPath) + ":" + pathInfo.urlPath
|
|
r.failedRoutes[key] = &RouteError{
|
|
Path: pathInfo.urlPath,
|
|
Method: strings.ToUpper(strings.TrimSuffix(filepath.Base(handlerPath), ".lua")),
|
|
ScriptPath: handlerPath,
|
|
Err: node.err,
|
|
}
|
|
}
|
|
}
|
|
|
|
// nodeForHandler finds a node by its handler path
|
|
func (r *LuaRouter) nodeForHandler(handlerPath string) (*node, bool) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
for _, root := range r.routes {
|
|
if node := findNodeByHandler(root, handlerPath); node != nil {
|
|
return node, true
|
|
}
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
// findNodeByHandler recursively searches for a node with the given handler path
|
|
func findNodeByHandler(n *node, handlerPath string) *node {
|
|
if n.handler == handlerPath || n.indexFile == handlerPath {
|
|
return n
|
|
}
|
|
|
|
// Search static children
|
|
for _, child := range n.staticChild {
|
|
if found := findNodeByHandler(child, handlerPath); found != nil {
|
|
return found
|
|
}
|
|
}
|
|
|
|
// Search param child
|
|
if n.paramChild != nil {
|
|
if found := findNodeByHandler(n.paramChild, handlerPath); found != nil {
|
|
return found
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// countNodesAndBytecode counts nodes and bytecode size in the tree
|
|
func countNodesAndBytecode(n *node) (int, int64) {
|
|
if n == nil {
|
|
return 0, 0
|
|
}
|
|
|
|
count := 0
|
|
bytes := int64(0)
|
|
|
|
// Count this node if it has a handler
|
|
if n.handler != "" || n.indexFile != "" {
|
|
count = 1
|
|
// Estimate bytecode size (would need actual bytecode cache lookup for accuracy)
|
|
bytes = 1024 // Placeholder
|
|
}
|
|
|
|
// Count static children
|
|
for _, child := range n.staticChild {
|
|
c, b := countNodesAndBytecode(child)
|
|
count += c
|
|
bytes += b
|
|
}
|
|
|
|
// Count param child
|
|
if n.paramChild != nil {
|
|
c, b := countNodesAndBytecode(n.paramChild)
|
|
count += c
|
|
bytes += b
|
|
}
|
|
|
|
return count, bytes
|
|
}
|