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:

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

Source

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

Source

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 to true 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

Source

abstract Obj? match(HttpRequest httpRequest)

Returns a response object should the given uri (and http method) match this route. Returns null if not.

matchHint

Source

abstract Str matchHint()

Returns a hint at what this route matches on. Used for debugging and in 404 / 500 error pages.

responseHint

Source

abstract Str responseHint()

Returns a hint at what response this route returns. Used for debugging and in 404 / 500 error pages.