Class: Hyperion::Server::RouteTable
- Inherits:
-
Object
- Object
- Hyperion::Server::RouteTable
- Defined in:
- lib/hyperion/server/route_table.rb
Overview
2.10-D — direct-dispatch route registry. Mirrors agoo’s ‘Agoo::Server.handle(:GET, “/hello”, handler)` design: a lookup table indexed by HTTP method then exact-match path. When a match hits inside `Connection#serve` (after parse, before the Rack adapter), the dispatcher skips the env-hash build, the middleware chain, and the body-iteration overhead — the handler is called directly with a `Hyperion::Request` value object and returns either a `[status, headers, body]` Rack tuple or a sentinel that points at a pre-built static response buffer (the `handle_static` path, where the response bytes are baked at registration time so the hot path is one `socket.write` syscall and zero Ruby allocation past the Connection ivars).
The table is per-process: forked workers each inherit a copy of the parent’s table at fork time (no IPC, no shared memory) so registrations made before ‘Server.start` propagate to every worker via copy-on-write. Registrations made AFTER fork (e.g. from `on_worker_boot`) only affect the calling worker — by design, this is the operator’s escape hatch for per-worker routing. The hot-path lookup is a two-Hash-key access (O(1)); write paths are guarded by a Mutex so a registration racing with an in-flight request lookup is safe.
Mutability invariant: registrations replace any prior entry for the same ‘(method, path)` tuple — last writer wins. No delete API for now (operator restarts to clear), keeps the public surface minimal.
Defined Under Namespace
Classes: StaticEntry
Constant Summary collapse
- KNOWN_METHODS =
The seven HTTP methods agoo’s ‘Server.handle` accepts. We match agoo’s surface verbatim so apps porting their registrations across servers don’t have to relearn the matrix. ‘OPTIONS` is included for CORS preflight handlers (commonly registered as direct routes since they have no business model).
%i[GET POST PUT DELETE HEAD PATCH OPTIONS].freeze
Instance Method Summary collapse
-
#clear ⇒ Object
Clear all registrations.
-
#initialize ⇒ RouteTable
constructor
A new instance of RouteTable.
-
#lookup(method_str, path) ⇒ Object
Hot-path lookup.
-
#register(method_sym, path, handler) ⇒ Object
Register a direct-dispatch handler for the given method + path.
-
#size ⇒ Object
Inspection helper — returns the count of registered routes across all methods.
Constructor Details
#initialize ⇒ RouteTable
Returns a new instance of RouteTable.
89 90 91 92 93 94 95 96 |
# File 'lib/hyperion/server/route_table.rb', line 89 def initialize # Per-method Hash so the lookup is `@routes[:GET][path]` # — two integer-keyed-Hash hits. Pre-allocate the seven # slots so the request hot path never lazily creates an # entry under a misspelled method (we just miss). @routes = KNOWN_METHODS.each_with_object({}) { |m, h| h[m] = {} } @mutex = Mutex.new end |
Instance Method Details
#clear ⇒ Object
Clear all registrations. Test / spec seam — production code restarts the process to drop routes.
147 148 149 150 |
# File 'lib/hyperion/server/route_table.rb', line 147 def clear @mutex.synchronize { @routes.each_value(&:clear) } nil end |
#lookup(method_str, path) ⇒ Object
Hot-path lookup. ‘method_str` is the request method as the parser produced it (a String like `’GET’‘); `path` is the request path String. Returns the registered handler or nil.
No mutex on the read side — Ruby Hash reads under MRI are safe against a concurrent write that’s mutex-guarded (the GVL pins the writer during the bucket update), and the cost of a Mutex acquire on every request would defeat the whole point of the fast path.
130 131 132 133 134 135 136 |
# File 'lib/hyperion/server/route_table.rb', line 130 def lookup(method_str, path) method_key = METHOD_LOOKUP[method_str] || normalize_method(method_str) table = @routes[method_key] return nil unless table table[path] end |
#register(method_sym, path, handler) ⇒ Object
Register a direct-dispatch handler for the given method + path. ‘handler` must respond to `#call(request)` and return either:
* a `[status, headers, body]` Rack tuple — the dispatcher
writes it via the standard ResponseWriter (no env hash,
no middleware), or
* a `StaticEntry` (built only via `Server.handle_static`)
— the dispatcher emits the pre-built bytes in one
write syscall.
‘method_sym` is upper-cased before lookup so callers may pass `:get` or `’get’‘ interchangeably with `:GET`.
111 112 113 114 115 116 117 118 119 |
# File 'lib/hyperion/server/route_table.rb', line 111 def register(method_sym, path, handler) method_key = normalize_method(method_sym) raise ArgumentError, "unknown method #{method_sym.inspect}" unless KNOWN_METHODS.include?(method_key) raise ArgumentError, 'path must be a String' unless path.is_a?(String) raise ArgumentError, 'handler must respond to #call' unless handler.respond_to?(:call) @mutex.synchronize { @routes[method_key][path.dup.freeze] = handler } handler end |
#size ⇒ Object
Inspection helper — returns the count of registered routes across all methods. Used by specs and the bench harness to assert registrations took effect.
141 142 143 |
# File 'lib/hyperion/server/route_table.rb', line 141 def size @routes.values.sum(&:size) end |