Posts Tagged WebServices

RESTful API and HATEOAS Conundrum

HATEOAS — Hypermedia as the Engine of Application State is an important constraint of REST architecture as defined by Roy Fielding. To better understand the principle I’ve been discussing the topic with various REST enthusiasts. In this post I would like to provide an update on some of the learning points. There are arguably quite a few false claims of RESTful-ness (by API providers) while not following some of the basic tenets of REST. HATEOAS seems to be one of the main principles that is either misunderstood or not implemented in its true spirit.

Hypermedia and Application State

There are two important pieces here:

  • What is Hypermedia?:  Just as Hypertext documents contains links to other chunks of text, Hypermedia links to many media types including — text, images, audio, video, etc. WWW is a common example of Hypermedia.
  • What is application state?: Not to be mistaken with the resource state. Application state is the state of the user’s application of computing to a given task. This is an important distinction, as Mark Baker explains in this InfoQ article:

“application state” refers to the state that determines “where” the user is in the process of completing a task. For example, when doing personal banking, is the user currently viewing account balances, filling in a bill payment form, or about to order new cheques? Those are each different application states. Some people mistakenly believe that “state” here refers to resource state, which would include, in this example, the balance of the accounts or the list of recent payments made. That isn’t the case.

HATEOAS achieves two very important aspects — protocol is not stateful, and loose coupling of client-server can be achieved as the server evolves describing new URIs.

Source of some confusion

Roy Fielding published a post in an effort to clarify what a true RESTful API is and how people are abusing the term. The last bullet point of the post says that ..

A REST API should be entered with no prior knowledge beyond the initial URI (bookmark) and set of standardized media types that are appropriate for the intended audience (i.e., expected to be understood by any client that might use the API). …

That along with some related discussions raised some interesting questions in my mind about how much prior knowledge that a client can have.

Ask an Expert

I have asked Subbu Allamaraju a couple of questions in this regard (Subbu is well known in this area, I learnt a lot from his blog posts and articles. He works at Yahoo developing standards, patterns and practices for HTTP web languages, and is extremely passionate about REST).

Question: How much information should the client know? If I strictly go by Fielding, it should only know the start page, and server should take care of the rest, driving the flow?

Subbu: That may be possible, provided the client knows all the link relations and media types. But there is no such constraint as having one staring URI. The idea is to keep representations contextual, but not necessarily to limit the number of prepublished URIs to one. When a representation is contextual, the client can decouple itself from server’s business rules.

In other words, what a client can do on a given resource depends on some business rules on the server. Instead of the client guessing that it can do certain action on a given resource, it can rely on the links contained in the representation. That is, by embedding application state and context into representations, servers can avoid leaking business rules to clients. That is the main benefit of HATEOAS for client-server apps.

[Next question could sound a bit extreme to some folks, but I read the idea earlier and so wanted find Subbu's take on that.]

Question: Is it practical to say that the client shouldn’t know anything including the fields that are being worked on (say username and password fields of the form) and shouldn’t know about what page it is posting to, say “/login”.

Subbu: This may be not practical and can be prohibitively expensive.

Media types, link relations and things like microformats can help to a certain extent. In the case of XHTML representations containing well-known microformats, you may be able to minimize the amount of information a client will need to know to intelligently interact with a RESTful server. Even on the web, given a perfectly described XHTML, try replacing a human with a perl program. How far can that perl script go? Can it pass the Turing test?

If automation and self-discovery is the goal, of course, it may be worth trying. But I doubt if every application needs that capability.

[Subbu's Describing RESTful applications is an excellent article that explains HATEOAS and more. Highly recommended]

Next Steps

Along the lines I will be closely following Solomon Duskis’ thought process to see what his experiments result in. Solomon and I had some good conversations, and his posts helped provoke some thought.

I guess the best way to understand this is by reviewing a good example. I’ve asked the following question on Twitter

If asked about an example of truly RESTful API, which (publicly available) API do you point to? Interested: no-state-on-server, HATEOAS.

.. for which I got a couple of responses — Amazon S3 and NetFlix. Did some research and found some good reviews on NetFlix API, so will explore that further ..

Tags: ,

REST: CRUD with JAX-RS (Jersey)

In an earlier post I’ve discussed setting up Tomcat and Eclipse environment for JAX-RS. This post is an attempt to discuss some more basics of JAX-RS, specifically performing CRUD operations.

Prerequisites

  • Setup environment, discussed in this post
  • Commons-HttpClient is used for the client program. Commons-codec and Commons-logging are its dependencies. All of them can be downloaded from Apache Commons website. Put those jars in the web application’s lib.

Now some fun

If you are remotely interested in REST read Roy Fielding’s dissertation on the topic. The chapter on REST is a must read, not necessary to understand this exercise but strongly recommended to gain a big picture.

As a part of the CRUD (creating, reading, updating and deleting) exercise let’s try and create a Customer resource, retrieve Customer details, update the details and finally delete the Customer. To keep it simple and to keep the focus on the topic at hand I will not use a real database, instead use a file system leveraging Java’s Properties mechanism.

Our customer POJO has got 4 properties: id, first name, last name and a zip code.

public class Customer {

	private Integer customerId;

	private String firstName;

	private String lastName;

	private String zipcode;

        //setters and getters
}

Create

Let’s start with CustomerResource class. A resource class is a Java class that uses JAX-RS annotations to implement corresponding Web resource. According to the specification, resource classes are POJOs that have at least one method annotated with @Path or a request method designator.

@Path ("customer")
public class CustomerResource {
    public static final String DATA_FILE = "C:\\TEMP\\customer-data.txt";

    @POST
    @Consumes ("application/xml")
    public Response addCustomer(InputStream customerData) {
    try {
        Customer customer = buildCustomer(null, customerData);
        long customerId = persist(customer, 0);
        return Response.created(URI.create("/" + customerId)).build();
    } catch (Exception e) {
        throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR);
    }
}

  • Paths are relative. For an annotated class the base URI is the application context. For an annotated method the base URI is the effective URI of the contatining class. Base URIs are treated as if they ended in “/”. So in the above example first line indicates how the Customer URI is mapped to /customer.
  • addCustomer method is annotated as a method that responds to the HTTP POST method calls. As there is no @Path annotation on the method, the URI of the class is also the URI to access the method.
  • Another important annotation is Consumes. It defines the media types that the methods of a resource class can accept. If not specified, it is assumed that any media type is acceptable. We don’t have a Consumes annotation at the class level in this example, but if there is one the method level annotation takes precedence over the class level annotation.
  • Response is returned to the client which contains a URI to the newly created resource. Return type Response results in an entity body mapped from the entity property of the Response with the status code specified by the status property of the response.
  • WebApplicationException is a RuntimeException that is used to wrap the appropriate HTTP status codes.
  • addCustomer method builds the customer from the received XML input and persists it. Code for the methods called in addMethod() is provided below and is self-explanatory. These methods are used for both create and update purposes and hence the method signatures take Customer as a parameter.

    private long persist(Customer customer, long customerId) throws IOException {
        Properties properties = new Properties();
        properties.load(new FileInputStream(DATA_FILE));

        if (customerId == 0) {
            customerId = System.currentTimeMillis();
        }

        properties.setProperty(String.valueOf(customerId),
            customer.getFirstName() + "," + customer.getLastName() +
            "," + customer.getZipcode());
        properties.store(new FileOutputStream(DATA_FILE), null);
        return customerId;
    }

    private Customer buildCustomer(Customer customer, InputStream customerData)
        throws ParserConfigurationException, SAXException, IOException {
        if (customer == null) {
            customer = new Customer();
        }
        DocumentBuilder documentBuilder =
            DocumentBuilderFactory.newInstance().newDocumentBuilder();
            Document document = documentBuilder.parse(customerData);
            document.getDocumentElement().normalize();

            NodeList nodeList = document.getElementsByTagName("customer");

            Node customerRoot = nodeList.item(0);
            if (customerRoot.getNodeType() == Node.ELEMENT_NODE) {
                Element element = (Element) customerRoot;
                NodeList childNodes = element.getChildNodes();
                for (int i = 0; i < childNodes.getLength(); i++) {
                    Element childElement = (Element)childNodes.item(i);
                    String tagName = childElement.getTagName();
                    String textContent = childElement.getTextContent();
                    if (tagName.equals("firstName")) {
                        customer.setFirstName(textContent);
                    } else if (tagName.equals("lastName")) {
                        customer.setLastName(textContent);
                    } else if (tagName.equals("zipcode")) {
                        customer.setZipcode(textContent);
                    }
                }
            } else {
                throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
            }

            return customer;
    }
Test it

A test client method to test add operation looks something like the following, you may run this as a Java application via a main() method or it is easy to modify the following as a Junit test case.

    private static void testAddCustomer() throws IOException, HttpException {
        final String addCustomerXML =
            "<customer>" +
                "<firstname>Joe</firstname>" +
                "<lastname>Schmo</lastname>" +
                "<zipcode>98042</zipcode>" +
            "</customer>";

        PostMethod postMethod = new PostMethod(
            "http://localhost:9000/RestExample/customer");
        RequestEntity entity = new InputStreamRequestEntity(
            new ByteArrayInputStream(addCustomerXML.getBytes()),
            "application/xml");
        postMethod.setRequestEntity(entity);
        HttpClient client = new HttpClient();
        try {
            int result = client.executeMethod(postMethod);
            System.out.println("Response status code: " + result);
            System.out.println("Response headers:");
            Header[] headers = postMethod.getResponseHeaders();
            for (int i = 0; i < headers.length; i++) {
            System.out.println(headers[i].toString());
        } finally {
            postMethod.releaseConnection();
        }
    }
Output


Response status code: 201
Response headers:
Server: Apache-Coyote/1.1
Location: http://localhost:9000/RestExample/customer/1236708444823
Content-Length: 0
Date: Thu, 05 Mar 2009 12:15:22 GMT

  • Response status code 201 indicates the resource was created
  • Location header displays the URI of the new resource that is created

Read

retrieveCustomer() method below illustrates the read operation:

      @GET
      @Path ("{id}")
      @Produces ("application/xml")
      public StreamingOutput retrieveCustomer(@PathParam ("id") String customerId) {
          try {
              String customerDetails = loadCustomer(customerId);
              System.out.println("customerDetails: " + customerDetails);
              if (customerDetails == null) {
                  throw new NotFoundException("<error>No customer with id: " +
                      customerId + "</error>");
              }

              final String[] details = customerDetails.split(",");

              return new StreamingOutput() {
                  public void write(OutputStream outputStream) {
                      PrintWriter out = new PrintWriter(outputStream);
                      out.println("< ?xml version=\"1.0\" encoding=\"UTF-8\"?>");
                      out.println("<customer>");
                      out.println("<firstname>" + details[0] + "</firstname>");
                      out.println("<lastname>" + details[1] + "</lastname>");
                      out.println("<zipcode>" + details[2] + "</zipcode>");
                      out.println("</customer>");
                      out.close();
                  }
              };
          } catch (IOException e) {
              throw new WebApplicationException(e,
                  Response.Status.INTERNAL_SERVER_ERROR);
          }
      }
  • Get annotation indicates that this method is responsible for HTTP GET operations
  • Path annotation indicates a dynamic id. As discussed above the paths are relative, so the URI /customer/{id} is the resultant path for this method
  • Produces annotation, indicates the media type that is resulted from this operation
  • PathParam annotation in the parameter reads the path id that is passed using the Path annotation
  • StreamingOutput is returned by this resource method. StreamingOutput is a simpler version of MessageBodyWriter. It has a write() method that has Output stream. All that you need is to write the data into that object, which will be returned to the client program.
Test it

In a web browser you may try the URI that was resulted from our create operation above (http://localhost:9000/RestExample/customer/1236708444823 for this example).

Output

You should see response something like the followiing:

< ?xml version="1.0" encoding="UTF-8" ?>
- <customer>
  <firstname>Joe</firstname>
  <lastname>Schmo</lastname>
  <zipcode>98042</zipcode>
  </customer>

Update

POST is used create a new resource, PUT method updates the state of a known resource. You often see discussions about when to use POST vs PUT. My understanding is that if you are trying to create a brand new resource use POST, no Request-URI is required for POST. State is sent as a part of the BODY and the server should return you back HTTP code 201 (resource created), just like what we discussed in the CREATE section above. On the other hand, if you’re updating the state of a resource use PUT. In this case you need the URI to the resource that you are trying to update.

Update code follows:

      @PUT
      @Path("{id}")
      @Consumes("application/xml")
      public void updateCustomer(@PathParam("id") String customerId,
          InputStream input) {
          try {
              String customerDetails = loadCustomer(customerId);
              if (customerDetails == null) {
                  throw new WebApplicationException(Response.Status.NOT_FOUND);
              }
              String[] details = customerDetails.split(",");
              Customer customer = new Customer();
              customer.setFirstName(details[0]);
              customer.setLastName(details[1]);
              customer.setZipcode(details[2]);
              buildCustomer(customer, input);
              persist(customer, Long.valueOf(customerId));
          } catch (Exception e) {
              throw new WebApplicationException(e,
                  Response.Status.INTERNAL_SERVER_ERROR);
          }
      }
  • PUT annotation marks the method as a resource method that handles HTTP PUT requests
  • Path and Consumes annotations are already discussed in the earlier sections
Test it

Using HttpClient the update test can be performed as follows. In this example zip code of a customer is being updated:

      private static void testUpdateCustomer() throws IOException, HttpException {
          final String addCustomerXML =
              "<customer>" +
                  "<zipcode>98043</zipcode>" +
              "</customer>";

          PutMethod putMethod = new PutMethod(
              "http://localhost:9000/RestExample/customer/1236708444823");
          RequestEntity entity = new InputStreamRequestEntity(
              new ByteArrayInputStream(addCustomerXML.getBytes()),
              "application/xml");
          putMethod.setRequestEntity(entity);
          HttpClient client = new HttpClient();
          try {
              int result = client.executeMethod(putMethod);
              System.out.println("Response status code: " + result);
              System.out.println("Response headers:");
              Header[] headers = putMethod.getResponseHeaders();
              for (int i = 0; i < headers.length; i++) {
                  System.out.println(headers[i].toString());
              }
          } finally {
              putMethod.releaseConnection();
          }
      }

Delete

The HTTP DELETE method requests the server to delete the resource identified by the request-URI.

    @DELETE
    @Path("{id}")
    public void deleteCustomer(@PathParam("id") String customerId) {
	try {
	    Properties properties = new Properties();
	    properties.load(new FileInputStream(DATA_FILE));
	    String customerDetails = properties.getProperty(customerId);
	    if (customerDetails == null) {
	        throw new WebApplicationException(Response.Status.NOT_FOUND);
	    }
	    properties.remove(customerId);
	    properties.store(new FileOutputStream(DATA_FILE), null);
	} catch (Exception e) {
		throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR);
	}
    }
  • DELETE annotation is used to mark the method as a resource method that handles the resource-delete operation.
Test it

Test code for Delete. Attempting to delete the customer resource created earlier:

    private static void testDeleteCustomer() throws HttpException, IOException {
        DeleteMethod deleteMethod = new DeleteMethod(
	    "http://localhost:9000/RestExample/customer/1236708444823");
        HttpClient client = new HttpClient();
        try {
            int result = client.executeMethod(deleteMethod);
            System.out.println("Response status code: " + result);
            System.out.println("Response headers:");
            Header[] headers = deleteMethod.getResponseHeaders();
            for (int i = 0; i < headers.length; i++) {
                System.out.println(headers[i].toString());
            }
        } finally {
            deleteMethod.releaseConnection();
        }
    }

output

Status code 204 is returned, stands for No Content. This code is used in cases where the request is successfully processed, but the response doesn’t have a message body


Response status code: 204
Response headers:
Server: Apache-Coyote/1.1
Date: Tue, 10 Mar 2009 18:34:46 GMT

Tags: ,

REST: Jersey configuration on Tomcat

Goal

I spent some time putting all these pieces together configuring Jersey on Tomcat, so hope this write-up may help somebody who intends to do the same.

Quick Background

JAX-RS is an annotation-based API for implementing RESTful web services. Jersey is the JAX-RS reference implementation from Sun. Jersey website says that it is more than the reference implementation; Jersey provides an API so that the developers may extend it suit their needs. There are other implementations by different vendors: RESTEasy (from JBoss), Restlet, Apache CXF.

Prerequisites

  • Download and install Tomcat (using version 6.0).
  • Download Jersey (using Jersey 1.0.2, most recent at the time of writing this)
  • I’m using Eclipse as IDE (heard that NetBeans has better REST support than Eclipse, I haven’t tried it yet)

Getting Started

1. Create a new dynamic web project in Eclipse

dynamicwebproject

2. Copy Jersey jars to the web project’s library:

asm-3.1.jar, jersey-core.jar, jersey-server.jar, jsr-311-api-1.0.jar

3. Deployment descriptor

Add Jersey Servlet declaration to the web.xml of the web application (this step took a little while to figure out exact class name of the jersey implementation)

    <servlet>
           <servlet-name>JerseyTest</servlet-name>
            <servlet-class>
                com.sun.jersey.spi.container.servlet.ServletContainer
            </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
           <servlet-name>JerseyTest</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

4.Resource class

Create a resource class, HelloWorldResource. See the power of JAX-RS and Jersey in the annotations below


package com.suryasuravarapu.jersey;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

@Path ("/helloworld")
public class HelloWorldResource {

@GET
@Produces ("text/plain")
public String sayHello() {
return "Hello World";
}
}

- Line 7: @Path indicates what URI path is mapped to the resource.
- Line 11:@GETindicates what HTTP method is allowed for this resource.
-Line 12: @Produces indicates what MIME type is returned by this resource.

5. Test it

Access the resource using http://localhost:8080/RestExample/helloworld (assuming your Tomcat is running on port 8080, otherwise adjust accordingly), you should see a response back with text ‘Hello World’.

This is obviously a  simple example but the focus here is mainly on the setup. Stay tuned, more RESTful stuff to come …

Tags: ,