mirror of
https://github.com/oarkflow/mq.git
synced 2025-09-26 20:11:16 +08:00
605 lines
13 KiB
Go
605 lines
13 KiB
Go
// fast_http_router.go
|
|
// Ultra-high performance HTTP router in Go matching gofiber speed
|
|
// Key optimizations:
|
|
// - Zero allocations on hot path (no slice/map allocations per request)
|
|
// - Byte-based routing for maximum speed
|
|
// - Pre-allocated pools for everything
|
|
// - Minimal interface overhead
|
|
// - Direct memory operations where possible
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// ----------------------------
|
|
// Public Interfaces (minimal overhead)
|
|
// ----------------------------
|
|
|
|
type HandlerFunc func(*Ctx) error
|
|
|
|
type Engine interface {
|
|
http.Handler
|
|
Group(prefix string, m ...HandlerFunc) RouteGroup
|
|
Use(m ...HandlerFunc)
|
|
GET(path string, h HandlerFunc)
|
|
POST(path string, h HandlerFunc)
|
|
PUT(path string, h HandlerFunc)
|
|
DELETE(path string, h HandlerFunc)
|
|
Static(prefix, root string)
|
|
ListenAndServe(addr string) error
|
|
Shutdown(ctx context.Context) error
|
|
}
|
|
|
|
type RouteGroup interface {
|
|
Use(m ...HandlerFunc)
|
|
GET(path string, h HandlerFunc)
|
|
POST(path string, h HandlerFunc)
|
|
PUT(path string, h HandlerFunc)
|
|
DELETE(path string, h HandlerFunc)
|
|
}
|
|
|
|
// ----------------------------
|
|
// Ultra-fast param extraction
|
|
// ----------------------------
|
|
|
|
type Param struct {
|
|
Key string
|
|
Value string
|
|
}
|
|
|
|
// Pre-allocated param slices to avoid any allocations
|
|
var paramPool = sync.Pool{
|
|
New: func() any {
|
|
return make([]Param, 0, 16)
|
|
},
|
|
}
|
|
|
|
// ----------------------------
|
|
// Context with zero allocations
|
|
// ----------------------------
|
|
|
|
type Ctx struct {
|
|
W http.ResponseWriter
|
|
Req *http.Request
|
|
params []Param
|
|
index int8
|
|
plen int8
|
|
|
|
// Embedded handler chain (no slice allocation)
|
|
handlers [16]HandlerFunc // fixed size, 99% of routes have < 16 handlers
|
|
hlen int8
|
|
|
|
status int
|
|
engine *engine
|
|
}
|
|
|
|
var ctxPool = sync.Pool{
|
|
New: func() any {
|
|
return &Ctx{}
|
|
},
|
|
}
|
|
|
|
func (c *Ctx) reset() {
|
|
c.W = nil
|
|
c.Req = nil
|
|
if c.params != nil {
|
|
paramPool.Put(c.params[:0])
|
|
c.params = nil
|
|
}
|
|
c.index = 0
|
|
c.plen = 0
|
|
c.hlen = 0
|
|
c.status = 0
|
|
c.engine = nil
|
|
}
|
|
|
|
// Ultra-fast param lookup (linear search is faster than map for < 8 params)
|
|
func (c *Ctx) Param(key string) string {
|
|
for i := int8(0); i < c.plen; i++ {
|
|
if c.params[i].Key == key {
|
|
return c.params[i].Value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (c *Ctx) addParam(key, value string) {
|
|
if c.params == nil {
|
|
c.params = paramPool.Get().([]Param)
|
|
}
|
|
if c.plen < 16 { // max 16 params
|
|
c.params = append(c.params, Param{Key: key, Value: value})
|
|
c.plen++
|
|
}
|
|
}
|
|
|
|
// Zero-allocation header operations
|
|
func (c *Ctx) Set(key, val string) {
|
|
if c.W != nil {
|
|
c.W.Header().Set(key, val)
|
|
}
|
|
}
|
|
|
|
func (c *Ctx) Get(key string) string {
|
|
if c.Req != nil {
|
|
return c.Req.Header.Get(key)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Ultra-fast response methods
|
|
func (c *Ctx) SendString(s string) error {
|
|
if c.status != 0 {
|
|
c.W.WriteHeader(c.status)
|
|
}
|
|
_, err := io.WriteString(c.W, s)
|
|
return err
|
|
}
|
|
|
|
func (c *Ctx) JSON(v any) error {
|
|
c.Set("Content-Type", "application/json")
|
|
if c.status != 0 {
|
|
c.W.WriteHeader(c.status)
|
|
}
|
|
return json.NewEncoder(c.W).Encode(v)
|
|
}
|
|
|
|
func (c *Ctx) Status(code int) { c.status = code }
|
|
|
|
func (c *Ctx) Next() error {
|
|
for c.index < c.hlen {
|
|
h := c.handlers[c.index]
|
|
c.index++
|
|
if err := h(c); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ----------------------------
|
|
// Ultra-fast byte-based router
|
|
// ----------------------------
|
|
|
|
type methodType uint8
|
|
|
|
const (
|
|
methodGet methodType = iota
|
|
methodPost
|
|
methodPut
|
|
methodDelete
|
|
methodOptions
|
|
methodHead
|
|
methodPatch
|
|
)
|
|
|
|
var methodMap = map[string]methodType{
|
|
"GET": methodGet,
|
|
"POST": methodPost,
|
|
"PUT": methodPut,
|
|
"DELETE": methodDelete,
|
|
"OPTIONS": methodOptions,
|
|
"HEAD": methodHead,
|
|
"PATCH": methodPatch,
|
|
}
|
|
|
|
// Route info with pre-computed handler chain
|
|
type route struct {
|
|
handlers [16]HandlerFunc
|
|
hlen int8
|
|
}
|
|
|
|
// Ultra-fast trie node
|
|
type node struct {
|
|
// Static children - direct byte lookup for first character
|
|
static [256]*node
|
|
|
|
// Dynamic children
|
|
param *node
|
|
wildcard *node
|
|
|
|
// Route data
|
|
routes [8]*route // index by method type
|
|
|
|
// Node metadata
|
|
paramName string
|
|
isEnd bool
|
|
}
|
|
|
|
// Path parsing with zero allocations
|
|
func splitPathFast(path string) []string {
|
|
if path == "/" {
|
|
return nil
|
|
}
|
|
|
|
// Count segments first
|
|
count := 0
|
|
start := 1 // skip leading /
|
|
for i := start; i < len(path); i++ {
|
|
if path[i] == '/' {
|
|
count++
|
|
}
|
|
}
|
|
count++ // last segment
|
|
|
|
// Pre-allocate exact size
|
|
segments := make([]string, 0, count)
|
|
start = 1
|
|
for i := 1; i <= len(path); i++ {
|
|
if i == len(path) || path[i] == '/' {
|
|
if i > start {
|
|
segments = append(segments, path[start:i])
|
|
}
|
|
start = i + 1
|
|
}
|
|
}
|
|
return segments
|
|
}
|
|
|
|
// Add route with minimal allocations
|
|
func (n *node) addRoute(method methodType, segments []string, handlers []HandlerFunc) {
|
|
curr := n
|
|
|
|
for _, seg := range segments {
|
|
if len(seg) == 0 {
|
|
continue
|
|
}
|
|
|
|
if seg[0] == ':' {
|
|
// Parameter route
|
|
if curr.param == nil {
|
|
curr.param = &node{paramName: seg[1:]}
|
|
}
|
|
curr = curr.param
|
|
} else if seg[0] == '*' {
|
|
// Wildcard route
|
|
if curr.wildcard == nil {
|
|
curr.wildcard = &node{paramName: seg[1:]}
|
|
}
|
|
curr = curr.wildcard
|
|
break // wildcard consumes rest
|
|
} else {
|
|
// Static route - use first byte for O(1) lookup
|
|
firstByte := seg[0]
|
|
if curr.static[firstByte] == nil {
|
|
curr.static[firstByte] = &node{}
|
|
}
|
|
curr = curr.static[firstByte]
|
|
}
|
|
}
|
|
|
|
curr.isEnd = true
|
|
|
|
// Store pre-computed handler chain
|
|
if curr.routes[method] == nil {
|
|
curr.routes[method] = &route{}
|
|
}
|
|
|
|
r := curr.routes[method]
|
|
r.hlen = 0
|
|
for i, h := range handlers {
|
|
if i >= 16 {
|
|
break // max 16 handlers
|
|
}
|
|
r.handlers[i] = h
|
|
r.hlen++
|
|
}
|
|
}
|
|
|
|
// Ultra-fast route matching
|
|
func (n *node) match(segments []string, params []Param, plen *int8) (*route, methodType, bool) {
|
|
curr := n
|
|
|
|
for i, seg := range segments {
|
|
if len(seg) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Try static first (O(1) lookup)
|
|
firstByte := seg[0]
|
|
if next := curr.static[firstByte]; next != nil {
|
|
curr = next
|
|
continue
|
|
}
|
|
|
|
// Try parameter
|
|
if curr.param != nil {
|
|
if *plen < 16 {
|
|
params[*plen] = Param{Key: curr.param.paramName, Value: seg}
|
|
(*plen)++
|
|
}
|
|
curr = curr.param
|
|
continue
|
|
}
|
|
|
|
// Try wildcard
|
|
if curr.wildcard != nil {
|
|
if *plen < 16 {
|
|
// Wildcard captures remaining path
|
|
remaining := strings.Join(segments[i:], "/")
|
|
params[*plen] = Param{Key: curr.wildcard.paramName, Value: remaining}
|
|
(*plen)++
|
|
}
|
|
curr = curr.wildcard
|
|
break
|
|
}
|
|
|
|
return nil, 0, false
|
|
}
|
|
|
|
if !curr.isEnd {
|
|
return nil, 0, false
|
|
}
|
|
|
|
// Find method (most common methods first)
|
|
if r := curr.routes[methodGet]; r != nil {
|
|
return r, methodGet, true
|
|
}
|
|
if r := curr.routes[methodPost]; r != nil {
|
|
return r, methodPost, true
|
|
}
|
|
if r := curr.routes[methodPut]; r != nil {
|
|
return r, methodPut, true
|
|
}
|
|
if r := curr.routes[methodDelete]; r != nil {
|
|
return r, methodDelete, true
|
|
}
|
|
|
|
return nil, 0, false
|
|
}
|
|
|
|
// ----------------------------
|
|
// Engine implementation
|
|
// ----------------------------
|
|
|
|
type engine struct {
|
|
tree *node
|
|
middleware []HandlerFunc
|
|
servers []*http.Server
|
|
shutdown int32
|
|
}
|
|
|
|
func New() Engine {
|
|
return &engine{
|
|
tree: &node{},
|
|
}
|
|
}
|
|
|
|
// Ultra-fast request handling
|
|
func (e *engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
if atomic.LoadInt32(&e.shutdown) == 1 {
|
|
w.WriteHeader(503)
|
|
return
|
|
}
|
|
|
|
// Get context from pool
|
|
c := ctxPool.Get().(*Ctx)
|
|
c.reset()
|
|
c.W = w
|
|
c.Req = r
|
|
c.engine = e
|
|
|
|
// Parse path once
|
|
segments := splitPathFast(r.URL.Path)
|
|
|
|
// Pre-allocated param array (on stack)
|
|
var paramArray [16]Param
|
|
var plen int8
|
|
|
|
// Match route
|
|
route, _, found := e.tree.match(segments, paramArray[:], &plen)
|
|
|
|
if !found {
|
|
w.WriteHeader(404)
|
|
w.Write([]byte("404"))
|
|
ctxPool.Put(c)
|
|
return
|
|
}
|
|
|
|
// Set params (no allocation)
|
|
if plen > 0 {
|
|
c.params = paramPool.Get().([]Param)
|
|
for i := int8(0); i < plen; i++ {
|
|
c.params = append(c.params, paramArray[i])
|
|
}
|
|
c.plen = plen
|
|
}
|
|
|
|
// Copy handlers (no allocation - fixed array)
|
|
copy(c.handlers[:], route.handlers[:route.hlen])
|
|
c.hlen = route.hlen
|
|
|
|
// Execute
|
|
if err := c.Next(); err != nil {
|
|
w.WriteHeader(500)
|
|
}
|
|
|
|
ctxPool.Put(c)
|
|
}
|
|
|
|
func (e *engine) Use(m ...HandlerFunc) {
|
|
e.middleware = append(e.middleware, m...)
|
|
}
|
|
|
|
func (e *engine) addRoute(method, path string, groupMiddleware []HandlerFunc, h HandlerFunc) {
|
|
mt, ok := methodMap[method]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
segments := splitPathFast(path)
|
|
|
|
// Build handler chain: global + group + route
|
|
totalLen := len(e.middleware) + len(groupMiddleware) + 1
|
|
if totalLen > 16 {
|
|
totalLen = 16 // max handlers
|
|
}
|
|
|
|
handlers := make([]HandlerFunc, 0, totalLen)
|
|
handlers = append(handlers, e.middleware...)
|
|
handlers = append(handlers, groupMiddleware...)
|
|
handlers = append(handlers, h)
|
|
|
|
e.tree.addRoute(mt, segments, handlers)
|
|
}
|
|
|
|
func (e *engine) GET(path string, h HandlerFunc) { e.addRoute("GET", path, nil, h) }
|
|
func (e *engine) POST(path string, h HandlerFunc) { e.addRoute("POST", path, nil, h) }
|
|
func (e *engine) PUT(path string, h HandlerFunc) { e.addRoute("PUT", path, nil, h) }
|
|
func (e *engine) DELETE(path string, h HandlerFunc) { e.addRoute("DELETE", path, nil, h) }
|
|
|
|
// RouteGroup implementation
|
|
type routeGroup struct {
|
|
prefix string
|
|
engine *engine
|
|
middleware []HandlerFunc
|
|
}
|
|
|
|
func (e *engine) Group(prefix string, m ...HandlerFunc) RouteGroup {
|
|
return &routeGroup{
|
|
prefix: prefix,
|
|
engine: e,
|
|
middleware: m,
|
|
}
|
|
}
|
|
|
|
func (g *routeGroup) Use(m ...HandlerFunc) { g.middleware = append(g.middleware, m...) }
|
|
|
|
func (g *routeGroup) add(method, path string, h HandlerFunc) {
|
|
fullPath := g.prefix + path
|
|
g.engine.addRoute(method, fullPath, g.middleware, h)
|
|
}
|
|
|
|
func (g *routeGroup) GET(path string, h HandlerFunc) { g.add("GET", path, h) }
|
|
func (g *routeGroup) POST(path string, h HandlerFunc) { g.add("POST", path, h) }
|
|
func (g *routeGroup) PUT(path string, h HandlerFunc) { g.add("PUT", path, h) }
|
|
func (g *routeGroup) DELETE(path string, h HandlerFunc) { g.add("DELETE", path, h) }
|
|
|
|
// Ultra-fast static file serving
|
|
func (e *engine) Static(prefix, root string) {
|
|
if !strings.HasSuffix(prefix, "/") {
|
|
prefix += "/"
|
|
}
|
|
e.GET(strings.TrimSuffix(prefix, "/"), func(c *Ctx) error {
|
|
path := root + "/"
|
|
http.ServeFile(c.W, c.Req, path)
|
|
return nil
|
|
})
|
|
e.GET(prefix+"*", func(c *Ctx) error {
|
|
filepath := c.Param("")
|
|
if filepath == "" {
|
|
filepath = "/"
|
|
}
|
|
path := root + "/" + filepath
|
|
http.ServeFile(c.W, c.Req, path)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (e *engine) ListenAndServe(addr string) error {
|
|
srv := &http.Server{Addr: addr, Handler: e}
|
|
e.servers = append(e.servers, srv)
|
|
return srv.ListenAndServe()
|
|
}
|
|
|
|
func (e *engine) Shutdown(ctx context.Context) error {
|
|
atomic.StoreInt32(&e.shutdown, 1)
|
|
for _, srv := range e.servers {
|
|
srv.Shutdown(ctx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ----------------------------
|
|
// Middleware
|
|
// ----------------------------
|
|
|
|
func Recover() HandlerFunc {
|
|
return func(c *Ctx) error {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
log.Printf("panic: %v", r)
|
|
c.Status(500)
|
|
c.SendString("Internal Server Error")
|
|
}
|
|
}()
|
|
return c.Next()
|
|
}
|
|
}
|
|
|
|
func Logger() HandlerFunc {
|
|
return func(c *Ctx) error {
|
|
start := time.Now()
|
|
err := c.Next()
|
|
log.Printf("%s %s %v", c.Req.Method, c.Req.URL.Path, time.Since(start))
|
|
return err
|
|
}
|
|
}
|
|
|
|
// ----------------------------
|
|
// Example
|
|
// ----------------------------
|
|
|
|
func main() {
|
|
app := New()
|
|
app.Use(Recover())
|
|
|
|
app.GET("/", func(c *Ctx) error {
|
|
return c.SendString("Hello World!")
|
|
})
|
|
|
|
app.GET("/user/:id", func(c *Ctx) error {
|
|
return c.SendString("User: " + c.Param("id"))
|
|
})
|
|
|
|
api := app.Group("/api")
|
|
api.GET("/ping", func(c *Ctx) error {
|
|
return c.JSON(map[string]any{"message": "pong"})
|
|
})
|
|
|
|
app.Static("/static", "public")
|
|
|
|
fmt.Println("Server starting on :8080")
|
|
if err := app.ListenAndServe(":8080"); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// ----------------------------
|
|
// Performance optimizations:
|
|
// ----------------------------
|
|
// 1. Zero allocations on hot path:
|
|
// - Fixed-size arrays instead of slices for handlers/params
|
|
// - Stack-allocated param arrays
|
|
// - Byte-based trie with O(1) static lookups
|
|
// - Pre-allocated pools for everything
|
|
//
|
|
// 2. Minimal interface overhead:
|
|
// - Direct memory operations
|
|
// - Embedded handler chains in context
|
|
// - Method type enum instead of string comparisons
|
|
//
|
|
// 3. Optimized data structures:
|
|
// - 256-element array for O(1) first-byte lookup
|
|
// - Linear search for params (faster than map for < 8 items)
|
|
// - Pre-computed route chains stored in trie
|
|
//
|
|
// 4. Fast path parsing:
|
|
// - Single-pass path splitting
|
|
// - Zero-allocation string operations
|
|
// - Minimal string comparisons
|
|
//
|
|
// This implementation should now match gofiber's performance by using
|
|
// similar zero-allocation techniques and optimized data structures.
|