File size: 3,065 Bytes
6a7089a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | package engine
import (
"log/slog"
"sync"
)
// Router selects the engine for each request.
//
// It evaluates an ordered list of RouteRules, picks the first non-Undecided
// verdict, and delegates to the matching engine. Rules can be added or
// removed at runtime (under a write lock) so that the routing strategy can
// evolve without modifying handlers or bridge code.
type Router struct {
mode Mode
lite Engine // may be nil when Mode == ModeChrome
rules []RouteRule
mu sync.RWMutex
}
// NewRouter creates a router for the given mode.
// Pass nil for lite when running in chrome-only mode.
func NewRouter(mode Mode, lite Engine) *Router {
r := &Router{
mode: mode,
lite: lite,
}
switch mode {
case ModeLite:
r.rules = []RouteRule{
CapabilityRule{}, // screenshot/pdf → chrome always
DefaultLiteRule{}, // everything else → lite
}
case ModeAuto:
r.rules = []RouteRule{
CapabilityRule{}, // chrome-only caps first
ContentHintRule{}, // static pages → lite
DefaultChromeRule{}, // fallback
}
default: // ModeChrome
r.rules = []RouteRule{
DefaultChromeRule{},
}
}
return r
}
// AddRule appends a rule to the chain. It is evaluated after all
// currently registered rules but before the default fallback.
func (r *Router) AddRule(rule RouteRule) {
r.mu.Lock()
defer r.mu.Unlock()
// Insert before the last rule (which is the fallback).
if len(r.rules) > 0 {
last := r.rules[len(r.rules)-1]
r.rules[len(r.rules)-1] = rule
r.rules = append(r.rules, last)
} else {
r.rules = append(r.rules, rule)
}
slog.Info("engine: rule added", "rule", rule.Name(), "total", len(r.rules))
}
// RemoveRule removes the first rule with the given name.
func (r *Router) RemoveRule(name string) bool {
r.mu.Lock()
defer r.mu.Unlock()
for i, rule := range r.rules {
if rule.Name() == name {
r.rules = append(r.rules[:i], r.rules[i+1:]...)
return true
}
}
return false
}
// Route returns the engine to use for a given operation and URL.
func (r *Router) Route(op Capability, url string) Engine {
r.mu.RLock()
defer r.mu.RUnlock()
for _, rule := range r.rules {
switch rule.Decide(op, url) {
case UseLite:
if r.lite != nil {
return r.lite
}
// lite unavailable — fall through
case UseChrome:
return nil // nil signals "use chrome bridge"
}
}
return nil // default: chrome
}
// UseLite returns true when the router would send this operation to the
// lite engine. Convenience helper for handler-level checks.
func (r *Router) UseLite(op Capability, url string) bool {
return r.Route(op, url) != nil
}
// Lite returns the lite engine (may be nil).
func (r *Router) Lite() Engine {
return r.lite
}
// Mode returns the configured engine mode.
func (r *Router) Mode() Mode {
return r.mode
}
// Rules returns a snapshot of the current rule names (for diagnostics).
func (r *Router) Rules() []string {
r.mu.RLock()
defer r.mu.RUnlock()
names := make([]string, len(r.rules))
for i, rule := range r.rules {
names[i] = rule.Name()
}
return names
}
|