URL routing
The routing component is the first piece of the framework that we will look at:
The preceding diagram depicts its process. It takes an HTTP request and calls the corresponding entry point of the application. The mapping between requests and entry points is defined by routes in the conf/routes
file. The routes
file provided by the application template is as follows:
# Routes # This file defines all application routes (Higher priority routes first) # ~~~~ # Home page GET / controllers.Application.index # Map static resources from the /public folder to the /assets URL path GET /assets/*file controllers.Assets.versioned(path="/public", file)
Apart from comments (starting with #
), each line of the routes file defines a route associating an HTTP verb and a URL pattern to a controller action call.
For instance, the first route associates the /
URL to the controllers.Application.index
action. This one processes requests by always returning an HTTP response with a 200 status code (OK
) and an HTML body that contains the result of the rendering of the views.html.index
template.
The content of the routes file is compiled by the sbt plugin into a Scala object named Router
and contains the dispatching logic (that is, which action to call according to the incoming request verb and URL). If you are curious, the generated code is written in the target/scala-2.10/src_managed/main/routes_routing.scala
file. The router tries each route, one after the other, in their order of declaration. If the verb and URL match the pattern, the corresponding action is called.
Your goal is to expose your Shop
business layer as a web service, so let's add the following lines to the routes
file:
GET /items controllers.Items.list POST /items controllers.Items.create GET /items/:id controllers.Items.details(id: Long) PUT /items/:id controllers.Items.update(id: Long) DELETE /items/:id controllers.Items.delete(id: Long)
The first route will return a list of items for sale in the shop, the second one will create a new item, the third one will show detailed information about an item, the fourth one will update the information of an item, and finally, the last one will delete an item. Note that we follow the REST conventions (http://en.wikipedia.org/wiki/REST) for the URL shapes and HTTP verbs.
In the controllers
package of your code, add the following Items
controller that matches the added routes:
package controllers import play.api.mvc.{Controller, Action} object Items extends Controller { val shop = models.Shop // Refer to your Shop implementation val list = Action { NotImplemented } val create = Action { NotImplemented } def details(id: Long) = Action { NotImplemented } def update(id: Long) = Action { NotImplemented } def delete(id: Long) = Action { NotImplemented } }
The equivalent Java code is as follows:
package controllers; import play.mvc.Controller; import play.mvc.Result; public class Items extends Controller { static final Shop shop = Shop.Shop; // Refer to your Shop implementation public static Result list() { return status(NOT_IMPLEMENTED); } public static Result create() { return status(NOT_IMPLEMENTED); } public static Result details(Long id) { return status(NOT_IMPLEMENTED); } public static Result update(Long id) { return status(NOT_IMPLEMENTED); } public static Result delete(Long id) { return status(NOT_IMPLEMENTED); } }
Each route is mapped by a controller member of type Action
(or in Java, a public static method that returns a Result
). For now, actions are not implemented (they all return NotImplemented
) but you will progressively connect them to your Shop
service so that, for instance, the Items.list
action exposes the shop list
method.
Route path parameters
In our example, in the first route, the URL pattern associated with the controllers.Items.details
action is /items/:id
, which means that any URL starting with /items/
and then containing anything but another /
will match. Furthermore, the content that is after the leading /
is bound to the id
identifier and is called a path parameter. The /items/42
path matches this pattern, but the /items/
, /items/42/0
, or even /items/42/
paths don't.
When a route contains a dynamic part such as a path parameter, the routing logic extracts the corresponding data from the URL and passes it to the action call.
You can also force a path parameter to match a given regular expression by using the following syntax:
GET /items/$id<\d+> controllers.Items.details(id: Long)
Here, we check whether the id
path parameter matches the regular expression \d+
(at least one digit). In this case, the /items/foo
URL will not match the route pattern and Play will return a 404 (Not Found) error for such a URL.
A route can contain several path parameters and each one is bound to only one path segment. Alternatively, you can define a path parameter spanning several path segments using the following syntax:
GET /assets/*file controllers.Assets.at(path = "/public", file)
In this case, the file
identifier captures everything after the /assets/
path segment. For instance, for an incoming request with the /assets/images/favicon.png
URL, file
is bound to images/favicon.png
. Obviously, a route can contain at most one path parameter that spans several path segments.
Parameters type coercion
By default, request parameters are coerced to String
values, but the type annotation id: Long
(for example, in the details
route) asks Play to coerce the id
parameter to type Long
. The routing process extracts the content corresponding to a parameter from the URL and tries to coerce it to its target type in the corresponding action (Long
in our example) before calling it.
Note that /items/foo
also matches the route URL pattern, but then the type coercion process fails. So, in such a case, the framework returns an HTTP response with a 400 (Bad Request) error status code.
This type coercion logic is extensible. See the API documentation of the QueryStringBindable
and PathBindable
classes for more information on how to support your own data types.
Parameters with fixed values
The parameter values of the called actions can be bound from the request URL, or alternatively, can be fixed in the routes file by using the following syntax:
GET / controllers.Pages.show(page = "index")
GET /:page controllers.Pages.show(page)
Here, in the first route, the page
action parameter is set to "index"
and it is bound to the URL path in the second route.
Query string parameters
In addition to path parameters, you can also define query string parameters: parameters extracted from the URL query string.
To define a query string parameter, simply use it in the action call part of the route without defining it in the URL pattern:
GET /items controllers.Items.details(id: Long)
The preceding route matches URLs with the /items
path and have a query string parameter id
. For instance, /items
and /items?foo=bar
don't match but /items?id=42
matches. Note that the URL must contain at least all the parameters corresponding to the route definition (here, there is only one parameter, which is id
), but they can also have additional parameters; /items?foo=bar&id=42
also matches the previous route.
Default values of query string parameters
Finally, you can define default values for query string parameters. If the parameter is not present in the query string, then it takes its default value. For instance, you can leverage this feature to support pagination in your list
action. It can take an optional page
parameter defaulting to the value 1
. The syntax for default values is illustrated in the following code:
GET /items controllers.Items.list(page: Int ?= 1)
The preceding route matches the /items?page=42
URL and binds the page
query string parameter to the value 42
, but also matches the /items
URL and, in this case, binds the page
query string parameter to its default value 1
.
Change the corresponding action definition in your code so that it takes a page
parameter, as follows:
def list(page: Int) = Action { NotImplemented }
The equivalent Java code is as follows:
public static Result list(Integer page) {
return status(NOT_IMPLEMENTED);
}
Trying the routes
If you try to perform requests to your newly added routes from your browser, you will see a blank page. It might be interesting to try them from another HTTP client to see the full HTTP exchange between your client and your server. You can use, for example, cURL (http://curl.haxx.se/):
$ curl -v http://localhost:9000/items > GET /items HTTP/1.1 > User-Agent: curl/7.35.0 > Host: localhost:9000 > Accept: */* > < HTTP/1.1 501 Not Implemented < Content-Length: 0 <
The preceding command makes an HTTP GET
request on the /items
path and gets an HTTP response with the status code 501 (Not Implemented). Try requesting other paths such as /items/42, /itemss/foo
, or /foo
and compare the response status codes you get.
Routes are the way to expose your business logic endpoints as HTTP endpoints; your Shop
service can now be used from the HTTP world!