const mixinafBedSheet::Route
afBedSheet::Route
Matches HTTP Requests to response objects.
Route
is a mixin so you may provide your own implementations. The rest of this documentation relates to the default implementation which uses regular expressions to match against the Request URL and HTTP Method.
Regex Routes
Matches the HTTP Request URL and HTTP Method to a response object using regular expressions.
Note that all URL matching is case-insensitive. If you really need case-sensitive matching (???) use the RegexRoute
explicitly, passing false
as the caseInsensitive
argument. Example:
RegexRoute(`/index`, MyPage#hello, "GET", false)
Response Objects
A Route
may return any response object, be it Text, HttpStatus, File
, or any other. It simply returns whatever is passed into the ctor.
Example, this matches the URL /greet
and returns the string Hello Mum!
Route(`/greet`, Text.fromPlain("Hello Mum!"))
And this redirects any request for /home
to /greet
Route(`/home`, Redirect.movedTemporarily(`/greet`))
You can use glob expressions in your URL, so:
Route(`/greet.*`, ...)
will match the URLs /greet.html
, /greet.php
but not /greet
.
Response Methods
Routes may also return MethodCall instances that call a Fantom method. To use, pass in the method as the response object. On a successful match, the Route
will convert the method into a MethodCall
object.
Route(`/greet`, MyPage#hello)
Method matching can also map URL path segments to method parameters and is a 2 stage process:
Stage 1 - URL Matching
First a special glob syntax is used to capture string sections from the request URL. In stage 2 these strings are used as potential method arguments.
In brief, the special glob syntax is:
?
optionally matches the last character,/*
captures a path segment,/**
captures all remaining path segments,/***
captures the remaining URL.
Full examples follow:
URL glob captures -------------------------------------------- /user/ --> /user/* => "" /user/42 --> /user/* => "42" /user/42/ --> /user/* => no match /user/42/dee --> /user/* => no match /user/ --> /user/*/* => no match /user/42 --> /user/*/* => no match /user/42/ --> /user/*/* => "42", "" /user/42/dee --> /user/*/* => "42", "dee" /user/ --> /user/** => "" /user/42 --> /user/** => "42" /user/42/ --> /user/** => "42", "" /user/42/dee --> /user/** => "42", "dee" /user/ --> /user/*** => "" /user/42 --> /user/*** => "42" /user/42/ --> /user/*** => "42/" /user/42/dee --> /user/*** => "42/dee"
Note that in stage 2 empty strings may be converted to nulls
.
The intention of the ?
character is to optionally match a trailing slash. Example:
URL glob captures -------------------------------------------- /index --> /index/? => match /index/ --> /index/? => match vs /index --> /index/ => no match /index/ --> /index => no match
Should a match be found, then the captured strings are further processed in stage 2.
A no match
signifies just that.
Stage 2 - Method Parameter Matching
An attempt is now made to match the captured strings to method parameters, taking into account nullable types and default values.
First, the number of captured strings have to match the number of method parameters, taking into account any optional / default values on the method.
Next the captured strings are converted to method arguments using the ValueEncoder service. If no value encoder is found then the following default behaviour is used:
- Non-empty strings are converted using a TypeCoercer
- Empty strings are considered
null
but- if the method parameter is not nullable, then BeanFactory.defaultValue() is used.
The above process may sound complicated but in practice it just works and does what you expect.
Here are a couple of examples:
strings method signature args ---------------------------------------------------------- --> (Obj a, Obj b) => no match "" --> (Str? a) => null "" --> (Str a) => "" "wotever" --> (Str a) => "wotever" "" --> (Int? a) => null "" --> (Int a) => 0 "68" --> (Int a) => 68 "wotever" --> (Int a) => no match "" --> (Str? a, Int b := 68) => null, (default) "" --> (Str a, Int b := 68) => "", (default)
Assuming you you have an entity object, such as User
, with an ID field; you can contribute a ValueEncoder
that inflates (or otherwise reads from a database) User
objects from a string version of the ID. Then your methods can declare User
as a parameter and BedSheet will convert the captured strings to User objects for you!
Method Invocation
Handler methods may be non-static. They they belong to an IoC service then the service is obtained from the IoC registry. Otherwise the containing class is autobuilt. If the class is const
, the instance is cached for future use.
- makeFromGlob
static new makeFromGlob(Uri urlGlob, Obj response, Str httpMethod := "GET")
Creates a Route that matches on the given URL glob pattern.
urlGlob
must start with a slash "/". Example:Route(`/index/**`)
Note that matching is made against URI patterns in Fantom standard form. That means certain delimiter characters in the path section will be escaped with a backslash. Notably the
:/?#[]@\
characters. Glob expressions have to take account of this.httpMethod
may specify multiple HTTP methods, separated by spaces and / or commas. Each may also be a glob pattern. Example, all the following are valid:GET
GET HEAD
GET, HEAD
GET, H*
Use the simple string
*
to match all HTTP methods.- makeFromRegex
static new makeFromRegex(Regex uriRegex, Obj response, Str httpMethod := "GET", Bool matchAllSegs := false)
For hardcore users; make a Route from a regex. Capture groups are used to match arguments. Example:
Route(Regex<|(?i)^\/index\/(.*?)$|>, #foo, "GET", true) ==> Route(`/index/**`)
Set
matchAllSegs
totrue
to have the last capture group mimic the glob**
operator, splitting on "/" to match all remaining segments.Note that matching is made against URI patterns in Fantom standard form. That means certain delimiter characters in the path section will be escaped with a backslash. Notably the
:/?#[]@\
characters. Regular expressions have to take account of this.httpMethod
may specify multiple HTTP methods, separated by spaces and / or commas. Each may also be a glob pattern. Example, all the following are valid:GET
GET HEAD
GET, HEAD
GET, H*
Use the simple string
*
to match all HTTP methods.- match
abstract Obj? match(HttpRequest httpRequest)
Returns a response object should the given uri (and http method) match this route. Returns
null
if not.- matchHint
abstract Str matchHint()
Returns a hint at what this route matches on. Used for debugging and in 404 / 500 error pages.
- responseHint
abstract Str responseHint()
Returns a hint at what response this route returns. Used for debugging and in 404 / 500 error pages.