Managing different output formats

In the preceding examples another API was consumed. When your application gets more users, the demand to get data out of your application will not only rise in web pages. As soon as machine-to-machine communication is needed, you will need to provide an API yourself.

This recipe will show you how to implement your own APIs using the Play framework. If you need an API that exposes your data in as many data formats as possible, you might not be right with selecting the Play framework. Currently, it is not that generic. You might want to go with enunciate for example, which is reachable at http://enunciate.codehaus.org/

Find the accompanying source example at examples/chapter4/mashup-api.

Getting ready

There is some preliminary information you should know before implementing anything. Play already has a built-in mechanism for finding out what type of data to return. It checks the Accept header of the request and puts the result in the request.format variable inside your controllers. Play also decides what template to render based on this information.

How to do it...

Let's start with a service to create tickets. There are several ways to provide authentication to your API. One way is to always use HTTP Basic Authentication. This means a password is supplied on every request. This implies that all your communication must be secure in order to not to leak passwords. Encryption is expensive, if you do not have special crypto hardware in your servers. If your data is not that confidential, it should be sufficient to only do the authentication via SSL and use the created ticket for the rest. This basically resembles the browser behaviour, where often login to a site is encrypted; the rest of the communication is clear text. Be aware that session hijacking is possible with this scenario. If you need security, go via HTTPS completely. The following code snippet is an example controller to generate a unique ticket:

public static void createTicket(String user, String pass) {
  User u = User.find("byNameAndPassword", user, pass).first();
  if (u == null) {
    error("No authorization granted");
  }
  
  String uuid = UUID.randomUUID().toString().replaceAll("-", "");
  Cache.set("ticket:" + uuid, u.name, "5min");
  renderText(uuid);
}

Now the ticket needs to be checked on every incoming request, except the ticket creation itself. This is shown in the following code snippet:

@Before(priority=1, unless="createTicket")
public static void checkAuth() {
  Header ticket = request.headers.get("x-authorization");
  if (ticket == null) {
    error("Please provide a ticket");
  }
  
  String cacheTicket = Cache.get("ticket:" + ticket.value(), String.class);
  if (cacheTicket == null) {
    error("Please renew your ticket");
  }

  Cache.set("ticket:" + ticket.value(), cacheTicket, "5min");
}

From now on, every request should have an X-Authorization header, where the created UUID is sent to the server. These tickets are valid for five minutes, and then they expire from the cache. On every request the expired time is reset to five minutes. You could possibly put this into the database as well, if you wanted, but the cache is a better place for such data.

As the ticket generator returns a text string by using renderText(), it is pretty easy to use. However, you may want to return different output formats based on the client's request. The following code snippet is an example controller that returns the user's favorite quote:

public static void quote() {
  String ticket = request.params.get("ticket");
  String username = Cache.get("ticket:" + ticket, String.class);
  User user = User.find("byName", username).first();
  String quote = user.quote;
  render(quote);
}

Now, add three templates for the controller method. The first is the HTML template which needs to be put at app/views/Application/quote.html:

<html><body>The quote is ${quote}</body></html>

Then comes app/views/Application/quote.xml:

<quote>${quote}</quote>

And finally app/views/Application/quote.json:

{ "quote": "${quote}" }

How it works...

It is pretty simple to test the above implemented functionality by running curl against the implementation – as an alternative to tests, which should always be the first choice. The first thing in this example is to get a valid ticket:

curl -X POST --data "user=test&pass=test" localhost:9000/ticket

096dc3153f774f898f122d9af3e5cfcb

After that you can call the quote service with different headers:

curl --header "X-Authorization: 096dc3153f774f898f122d9af3e5cfcb"--header "Accept: application/json" localhost:9000/quote

{ "quote": "Aleaiactaest!" }

XML is also possible:

curl--header "X-Authorization: 096dc3153f774f898f122d9af3e5cfcb" --header "Accept: application/xml" localhost:9000/quote

<quote>Aleaiactaest!</quote>

Adding no header – or an invalid one –returns the standard HTML response:

curl--header "X-Authorization: 096dc3153f774f898f122d9af3e5cfcb" localhost:9000/quote

<html><body>The quote is Aleaiactaest!</body></html>

The functionality of being able to return different templates based on the client Accept header looks pretty useful at first sight. However, it carries the burden of forcing the developer to ensure that valid XML or JSON is generated. This is usually not what you want. Both formats are a little bit picky about validation. The developer should create neither of those by hand. This is what the renderJSON() and renderXml() methods are for. So always use the alternative methods presented in this recipe with care, as they are somewhat error prone, even though they save some lines of controller code.

There's more...

It is very simple to add more text based output formats such as CSV, and combine them with the default templating engine. However, it is also possible to support binary protocols such as the AMF protocol if you need to.

Integrating arbitrary formats

It is easily possible to integrate arbitrary formats in the rendering mechanism of Play. You can add support for templates with a .vcf file suffix with one line of code. More information about this is provided in the official documentation at the following link: http://www.playframework.org/documentation/1.2.1/routes#Customformats

Getting out AMF formats

You should check the modules repository for more output formats like just JSON or XML. If you are developing a Flex application, then you might need to create some AMF renderer. In this case, you should check out https://bitbucket.org/maxmc/cinnamon-play/overview.

See also

Another minor problem that needs to be solved is the ability to also accept incoming XML and JSON data, and to convert it into objects. This is done in the next recipe.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.5.57