Play framework 1.0 content negotiation

Play framework 1.0 content negotiation

One thing that the http://www.playframework.org/[Play framework] 1.0 has in common with other RESTful architectures is the direct use of HTTP functionality, instead of trying to hide HTTP or put an abstraction layer on top of it. This article shows you how to use HTTP content negotiation in a Play framework web application.

Content negotiation

Content negotiation is an HTTP feature that allows an HTTP server to serve different media types for the same URL, according to which media types are requested by the HTTP client. The client specifies acceptable content types using media types in the Accept header, such as requiring an XML response with:

Accept: application/xml

A client may specify more than one media type, and also specify that any media type is acceptable with a catch-all wild-card media type (*/*):

Accept: application/xml, image/png, */*

Conventional web browsers always include the wild-card value in theAccept header: they will accept any media type, and Play will serve HTML - the default 'format'. Content negotiation is more likely to be used by custom clients, such as an Ajax request that requires a JSON response, or an e-book reader that requires a PDF or EPUB version of a document.

Play request format

Play implements content negotiation by parsing the Accept header and setting request.format, which is used to select the view template by file extension.

The default format for a Play request is html, which is selected if the Accept header contains text/html or application/xhtml, or as a result of the wildcard */* value. The default format is not selected if the wildcard value is not present.

The default template for the index() controller method (and htmlformat) is therefore the file index.html. If you specify a different format, in one of several ways, you can select an alternate template.

Built-in formats

Play has built-in support for a few formats: html, txt, json andxml. For example, define a controller method that renders some data:

public static void index() {
   final String name = "Peter Hilton";
   final String organisation = "Lunatech Research";
   final String url = "http://www.lunatech-research.com/";
   render(name, organisation, url);
}

If you request a URL that is mapped to this method ( http://localhost:9000/ in a new Play application) in a web browser, then play will render the index.html template, because web browsers send an Accept header that includes the value text/html.

Play responds to a request with the header Accept: text/xml by setting the request format to xml and rendering an index.xml template, such as:

<?xml version="1.0"?>
<contact>
   <name>${name}</name>
   <organisation>${organisation}</organisation>
   <url>${url}</url>
</contact>

The built in Accept header format mappings work as follows, for anindex() controller method.

`Accept` header  Format  Template file name  Mapping
`null` `null` `index.html` Default template extension for `null` format
`image/png` `null` `index.html` Media type not mapped to a format
`*/*, image/png` `html` `index.html` Default media type mapped to `html` format
`text/html` `html` `index.html` Built-in format
`application/xhtml` `html` `index.html` Built-in format
`text/xml` `xml` `index.xml` Built-in format
`application/xml` `xml` `index.xml` Built-in format
`text/plain` `txt` `index.txt` Built-in format
`text/javascript` `json` `index.json` Built-in format
`application/json, */*` `json` `index.json` Built-in format, default media type ignored

Custom formats

If you want to serve a response with a particular media type you can set the format before calling the render method. For example, to serve a vCard , you can do:

request.format = "vcf";

Instead of applying this format to all requests, you can implement content negotiation by inspecting the request headers, so that you only set that format when the HTTP request selects the corresponding media type. In your controller, check for your custom format before all requests:

@Before
static void setFormat() {
   if (request.headers.get("accept").value().equals("text/x-vcard")) {
      request.format = "vcf";
   }
}

Now, a request with a Accept: text/x-vcard header will render anindex.vcf template, such as:

BEGIN:VCARD
VERSION:3.0
N:${name}
FN:${name}
ORG:${organisation}
URL:${url}
END:VCARD

Note that this also sets the response Content-type to text/x-vcardbecause of the media type mapping in Play's mime-types.propertiesfile, which may allow your system to open the vCard in a suitable application, such as Address Book on a Mac.

URL-based format specification

An alternative to using HTTP headers is to use the URL to specify the format. This is less in the spirit of HTTP, but can be useful as a fallback for supporting HTTP clients that cannot control the headers they send.

You can add formats as specific routes, by specifying the format for the controller method. With the same index() method as above, the following route will handle a request for /index.xml, setting the format to xml and rendering the index.xml template.

GET    /index.xml          Application.index(format:'xml')

Play can also extract the format directly from the URL, with a route such as the following.

GET    /index.{format}     Application.index

With this route, a request for /index.xml will set the format to xmland render the XML template, while /index.txt will render the plain text template.