httprouter
httprouter copied to clipboard
httprouter 2.0 status and Routing priority over wildcards
Hi Julien,
I read the other issues that point to a 2.0 version where you intend to support routing priority such that more specific routes will take precedence over wildcards. Is this code in progress? Any way I can help out? If not, I have been working on the following:
- If a node has two or more children, and one is a wildcard, it receives the worst priority.
- Only one wildcard is allowed per node within the group of children.
Can you share your thoughts on the strategy you feel is necessary to implement this and keep the router fast?
package main
import (
"fmt"
"net/http"
"strings"
)
// Node represents a trie node in the routing tree
type Node struct {
segment string // Path segment (e.g., "users", "{id}", "*")
isWildcard bool // True for wildcard nodes (e.g., "*")
priority int // Higher priority for specific routes
handler http.Handler // Handler for the node (if any)
children []*Node // Child nodes
method string // HTTP method (e.g., "GET")
}
// Router is a custom Chi-like router
type Router struct {
trees map[string]*Node // Trees per HTTP method (e.g., "GET" -> root node)
}
// NewRouter creates a new router
func NewRouter() *Router {
return &Router{
trees: make(map[string]*Node),
}
}
// Get registers a GET route
func (r *Router) Get(pattern string, handler http.Handler) {
r.addRoute("GET", pattern, handler)
}
// addRoute adds a route to the trie
func (r *Router) addRoute(method, pattern string, handler http.Handler) {
if pattern == "" || pattern[0] != '/' {
panic("pattern must start with /")
}
// Get or create the method's tree
root, exists := r.trees[method]
if !exists {
root = &Node{}
r.trees[method] = root
}
// Split pattern into segments
segments := strings.Split(strings.Trim(pattern, "/"), "/")
current := root
for _, segment := range segments {
// Find or create child node
var child *Node
isWildcard := segment == "*" || strings.HasPrefix(segment, "{") && strings.Contains(segment, ":.*")
// Check for existing child with same segment
for _, c := range current.children {
if c.segment == segment {
child = c
break
}
}
// Enforce single wildcard per node
if isWildcard {
for _, c := range current.children {
if c.isWildcard {
panic(fmt.Sprintf("multiple wildcards not allowed at node %s", current.segment))
}
}
}
// Create new child if not found
if child == nil {
child = &Node{
segment: segment,
isWildcard: isWildcard,
priority: 1, // Default priority for specific routes
}
if isWildcard {
child.priority = 0 // Wildcards get lowest priority
}
current.children = append(current.children, child)
}
current = child
}
// Set handler at leaf node
if current.handler != nil {
panic(fmt.Sprintf("handler already registered for %s %s", method, pattern))
}
current.handler = handler
current.method = method
// Sort children by priority (non-wildcards first)
for i, child := range current.children {
for j := i + 1; j < len(current.children); j++ {
if current.children[j].priority > child.priority {
current.children[i], current.children[j] = current.children[j], current.children[i]
}
}
}
}
// ServeHTTP handles incoming requests
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
node := r.findNode(req.Method, req.URL.Path)
if node != nil && node.handler != nil {
node.handler.ServeHTTP(w, req)
return
}
http.NotFound(w, req)
}
// findNode looks up a node for the given method and path
func (r *Router) findNode(method, path string) *Node {
root, exists := r.trees[method]
if !exists {
return nil
}
segments := strings.Split(strings.Trim(path, "/"), "/")
current := root
for _, segment := range segments {
var next *Node
// Try matching non-wildcard children first (higher priority)
for _, child := range current.children {
if !child.isWildcard && child.segment == segment {
next = child
break
}
}
// If no specific match, try wildcard
if next == nil {
for _, child := range current.children {
if child.isWildcard {
next = child
break
}
}
}
if next == nil {
return nil
}
current = next
}
return current
}
// hello creates a handler that outputs "Hello <text>"
func hello(text string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello %s\n", text)
})
}
funct main() {
r := NewRouter()
// Specific routes
r.Get("/users/{id}", hello("User ID"))
r.Get("/users/profile", hello("Profile"))
// Wildcard route
r.Get("/users/*", hello("Wildcard"))
// Start server
fmt.Println("Server listening on :8080")
if err := http.ListenAndServe(":8080", r); err != nil {
fmt.Printf("Server error: %v\n", err)
}
}
这是来自QQ邮箱的假期自动回复邮件。 您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。