§String Interpolating Routing DSL
Play provides a DSL for defining embedded routers called the String Interpolating Routing DSL, or sird for short. This DSL has many uses, including embedding a light weight Play server, providing custom or more advanced routing capabilities to a regular Play application, and mocking REST services for testing.
Sird is based on a string interpolated extractor object. Just as Scala supports interpolating parameters into strings for building strings (and any object for that matter), such as s"Hello $to"
, the same mechanism can also be used to extract parameters out of strings, for example in case statements.
The DSL lives in the play.api.routing.sird
package. Typically, you will want to import this package, as well as a few other packages:
import play.api.mvc._
import play.api.routing._
import play.api.routing.sird._
A simple example of its use is:
val router = Router.from {
case GET(p"/hello/$to") =>
Action {
Results.Ok(s"Hello $to")
}
}
In this case, the $to
parameter in the interpolated path pattern will extract a single path segment for use in the action. The GET
extractor extracts requests with the GET
method. It takes a RequestHeader
and extracts the same RequestHeader
parameter, it’s only used as a convenient filter. Other method extractors, including POST
, PUT
and DELETE
are also supported.
Like Play’s compiled router, sird supports matching multi path segment parameters, this is done by postfixing the parameter with *
:
val router = Router.from {
case GET(p"/assets/$file*") =>
Assets.versioned(path = "/public", file = file)
}
Regular expressions are also supported, by postfixing the parameter with a regular expression in angled brackets:
val router = Router.from {
case GET(p"/items/$id<[0-9]+>") =>
Action {
Results.Ok(s"Item $id")
}
}
Query parameters can also be extracted, using the ?
operator to do further extractions on the request, and using the q
extractor:
val router = Router.from {
case GET(p"/search" ? q"query=$query") =>
Action {
Results.Ok(s"Searching for $query")
}
}
While q
extracts a required query parameter as a String
, q_?
or q_o
if using Scala 2.10 extracts an optional query parameter as Option[String]
:
val router = Router.from {
case GET(p"/items" ? q_o"page=$page") =>
Action {
val thisPage = page.getOrElse("1")
Results.Ok(s"Showing page $thisPage")
}
}
Likewise, q_*
or q_s
can be used to extract a sequence of multi valued query parameters:
val router = Router.from {
case GET(p"/items" ? q_s"tag=$tags") =>
Action {
val allTags = tags.mkString(", ")
Results.Ok(s"Showing items tagged: $allTags")
}
}
Multiple query parameters can be extracted using the &
operator:
val router = Router.from {
case GET(
p"/items" ? q_o"page=$page"
& q_o"per_page=$perPage"
) =>
Action {
val thisPage = page.getOrElse("1")
val pageLength = perPage.getOrElse("10")
Results.Ok(s"Showing page $thisPage of length $pageLength")
}
}
Since sird is just a regular extractor object (built by string interpolation), it can be combined with any other extractor object, including extracting its sub parameters even further. Sird provides some useful extractors for some of the most common types out of the box, namely int
, long
, float
, double
and bool
:
val router = Router.from {
case GET(p"/items/${int(id) }") =>
Action {
Results.Ok(s"Item $id")
}
}
In the above, id
is of type Int
. If the int
extractor failed to match, then of course, the whole pattern will fail to match.
Similarly, the same extractors can be used with query string parameters, including multi value and optional query parameters. In the case of optional or multi value query parameters, the match will fail if any of the values present can’t be bound to the type, but no parameters present doesn’t cause the match to fail:
val router = Router.from {
case GET(p"/items" ? q_o"page=${int(page) }") =>
Action {
val thePage = page.getOrElse(1)
Results.Ok(s"Items page $thePage")
}
}
To further the point that these are just regular extractor objects, you can see here that you can use all other features of a case
statement, including @
syntax and if statements:
val router = Router.from {
case rh @ GET(
p"/items/${idString @ int(id) }" ?
q"price=${int(price) }"
) if price > 200 =>
Action {
Results.Ok(s"Expensive item $id")
}
}
§Binding sird Router
Configuring an application to use a sird Router can be achieved in many ways, depending on use case:
§Using SIRD router from a routes files
To use the routing DSL in conjunction with a regular Play project that uses a routes file and controllers, extend the SimpleRouter
:
package api
import javax.inject.Inject
import play.api.mvc._
import play.api.routing.Router.Routes
import play.api.routing.SimpleRouter
import play.api.routing.sird._
class ApiRouter @Inject() (controller: ApiController) extends SimpleRouter {
override def routes: Routes = {
case GET(p"/") => controller.index
}
}
Add the following line to conf/routes:
-> /api api.ApiRouter
§Composing SIRD routers
You can compose multiple routers together, because Routes are partial functions. So you can split your routes in smaller and more focused routers and later compose them in an application router. For example, considering the ApiRouter
above and a new SinglePageApplicationRouter
like:
class SpaRouter @Inject() (controller: SinglePageApplicationController) extends SimpleRouter {
override def routes: Routes = {
case GET(p"/api") => controller.api
}
}
You can then compose both to have a complete router for you application:
class AppRouter @Inject() (spaRouter: SpaRouter, apiRouter: ApiRouter) extends SimpleRouter {
// Composes both routers with spaRouter having precedence.
override def routes: Routes = spaRouter.routes.orElse(apiRouter.routes)
}
§Embedding play
An example of embedding a play server with sird router can be found in Embedding Play section.
§Providing a DI router
A router can be provided to the application as detailed in Application Entry point and Providing a router:
class SirdAppLoader extends ApplicationLoader {
def load(context: Context) = {
new SirdComponents(context).application
}
}
class SirdComponents(context: Context) extends BuiltInComponentsFromContext(context) with NoHttpFiltersComponents {
lazy val router = Router.from {
case GET(p"/hello/$to") =>
Action {
Ok(s"Hello $to")
}
}
}
§Providing a DI router with Guice
A SIRD router can be provided in Guice-based Play apps by overriding the GuiceApplicationLoader
and the Provider[Router]
:
class ScalaSimpleRouter @Inject() (val Action: DefaultActionBuilder) extends SimpleRouter {
override def routes: Routes = {
case GET(p"/") =>
Action {
Results.Ok
}
}
}
@Singleton
class ScalaRoutesProvider @Inject() (playSimpleRouter: ScalaSimpleRouter, httpConfig: HttpConfiguration)
extends Provider[Router] {
lazy val get = playSimpleRouter.withPrefix(httpConfig.context)
}
class ScalaGuiceAppLoader extends GuiceApplicationLoader {
protected override def overrides(context: ApplicationLoader.Context): Seq[GuiceableModule] = {
super.overrides(context) :+ (bind[Router].toProvider[ScalaRoutesProvider]: GuiceableModule)
}
}
A SIRD router is more powerful than the routes file and is more accessible by IDE’s.
§Overriding binding
A router can also be provided using e.g. GuiceApplicationBuilder in the application loader to override with custom router binding or module as detailed in Bindings and Modules
Next: Javascript routing