class Kemal::RouteHandler

Included Modules

Defined in:

kemal/route_handler.cr

Constant Summary

INSTANCE = new

Constructors

Instance Method Summary

Constructor Detail

def self.new #

[View source]

Instance Method Detail

def add_route(method : String, path : String, &handler : HTTP::Server::Context -> _) #

Adds a given route to routing tree.


[View source]
def allowed_methods(path : String) : Array(String) #

Returns every HTTP method registered for path, in Allow header order, or an empty array when nothing is routed there.

The radix tree is keyed by /METHOD/path, so there is no way to ask it which methods a path carries - each routable verb has to be looked up in turn. HEAD is reported wherever GET is, matching the HEAD -> GET fallback in #lookup_route.

Deliberately bypasses the route cache: this only runs for a request that already failed to match, and priming the LRU with one entry per verb would let mismatched requests evict the routes actually being served.

Only verbs the application actually registered are probed, so an app serving GET and POST pays two tree lookups here rather than one per routable verb - this is the path scanner traffic takes. The index is maintained by #add_route and rebuilt from the tree by #routes=, which are the two supported ways routes get in. A route pushed straight into the tree through the #routes getter bypasses both; that path degrades to the pre-405 answer of 404 rather than reporting a wrong Allow.

A ws route is reported as GET: the opening handshake is a GET (RFC 6455 ยง4.1), so a path carrying one does support the method. It gets no HEAD - the upgrade is the only thing served there, and Kemal::WebSocketHandler answers nothing else. The probe sits outside the @registered_methods shortcut above because that index only tracks HTTP routes, so a WebSocket-only app would otherwise report nothing at all. It is skipped once an HTTP route has already put GET in the list.

It adds one lookup in a separate, WebSocket-only tree to every miss: measured at ~40ns on top of the ~490ns a two-verb probe already costs (Crystal 1.21, --release, Apple M-series), and unchanged whether or not the app registers any ws routes.


[View source]
def cached_routes : Kemal::LRUCache(String, Radix::Result(Kemal::Route)) #

[View source]
def cached_routes=(cache : LRUCache(String, Radix::Result(Route))) #

Setter is synchronized for thread-safety when specs reset the cache.


[View source]
def call(context : HTTP::Server::Context) #

[View source]
def lookup_route(verb : String, path : String) #

Looks up the route from the Radix::Tree for the first time and caches to improve performance. Cache access is synchronized so multiple fibers can call this concurrently.


[View source]
def routes : Radix::Tree(Kemal::Route) #

[View source]
def routes=(routes : Radix::Tree(Route)) #

Rebuilds the verb index #allowed_methods probes with, so a tree handed over wholesale advertises the routes it already carries instead of none.


[View source]