class Athena::Routing::Route
inherits Reference
#
Provides an object-oriented way to represent an HTTP route, including the path, methods, schemes, host, and/or conditions required for it to match.
Ultimately, ART::Route
s are compiled into ART::CompiledRoute
that represents an immutable
snapshot of a route, along with ART::CompiledRoute::Token
s representing each route parameter.
By default, a route is very liberal in regards to what allows when matching.
E.g. Matching anything that matches the path
, but with any HTTP method and any scheme.
The methods
and schemes
properties can be used to restrict which methods/schemes the route allows.
# This route will only handle `https` `POST` requests to `/path`.
route1 = ART::Route.new "/path", schemes: "https", methods: "POST"
# This route will handle `http` or `ftp` `GET`/`PATCH` requests to `/path`.
route2 = ART::Route.new "/path", schemes: {"https", "ftp"}, methods: {"GET", "PATCH"}
Expressions#
In some cases you may want to match a route using arbitrary dynamic runtime logic.
An example use case for this could be checking a request header, or anything else on the underlying ART::RequestContext
and/or ART::Request
instance.
The condition
property can be used for just this purpose:
route = ART::Route.new "/contact"
route.condition do |context, request|
request.headers["user-agent"].includes? "Firefox"
end
This route would only match requests whose user-agent
header includes Firefox
.
Be sure to also handle cases where headers may not be set.
Warning
Route conditions are NOT taken into consideration when generating routes via an ART::Generator::Interface
.
Parameters#
Route parameters represent variable portions within a route's path
.
Parameters are uniquely named placeholders wrapped within curly braces.
For example, /blog/{slug}
includes a slug
parameter.
Routes can have more than one parameter, but each one may only map to a single value.
Parameter placeholders may also be included with static portions for a string, such as /blog/posts-about-{category}
.
This can be useful for supporting format based URLs, such as /users.json
or /users.csv
via a /users.{_format}
path.
Parameter Validation#
By default, a placeholder is happy to accept any value.
However in most cases you will want to restrict which values it allows, such as ensuring only numeric digits are allowed for a page
parameter.
Parameter validation also allows multiple routes to have variable portions within the same location.
I.e. allowing /blog/{slug}
and /blog/{page}
to co-exist, which is a limitation for some other Crystal routers.
The requirements
property accepts a Hash(String, String | Regex)
where the keys are the name of the parameter and the value is a pattern
in which the value must match for the route to match. The value can either be a string for exact matches, or a Regex
for more complex patterns.
Route parameters may also be inlined within the path
by putting the pattern within <>
, instead of providing it as a dedicated argument.
For example, /blog/{page<\\d+>}
(note we need to escape the \
within a string literal).
routes = ART::RouteCollection.new
routes.add "blog_list", ART::Route.new "/blog/{page}", requirements: {"page" => /\d+/}
routes.add "blog_show", ART::Route.new "/blog/{slug}"
matcher.match "/blog/foo" # => {"_route" => "blog_show", "slug" => "foo"}
matcher.match "/blog/10" # => {"_route" => "blog_list", "page" => "10"}
Tip
Checkout ART::Requirement
for a set of common, helpful requirement regexes.
Optional Parameters#
By default, all parameters are required, meaning given the path /blog/{page}
, /blog/10
would match but /blog
would NOT match.
Parameters can be made optional by providing a default value for the parameter, for example:
ART::Route.new "/blog/{page}", {"page" => 1}, {"page" => /\d+/}
# ...
matcher.match "/blog" # => {"_route" => "blog_list", "page" => "1"}
Caution
More than one parameter may have a default value, but everything after an optional parameter must also be optional.
For example within /{page}/blog
, page
will always be required and /blog
will NOT match.
defaults
may also be inlined within the path
by putting the value after a ?
.
This is also compatible with requirements
, allowing both to be defined within a path.
For example /blog/{page<\\d+>?1}
.
Tip
The default value for a parameter may also be nil
, with the inline syntax being adding a ?
with no following value, e.g. {page?}
.
Be sure to update any type restrictions to be nilable as well.
Priority Parameter#
When determining which route should match, the first matching route will win. For example, if two routes were added with variable parameters in the same location, the first one that was added would match regardless of what their requirements are. In most cases this will not be a problem, but in some cases you may need to ensure a particular route is checked first.
Special Parameters#
The routing component comes with a few standardized parameters that have special meanings. These parameters could be leveraged within the underlying implementation, but are not directly used within the routing component other than for matching.
_format
- Could be used to set the underlying format of the request, as well as determining the content-type of the response._fragment
- Represents the fragment identifier when generating a URL. E.g./article/10#summary
with the fragment beingsummary
._locale
- Could be used to set the underlying locale of theART::Request
based on which route is matched.
ART::Route.new(
"/articles/{_locale}/search.{_format}",
{
"_locale" => "en",
"_format" => "html",
},
{
"_locale" => /en|fr/,
"_format" => /html|xml/,
}
)
This route supports en
and fr
locales in either html
or xml
formats with a default of en
and html
.
Tip
The trailing .
is optional if the parameter to the right has a default.
E.g. /articles/en/search
would match with a format of html
but /articles/en/search.xml
would be required for matching non-default formats.
Extra Parameters#
The defaults defined within a route do not all need to be present as route parameters. This could be useful to provide extra context to the controller that should handle the request.
ART::Route.new "/blog/{page}", {"page" => 1, "title" => "Hello world!"}
Slash Characters in Route Parameters#
By default, route parameters may include any value except a /
, since that's the character used to separate the different portions of the URL.
Route parameter matching logic may be made more permissive by using a more liberal regex, such as .+
, for example:
ART::Route.new "/share/{token}", requirements: {"token" => /.+/}
Special parameters should NOT be made more permissive.
For example, if the pattern is /share/{token}.{_format}
and {token}
allows any character, the /share/foo/bar.json
URL will consider foo/bar.json
as the token and the format will be empty.
This can be solved by replacing the .+
requirement with [^.]+
to allow any character except dots.
Related to this, allowing multiple parameters to accept /
may also lead to unexpected results.
Sub-Domain Routing#
The host
property can be used to require the HTTP host header to match this value in order for the route to match.
mobile_homepage = ART::Route.new "/", host: "m.example.com"
homepage = ART::Route.new "/"
In this example, both routes match the same path, but one requires a specific hostname.
The host
parameter can also be used as route parameters, including defaults
and requirements
support:
mobile_homepage = ART::Route.new(
"/",
{"subdomain" => "m"},
{"subdomain" => /m|mobile/},
"{subdomain}.example.com"
)
homepage = ART::Route.new "/"
Tip
Inline defaults and requirements also works for host
values, "{subdomain<m|mobile>?m}.example.com"
.
Constructors#
.new(path : String, defaults : Hash(String, _) = Hash(String, String | ::Nil).new, requirements : Hash(String, Regex | String) = Hash(String, Regex | String).new, host : String | Regex | Nil = nil, methods : String | Enumerable(String) | Nil = nil, schemes : String | Enumerable(String) | Nil = nil, condition : ART::Route::Condition | Nil = nil)
#
Methods#
#add_defaults(defaults : Hash(String, _)) : self
#
Adds the provided defaults, overriding previously set values.
#add_requirements(requirements : Hash(String, Regex | String)) : self
#
Adds the provided requirements, overriding previously set values.
#clone
#
Returns a copy of self
with all instance variables cloned.
#compile : CompiledRoute
#
Compiles and returns an ART::CompiledRoute
representing this route.
The route is only compiled once and future calls to this method will return the same compiled route,
assuming no changes were made to this route in between.
#condition : Condition | ::Nil
#
Returns the optional ART::Route::Condition
callback used to determine if this route should match.
See Routing Expressions for more information.
#condition : self
#
Sets the optional ART::Route::Condition
callback used to determine if this route should match.
route = ART::Route.new "/foo"
route.condition do |context, request|
request.headers["user-agent"].includes? "Firefox"
end
See Routing Expressions for more information.
#condition=(condition : Condition | Nil)
#
Returns the optional ART::Route::Condition
callback used to determine if this route should match.
See Routing Expressions for more information.
#default(key : String) : String | Nil
#
Returns the default with the provided key, if any.
#defaults : Hash(String, String | ::Nil)
#
Returns a hash representing the default values of a route's parameters if they were not provided in the request. See Optional Parameters for more information.
#defaults=(defaults : Hash(String, _)) : self
#
Sets the hash representing the default values of a route's parameters if they were not provided in the request to the provided defaults. See Optional Parameters for more information.
#has_default?(key : String) : Bool
#
Returns true
if this route has a default with the provided key, otherwise false
.
#has_requirement?(key : String) : Bool
#
Returns true
if this route has a requirement with the provided key, otherwise false
.
#has_scheme?(scheme : String) : Bool
#
Returns true
if this route allows the provided scheme, otherwise false
.
#host : String | ::Nil
#
Returns the hostname that the HTTP host header must match in order for this route to match. See Sub-Domain Routing for more information.
#host=(pattern : String | Regex) : self
#
Sets the hostname that the HTTP host header must match in order for this route to match to the provided pattern. See Sub-Domain Routing for more information.
#methods : Set(String) | ::Nil
#
Returns the set of valid HTTP methods that this route supports. See ART::Route for more information.
#methods=(methods : String | Enumerable(String)) : self
#
Sets the set of valid HTTP method(s) that this route supports. See ART::Route for more information.
#path : String
#
Returns the URL that this route will handle. See Routing Parameters for more information.
#path=(pattern : String) : self
#
Sets the path required for this route to match to the provided pattern.
#requirement(key : String) : Regex | Nil
#
Returns the requirement with the provided key, if any.
#requirements : Hash(String, Regex)
#
Returns a hash representing the requirements the route's parameters must match in order for this route to match. See Parameter Validation for more information.
#requirements=(requirements : Hash(String, Regex | String)) : self
#
Sets the hash representing the requirements the route's parameters must match in order for this route to match to the provided requirements. See Parameter Validation for more information.
#schemes : Set(String) | ::Nil
#
Returns the set of valid URI schemes that this route supports. See ART::Route for more information.
#schemes=(schemes : String | Enumerable(String)) : self
#
Sets the set of valid URI scheme(s) that this route supports. See ART::Route for more information.
#set_default(key : String, value : String | Nil) : self
#
Sets the default with the provided key to the provided value.
#set_requirement(key : String, requirement : Regex | String) : self
#
Sets the requirement with the provided key to the provided value.