Introduction to REpresentational State Transfer (REST)


Bhaskar S 02/28/2009


Introduction

We have been hearing of the acronym REST quite often these days. So, what is REST ? REST stands for REpresentational State Transfer and comprises of the following set of architectural principles for building any service oriented web application:

Lets expand on each of the above mentioned principles.


Identification

Every web application has one or more resources. Resources could be state or functionality. For example, Stock quote, Customer address, Product detail, etc represent resources. In a web application, resources are represented using URLs. For example, the URL http://www.mystore.com/dvd/wall-e could identify the information for the DVD of the movie Wall-E.

What this principle is hinting at is that every resource in a web application needs to be identifiable using an unique URL. The URL would then act as an interface for interacting with the resource which makes it easy for any application to deal with in a standard way.


Representation

Having exposed resources as URLs, what happens when a user or another application accesses any of the URLs ? Accessing any URL in a web application typically returns some data. For a human accessing the URL from a Web Browser, the data may be returned as HTML, while an application accessing the same URL could see the data as XML. In order to support the various data representations, a web application typically looks at the HTTP header “Accept” to see what types the client is willing to accept and format the data accordingly. For example, accessing the URL for getting the DVD information for the movie Wall-E in a Web Browser could return the following HTML content:

Output.1

<html><body>
<h2><pre>
<p>Genre: Animation
<p>Language: English
<p>Format: Widescreen
<p>Year: 2008
<p>Produced By: Disney
<p>Price: $24.95
</pre></h2>
</body></html>

While accessing the same URL for getting the DVD information for the movie Wall-E from a Java application could return the following XML content:

Output.2

<DVD name='Wall-E'>
<Genre>Animation</Genre>
<Language>English</Language>
<Format>Widescreen</Format>
<Year>2008</Year>
<ProducedBy>Disney</ProducedBy>
<Price>$24.95</Price>
</DVD>

What this principle is hinting at is that a web application should be flexible enough to support the various data representations like HTML, XML, etc. This would make the web application consumable by any type of target, be it a Web browser or another application.


Linkage

Lets consider our Online DVD URL http://www.mystore.com/dvd/wall-e. This URL returns the basic DVD information for Wall-E. Now, consider the URL http://www.mystore.com/dvd/wall-e/details that returns a very detailed information for the DVD Wall-E. For brevity, lets assume the details include the basic movie information plus the cast information. We could combine all that information as one large monolithic content and return it to the target that accessed the URL. That means the target would have to process all the content irrespective of what it desired to see. Instead, if we return the content with the basic DVD information plus URLs to cast information, the target could drill down further by following any of the included URLs to get the necessary details if needed. We are not forcing the target to process all the details. The following illustrates this hypothetical content in XML with URL links:

<DVD name='Wall-E'>
<Genre>Animation</Genre>
<Language>English</Language>
<Format>Widescreen</Format>
<Year>2008</Year>
<ProducedBy>Disney</ProducedBy>
<Price>$24.95</Price>
<Cast>
   <Star name='Ben Burtt'>http://www.mystarinfo.com/star/ben_burtt</Star>
   <Star name='Elissa Knight'>http://www.mystarinfo.com/star/elissa_knight</Star>
</Cast>
</DVD>

What this principle is hinting at is that content from a web application needs to be modular with links to related resource URLs. This approach allows an application to navigate the related resources very easily and seek the desired information.


Manipulation

Having resources in an application generally leads to the need for manipulating them. Resource manipulation includes creating, querying, updating or deleting the resources. So what interface could we use to handle these operations. This is where the standard HTTP methods GET, POST, PUT, and DELETE come into play. GET is used to query information about a resource. POST is used to create information about a resource. PUT is used to update the information of a resource. And finally, DELETE is used to delete the information about a resource.

What this principle is hinting at is the use of standard HTTP methods for performing various operations on the resources. This way any application can interact with your application in a standard way.

With this we should have an understanding of what REST is all about from a theoretical perspective. Let us firm our understanding of REST by trying out the DVD example in Java.


Hands-on with REST

To get started we need a REST framework for Java. Restlet is one of the earlier and popular REST framework for Java. You can download the latest distribution from http://www.restlet.org. We will be using Restlet as a standalone Java application for our demostration. To use Restlet in a standalone mode, we only need to reference the following two jars:

Lets look at our simple DVD example that only supports the GET operation. First, we need a way to persist information about the DVDs. Rather than using a Database, for simplicity, we will use in-memory persistence using the Java HashMap. The following code illustrates the simple DVD Data Access Object:

Listing.1
/* 
* Name: Bhaskar S
*
* Date: 03/07/2009
*/

package com.polarsparc.restlet.example.one;

import java.util.HashMap;

public class DvdDAO {
// In-memory persistence of DVDs
private HashMap<String, String> _DVDs = new HashMap<String, String>();

// To start with have Wall-E in memory
public void init()
throws Exception {
_DVDs.put("walle", "<DVD name='Wall-E'>\n" +
"<Genre>Animation</Genre>\n" +
"<Language>English</Language>\n" +
"<Format>Widescreen</Format>\n" +
"<Year>2008</Year>\n" +
"<ProducedBy>Disney</ProducedBy>\n" +
"<Price>$24.95</Price>\n" +
"</DVD>");
}

public String find(String movie)
throws Exception {
if (movie != null) {
return _DVDs.get(movie);
}
return null;
}
}

The DVD Data Access Object has information for only one DVD of Wall-E.

Next, we need to represent the DVD information as a web resource. The following code illustrates the DVD information represented as a Restlet Resource:

Listing.2
/* 
* Name: Bhaskar S
*
* Date: 03/07/2009
*/

package com.polarsparc.restlet.example.one;

import org.restlet.*;
import org.restlet.data.*;
import org.restlet.resource.*;

// Resource represents an abstract web entity that can be identified
// with a URL. A Resource also manages data representations
public class DvdResource extends Resource {
private static DvdDAO _DAO;

static {
try {
_DAO = new DvdDAO();
_DAO.init();
}
catch (Exception ex) {
ex.printStackTrace(System.err);
System.exit(1);
}
}

public DvdResource(Context context, Request request, Response response) {
super(context, request, response);

// Variant is an abstraction of data representation for a Resource
// In this case we are only supporting XML data representation
getVariants().add(new Variant(MediaType.TEXT_XML));
}

@Override
// Handle the GET - Need to override to implement the state behind
// our DVD resource
public Representation represent(Variant variant)
throws ResourceException {
if (MediaType.TEXT_XML.equals(variant.getMediaType())) {
String movie = (String) getRequest().getAttributes().get("movie");

System.out.println("-> Requested DVD Movie: " + movie);

String info = null;

try {
info = _DAO.find(movie);
}
catch (Exception ex) {
ex.printStackTrace(System.err);
}

if (info != null) {
return new StringRepresentation(info, MediaType.TEXT_XML);
}
}
return null;
}
}

Restlet Resource is an abstraction for a resource and also manages the data representations for the resource.

Next, we need a container that can handle the URL requests from clients for DVD information. The following code illustrates the Restlet Application that manages the DVD resource:

Listing.3
/*
* Name: Bhaskar S
*
* Date: 03/07/2009
*/

package com.polarsparc.restlet.example.one;

import org.restlet.*;

// Application is a REST container that can attached to a host. They
// manage Resources
public class DvdApplication extends Application {
@Override
public synchronized Restlet createRoot() {
// Router manages URLs and routes to the appropriate Resources
Router router = new Router(getContext());

// Based on the URL, the Router routes requests to the
// appropriate Resource for processing. Manage the DVD
// resource
router.attach("/dvd/{movie}", DvdResource.class);

return router;
}
}

Restlet Application is a container that manages and handles requests for URLs such as http://www.mystore.com/dvd/wall-e.

Finally, we need a way to expose the Restlet Application through HTTP. The following code illustrates how to expose the DVD Application using HTTP connector in Restlet:

Listing.4
/* 
* Name: Bhaskar S
*
* Date: 03/07/2009
*/

package com.polarsparc.restlet.example.one;

import org.restlet.*;
import org.restlet.data.*;

public class DvdRestService {
public static void main(String[] args) {
try {
// Component is a container for managing network endpoints
// like HTTP and the associated Applications
Component component = new Component();

// Use HTTP as the protocol at port 9090
component.getServers().add(Protocol.HTTP, 9090);

// Manage DVD application
component.getDefaultHost().attach(new DvdApplication());

component.start();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}

Executing the above code will start a HTTP server that is listening on port 9090 for requests. Assuming the code has been setup as a project in Eclipse, go ahead and Run the DvdRestService as a Java Application. The following diagram shows the screenshot:

REST_01 Image
Figure.1

Now, try the URL http://localhost:9090/dvd/wall-e from a Web Browser and you should see the following content:

Output.3

<DVD name="Wall-E">
<Genre>Animation</Genre>
<Language>English</Language>
<Format>Widescreen</Format>
<Year>2008</Year>
<ProducedBy>Disney</ProducedBy>
<Price>$24.95</Price>
</DVD>

Now, try a different URL like http://localhost:9090/dvd/nemo from the Web Browser and you will see the following:

Output.4

The server has not found anything matching the request URI

Now that we are comfortable with Restlet, lets enhance the simple example to support all the REST operations such as GET, POST, PUT and DELETE. We will be using the Web Browser for GET and the standalone Java clients for performing POST, PUT, and DELETE.

The following code illustrates the simple DVD Data Access Object:

Listing.5
/* 
* Name: Bhaskar S
*
* Date: 03/07/2009
*/

package com.polarsparc.restlet.example.two;

import java.util.HashMap;

public class DvdDAO {
// In-memory persistence of DVDs
private HashMap<String, String> _DVDs = new HashMap<String, String>();

// To start with have Wall-E in memory
public void init()
throws Exception {
_DVDs.put("walle", "<DVD name='Wall-E'>\n" +
"<Genre>Animation</Genre>\n" +
"<Language>English</Language>\n" +
"<Format>Widescreen</Format>\n" +
"<Year>2008</Year>\n" +
"<ProducedBy>Disney</ProducedBy>\n" +
"<Price>$24.95</Price>\n" +
"</DVD>");
}

public String find(String movie)
throws Exception {
if (movie != null) {
return _DVDs.get(movie);
}
return null;
}

public boolean create(String movie, String name, String genre, String lang,
String format, String year, String prod, String price)
throws Exception {
if (movie != null) {
_DVDs.put(movie, "<DVD name='" + name + "'>\n" +
"<Genre>" + genre + "</Genre>\n" +
"<Language>" + lang + "</Language>\n" +
"<Format>" + format + "</Format>\n" +
"<Year>" + year + "</Year>\n" +
"<ProducedBy>" + prod + "</ProducedBy>\n" +
"<Price>" + price + "</Price>\n" +
"</DVD>");
return true;
}
return false;
}

public boolean update(String movie, String name, String genre, String lang,
String format, String year, String prod, String price)
throws Exception {
if (movie != null) {
_DVDs.put(movie, "<DVD name='" + name + "'>\n" +
"<Genre>" + genre + "</Genre>\n" +
"<Language>" + lang + "</Language>\n" +
"<Format>" + format + "</Format>\n" +
"<Year>" + year + "</Year>\n" +
"<ProducedBy>" + prod + "</ProducedBy>\n" +
"<Price>" + price + "</Price>\n" +
"</DVD>");
return true;
}
return false;
}

public boolean delete(String movie)
throws Exception {
// Cannot delete walle
if (movie != null && movie.equalsIgnoreCase("walle") == false) {
Object dvd = _DVDs.remove(movie);
if (dvd != null) {
return true;
}
}
return false;
}
}

The following code illustrates the DVD information represented as a Restlet Resource:

Listing.6
/* 
* Name: Bhaskar S
*
* Date: 03/07/2009
*/

package com.polarsparc.restlet.example.two;

import org.restlet.*;
import org.restlet.data.*;
import org.restlet.resource.*;

// Resource represents an abstract web entity that can be identified
// with a URL. A Resource also manages data representations
public class DvdResource extends Resource {
private static DvdDAO _DAO;

static {
try {
_DAO = new DvdDAO();
_DAO.init();
}
catch (Exception ex) {
ex.printStackTrace(System.err);
System.exit(1);
}
}

public DvdResource(Context context, Request request, Response response) {
super(context, request, response);

// Variant is an abstraction of data representation for a Resource
// In this case we are only supporting XML data representation
getVariants().add(new Variant(MediaType.TEXT_XML));
}

@Override
// Important - To handle POST
public boolean allowPost() {
return true;
}

@Override
// Important - To handle PUT
public boolean allowPut() {
return true;
}

@Override
// Important - To handle DELETE
public boolean allowDelete() {
return true;
}

@Override
// Handle GET - Need to override to implement the state behind
// our DVD resource
public Representation represent(Variant variant)
throws ResourceException {
if (MediaType.TEXT_XML.equals(variant.getMediaType())) {
String movie = (String) getRequest().getAttributes().get("movie");
if (movie == null || movie.trim().length() == 0) {
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
return new StringRepresentation("<Error>No movie name specified
for query</Error>", MediaType.TEXT_XML);
}

System.out.println("-> Requested DVD Movie: " + movie);

String info = null;

try {
info = _DAO.find(movie);
}
catch (Exception ex) {
ex.printStackTrace(System.err);
}

if (info != null) {
return new StringRepresentation(info, MediaType.TEXT_XML);
}
}
return null;
}

@Override
// Handle POST - Need to override to add a new DVD resource
public void acceptRepresentation(Representation entity)
throws ResourceException {
String movie = (String) getRequest().getAttributes().get("movie");
if (movie == null || movie.trim().length() == 0) {
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
getResponse().setEntity(new StringRepresentation("<Error>No movie name
specified for create</Error>", MediaType.TEXT_XML));
return;
}

if (entity.getMediaType().equals(MediaType.APPLICATION_WWW_FORM, true)) {
// Retrieve name-value pairs from the Form
Form form = new Form(entity);

String name = form.getFirstValue("name");
String genre = form.getFirstValue("genre");
String lang = form.getFirstValue("language");
String format = form.getFirstValue("format");
String year = form.getFirstValue("year");
String prod = form.getFirstValue("producer");
String price = form.getFirstValue("price");

boolean status = false;

try {
status = _DAO.update(movie, name, genre, lang, format, year, prod, price);
}
catch (Exception ex) {
ex.printStackTrace(System.err);
}

if (status) {
getResponse().setStatus(Status.SUCCESS_CREATED);
getResponse().setEntity(new StringRepresentation("<Success>DVD information
created for movie " + movie + "</Success>", MediaType.TEXT_XML));
}
else {
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
getResponse().setEntity(new StringRepresentation("<Error>Could not create
DVD information for movie " + movie + "</Error>", MediaType.TEXT_XML));
}
}
else {
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
getResponse().setEntity(new StringRepresentation("<Error>Invalid request to
create DVD information for movie " + movie + "</Error>", MediaType.TEXT_XML));
}
}

@Override
// Handle PUT - Need to override to update existing DVD resource
public void storeRepresentation(Representation entity)
throws ResourceException {
String movie = (String) getRequest().getAttributes().get("movie");
if (movie == null || movie.trim().length() == 0) {
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
getResponse().setEntity(new StringRepresentation("<Error>No movie name specified
for update</Error>", MediaType.TEXT_XML));
return;
}

if (entity.getMediaType().equals(MediaType.APPLICATION_WWW_FORM, true)) {
// Retrieve name-value pairs from the Form
Form form = new Form(entity);

String name = form.getFirstValue("name");
String genre = form.getFirstValue("genre");
String lang = form.getFirstValue("language");
String format = form.getFirstValue("format");
String year = form.getFirstValue("year");
String prod = form.getFirstValue("producer");
String price = form.getFirstValue("price");

boolean status = false;

try {
status = _DAO.update(movie, name, genre, lang, format, year, prod, price);
}
catch (Exception ex) {
ex.printStackTrace(System.err);
}

if (status) {
getResponse().setStatus(Status.SUCCESS_CREATED);
getResponse().setEntity(new StringRepresentation("<Success>DVD information
updated for movie " + movie + "</Success>", MediaType.TEXT_XML));
}
else {
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
getResponse().setEntity(new StringRepresentation("<Error>Could not update
DVD information for movie " + movie + "</Error>", MediaType.TEXT_XML));
}
}
else {
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
getResponse().setEntity(new StringRepresentation("<Error>Invalid request to update
DVD information for movie " + movie + "</Error>", MediaType.TEXT_XML));
}
}

@Override
// Handle DELETE - Need to override to delete existing DVD resource
public void removeRepresentations()
throws ResourceException {
String movie = (String) getRequest().getAttributes().get("movie");
if (movie == null || movie.trim().length() == 0) {
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
getResponse().setEntity(new StringRepresentation("<Error>No movie name specified
for delete</Error>", MediaType.TEXT_XML));
return;
}

boolean status = false;

try {
status = _DAO.delete(movie);
}
catch (Exception ex) {
ex.printStackTrace(System.err);
}

if (status) {
getResponse().setStatus(Status.SUCCESS_CREATED);
getResponse().setEntity(new StringRepresentation("<Success>DVD information deleted
for movie " + movie + "</Success>", MediaType.TEXT_XML));
}
else {
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
getResponse().setEntity(new StringRepresentation("<Error>Could not delete DVD
information for movie " + movie + "</Error>", MediaType.TEXT_XML));
}
}
}

There is no change to either the DvdApplication or the DvdRestService Java code.

The following code illustrates the test client that sends a POST request to create the Nemo DVD information:

Listing.7
/* 
* Name: Bhaskar S
*
* Date: 03/07/2009
*/

package com.polarsparc.restlet.example.two;

import org.restlet.*;
import org.restlet.data.*;

public class DvdCreateClient {
public static void main(String[] args) {
try {
// Restlet HTTP client
Client client = new Client(Protocol.HTTP);


// URL of the DVD resource for Nemo
Reference ref = new Reference("http://localhost:9090/dvd/nemo");

Form form = new Form();
form.add("name", "Nemo");
form.add("genre", "Animation");
form.add("language", "English");
form.add("format", "Widescreen");
form.add("year", "2003");
form.add("producer", "Disney");
form.add("price", "$18.99");

// POST request to create the DVD resource
Response response = client.post(ref, form.getWebRepresentation());

// Response from the DVD service
if (response.getStatus().isSuccess()) {
System.out.println(response.getEntity().getText());
}
else {
System.out.println(response.getEntity().getText());
}
}
catch (Exception ex) {
ex.printStackTrace(System.err);
System.exit(1);
}
System.exit(0);
}
}

The following code illustrates the test client that sends a PUT request to update the Nemo DVD information:

Listing.8
/* 
* Name: Bhaskar S
*
* Date: 03/07/2009
*/

package com.polarsparc.restlet.example.two;

import org.restlet.*;
import org.restlet.data.*;

public class DvdUpdateClient {
public static void main(String[] args) {
try {
// Restlet HTTP client
Client client = new Client(Protocol.HTTP);


// URL of the DVD resource for Nemo
Reference ref = new Reference("http://localhost:9090/dvd/nemo");

Form form = new Form();
form.add("name", "Nemo");
form.add("genre", "Animation");
form.add("language", "English");
form.add("format", "Widescreen");
form.add("year", "2003");
form.add("producer", "Disney");
form.add("price", "$15.99");

// PUT request to update the DVD resource
Response response = client.put(ref, form.getWebRepresentation());

// Response from the DVD service
if (response.getStatus().isSuccess()) {
System.out.println(response.getEntity().getText());
}
else {
System.out.println(response.getEntity().getText());
}
}
catch (Exception ex) {
ex.printStackTrace(System.err);
System.exit(1);
}
System.exit(0);
}
}

The following code illustrates the test client that sends a DELETE request to remove the Nemo DVD information:

Listing.9
/* 
* Name: Bhaskar S
*
* Date: 03/07/2009
*/

package com.polarsparc.restlet.example.two;

import org.restlet.*;
import org.restlet.data.*;

public class DvdDeleteClient {
public static void main(String[] args) {
try {
// Restlet HTTP client
Client client = new Client(Protocol.HTTP);


// URL of the DVD resource for Nemo
Reference ref = new Reference("http://localhost:9090/dvd/nemo");

// DELETE request to remove the DVD resource
Response response = client.delete(ref);

// Response from the DVD service
if (response.getStatus().isSuccess()) {
System.out.println(response.getEntity().getText());
}
else {
System.out.println(response.getEntity().getText());
}
}
catch (Exception ex) {
ex.printStackTrace(System.err);
System.exit(1);
}
System.exit(0);
}
}

To see the simple DVD application in action, start the DVDRestService as before.

Now, try the URL http://localhost:9090/dvd/nemo from the Web Browser and you will see the following:

Output.5

The server has not found anything matching the request URI

Execute the DvdCreateClient. Now, retry the URL http://localhost:9090/dvd/nemo from the Web Browser and you will see the following:

Output.6

<DVD name="Nemo">
<Genre>Animation</Genre>
<Language>English</Language>
<Format>Widescreen</Format>
<Year>2003</Year>
<ProducedBy>Disney</ProducedBy>
<Price>$18.99</Price>
</DVD>

Next, execute the DvdUpdateClient. Now, retry the URL http://localhost:9090/dvd/nemo from the Web Browser and you will see the following:

Output.7

<DVD name="Nemo">
<Genre>Animation</Genre>
<Language>English</Language>
<Format>Widescreen</Format>
<Year>2003</Year>
<ProducedBy>Disney</ProducedBy>
<Price>$15.99</Price>
</DVD>

Finally, execute the DvdDeleteClient. Now, retry the URL http://localhost:9090/dvd/nemo from the Web Browser and you will see the following:

Output.8

The server has not found anything matching the request URI

This completes our demonstration of REST using Restlet Java framework.