Developing Representational State Transfer services
Friday, October 19, 2007 09:34 AM
In my previous article, Rise of Web platforms, I wrote how the Web has been evolving over the years. The focus now is to turn the Web into a platform for hosting all kinds of services and resources.
Some examples include Flickr, twitter.com, Google's set of APIs to its services, ohloh.com, box.net, and others. The trend seems to be that old and new Web sites are putting up some sort of programmable interface for developers and consumers to extend and innovate on top of these sites. Most of these sites often offer an interface called REST.
In this article, we will look at how you can develop RESTful services. We will assume that you are building this in a Servlet container/environment.
JSR-311 Standard
So what is this arcane JSR-311? JSR 311 is a standard developed by the Java Community Process to define a standard programming model for the development of RESTful Web services on the Java Platform. The reference implementation for JSR 311 is a project called Jersey, and because it is an important standard, JSR 311 will be the focus of this article.
RESTful Service
Before we develop a RESTful services, we need to answer the following four questions[1]:
1. What are the URIs?
What this means is, how will the URIs reflect our resources/service; for example the following URI:
http://www.myserver.com/customer/12345
might represent customer 12345 whereas the following
http://www.myserver.com/customer
may return a list of all our customers.
2. What is the format?
What is the data format that our services consume and what format can it produce; for example, when we are using JavaScript, it may be more convenient to work with JSON. So if the RESTful service supports JSON, we can request our record to be returned in JSON format. Others may prefer XML if XML is supported.
3. What are the methods that are supported by each URI?
The methods here refer to HTTP methods. Common methods includes POST, GET, PUT, DELETE. Now if we map the preceding four methods to CRUD, then the following HTTP POST
POST http://www.myserver.com/customer/123456 HTTP/1.0 Content-Type: application/xml ... <customer> <name>means 'create customer 123456 with the following data and the data format is in XML'.Flintstone Fred </name> ... </customer>
4. What status code could it return?
This refers to the possible HTTP status code that a HTTP method could return. We are familiar with 404 when we look up a non-existent URL with our browser. Using the example above, if we do not have the authorization to create a new customer record, our service could return a 401 which is 'Unauthorized'. Invoking a HTTP method could also return something other than a status code. For example, if we GET customer record 123456,
GET http://www.myserver.com/customer/123456 HTTP/1.0 Accept: application/json ...the service would return the record 123456 in JSON format or a status code 404 if there is no customer number 123456.
Developing with JSR 311
So let's now turn our attention to developing our RESTful service. For our purpose, we will create a simple service that retrieves a customer record.
1. Mapping the URI
According to what we have seen in the previous section, the first thing we have to consider is the URI. In this case, we are going to use the following scheme:
http://www.myserver.com/customer/cust_id
where cust_id is a valid customer id. So here is how you create a class and map this resource to it
import javax.ws.rs.*;
@UriTemplate("/customer/{id}")
public class Customer {
The @UriTemplate annotation maps the path "/customer/{id}" to the Customer class. The "{id}" states that the id will be assigned to whatever value comes after 'customer/'. So if we are presented with the following paths:
http://www.myserver.com/customer/123456 http://www.myserver.com/customer/platinum/123456
then 'id' will be '123456' and 'platinum/123456' respectively.
2. Consuming and Producing Data Types
The next step is to tell our service what data format it consumes and what data format it generates with @ConsumeMime and @ProduceMime respectively. Our service will ignore the incoming data type, specifed by 'Content-type' HTTP header and we will only produce the record data in String type.
import javax.ws.rs.*;
@UriTemplate("/customer/{id}")
@ProduceMime("text/plain") @ProduceMime
("application/customer+xml")
public class Customer {
If your service produces or consumes more than one data type, then just keep using the appropriate annotation to specify it as I've shown above.
3. Mapping HTTP to Java Methods
We will now have to determine which HTTP method maps to which Java method. For simplicity sake, we are only going to work with GET, that is:
GET http://www.myserver.com/customer/123456
will return 123456 record. So first, create a normal Java method that takes in a String parameter; the parameter is our customer number that we want to lookup. The return type of this method will be a String containing the customer information. Here is how the method might look like:
public String getCustomer(String custId) {
//Retrieve customer from a predefined method
Customer cust = findCustomerById(custId);
//Now create a String with the information
StringBuilder sb = new StringBuilder();
sb.append("last:").append(cust.getLastName()).append(",");
sb.append("first:").append(cust.getFirstName()).append(",");
...
return (sb.toString());
}
Now we need to
1. Specify that getCustomer() method will be invoked whenever there is a HTTP GET method invoked; actually to be more accurate it will only invoke on a GET and the URI with the following pattern: customer/followed/by/a/suffix. To map GET to getCustomer(), we will use the @HttpMethod annotation
2. The second thing is to relate the {id} in the URI to the custId parameter in getCustomer() with @UriParam annotation.
Here is the getCustomer() with all the added annotations
@HttpMethod("GET")
public String getCustomer(@UriParam("id") String custId) {
//Retrieve customer from a predefined method
Customer cust = findCustomerById(custId);
//Now create a String with the information
StringBuilder sb = new StringBuilder();
sb.append("last:").append
(cust.getLastName()).append(",");
sb.append("first:").append
(cust.getFirstName()).append(",");
...
return (sb.toString());
}
If you prefer the custId to be an integer, just change the parameter type from String to int.
4. Return Status
If the service has any errors, it needs to report it. There are several ways to report an error. The easiest way is to throw a WebApplicationException with the appropriate HTTP status code. The following shows the entire Customer services:
import javax.ws.rs.*;
@UriTemplate("/customer/{id}")
@ProduceMime("text/plain") @ProduceMime
("application/customer+xml")
public class Customer {
HttpMethod("GET")
public String getCustomer(@UriParam("id")
String custId) {
//Retrieve customer from a predefined method
Customer cust = findCustomerById(custId);
if (cust == null)
throw new WebApplicationException(404);
//Now create a String with the information
StringBuilder sb = new StringBuilder();
sb.append("last:").append
(cust.getLastName()).append(",");
sb.append("first:").append
(cust.getFirstName()).append(",");
...
return (sb.toString());
}
What we have covered is just the tip of the proverbial iceberg. You can learn more about developing RESTful services with JSR 311 from here. NetBeans 6 has a plug-in that makes it really easy to develop and deploy RESTful services to your favorite Web container. You can learn more about it here.
Lee Chuk-Munn has been programming in the Java language since 1996, when he first joined Sun Microsystems in Hong Kong. He currently works as a senior developer consultant and technology evangelist for Technology Outreach at Sun in Singapore. Chuk's focus is in Java APIs, Java EE, Java SE, and Java ME. Chuk graduated in 1987 from the Royal Melbourne Institute of Technology in Melbourne, Australia, where his favorite subject was compiler theory.



There are currently no comments for this post.