Book Image

Play Framework essentials

By : Julien Richard-Foy
Book Image

Play Framework essentials

By: Julien Richard-Foy

Overview of this book

Table of Contents (14 chapters)
Play Framework Essentials
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

URL routing


The routing component is the first piece of the framework that we will look at:

URL routing

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!