Play supports both static and dynamic request paths. If a request path cannot be matched to any of the defined routes, an Action not found
error is thrown at runtime, which is rendered using the devNotFound.scala.html
default template.
Dynamic paths are those that can be used for multiple requests and they may or may not result in a similar response. For example, the default assets path is a path used to serve resources:
GET /assets/*file controllers.Assets.at(path="/public", file)
The *
symbol indicates that anything following /assets/
until a space is found is the value of the file
variable.
Let's look at another way to make the path dynamic when we need to add one or more variables. For example, to get a user's details by userId
we use the following code:
GET /api/user/:userId controllers.UserController.getUser(userId)
By default, all the variables that occur in a path are of the String
type. If a conversion is required, the type should be mentioned explicitly. So, if the getUser
method takes a long parameter, we would just need to specify it in this way:
GET /api/user/:userId controllers.UserController.getUser(userId:Long)
Using the":
" prefix for userId
means that the userId
variable is exactly one URI part. The assets path uses any suffix indicator as the relative file path, which is required to access any file.
It is not necessary that a path should end with a variable; for example, /api/user/:userId/album
can be used as a valid path to fetch all the albums stored by a user.
Multiple variables can also be used in the same path. Supposing we wished to fetch a specific album, we could use /api/user/:userId/album/:albumId
.
The maximum number of variables we can specify in a path is 21, since this is the maximum that the call
method used in routes_routing.scala
is defined to handle. Also, the request path becomes complicated and ends up with too many variables. In general, keeping the number of such parameters to less than five is a good practice.
Play also supports using regular expressions to match the variables. For example, assume that we want to restrict a string variable to consisting of only letters, such as a region code; in this case, our route can be defined as follows:
GET /api/region/$regionId<[a-zA-Z]{2}>/user controllers.UserController.getUserByRegion(regionId)
Notice that when we specify a regular expression for the variable in the route, it is prefixed with a $
symbol instead of the :
symbol while defining the route.
The preceding route definition restricts the request by a regular expression. For example:
/api/region/IN/user
is a valid path/api/region/CABT/user
and /api/region/99/user
are invalidThe order of preference to a route is defined by its position in the routes
file. The router returns the first matching route for a given path. If the same request type and route are mapped for two different actions, the compiler does not throw an error or warning. Some IDEs indicate when duplicate route definitions occur, but it is completely the developer's responsibility to ensure that such cases do not occur.
This table summarizes the different ways of defining a dynamic path:
Sr.no. |
Purpose |
Special characters |
Example usage(s) |
---|---|---|---|
1 |
URI path separator is part of the variable |
|
|
2 |
Single or multiple variables |
|
|
3 |
Regular expression pattern for variables |
|
|
Static request paths are fixed and constant. They cannot support arguments in the request path. All the data required for such requests should be sent through request parameters or request bodies. For example, the actions used for signing in or signing out are given as follows:
GET /login controllers.Application.login
So does Play search for specific characters to identify the kind of path?
Yes, the special characters are used by RoutesFileParser
to recognize whether a path is static or dynamic. The paths are defined as follows:
def singleComponentPathPart: Parser[DynamicPart] = (":" ~> identifier) ^^ { case name => DynamicPart(name, """[^/]+""", encode = true) } def multipleComponentsPathPart: Parser[DynamicPart] = ("*" ~> identifier) ^^ { case name => DynamicPart(name, """.+""", encode = false) } def regexComponentPathPart: Parser[DynamicPart] = "$" ~> identifier ~ ("<" ~> (not(">") ~> """[^s]""".r +) <~ ">" ^^ { case c => c.mkString }) ^^ { case name ~ regex => DynamicPart(name, regex, encode = false) } def staticPathPart: Parser[StaticPart] = (not(":") ~> not("*") ~> not("$") ~> """[^s]""".r +) ^^ { case chars => StaticPart(chars.mkString) }
In the methods used to identify a path, the ~>
, not
, and ^^
methods are from scala.util.parsing.combinator.{Parser, RegexParsers}
. DynamicPart
and StaticPart
are defined with the intention of capturing the parts of a URL, so that it's simpler to pass values to a corresponding action. They are defined as follows:
trait PathPart case class DynamicPart(name: String, constraint: String, encode: Boolean) extends PathPart with Positional { override def toString = """DynamicPart("""" + name + "", """" + constraint + """"," + encode + ")" //" } case class StaticPart(value: String) extends PathPart { override def toString = """StaticPart("""" + value + """")""" } case class PathPattern(parts: Seq[PathPart]) { def has(key: String): Boolean = parts.exists { case DynamicPart(name, _, _) if name == key => true case _ => false } override def toString = parts.map { case DynamicPart(name, constraint, encode) => "$" + name + "<" + constraint + ">" case StaticPart(path) => path }.mkString }
3.144.39.133