You are previewing REST in Practice.

REST in Practice

Cover of REST in Practice by Ian Robinson... Published by O'Reilly Media, Inc.
  1. REST in Practice
    1. SPECIAL OFFER: Upgrade this ebook with O’Reilly
    2. Foreword
    3. Preface
      1. Should I Read This Book?
      2. Should I Skip This Book?
      3. Resources
      4. What Did You Think About the Book?
      5. Errata
      6. Conventions Used in This Book
      7. Using Code Examples
      8. How to Contact Us
      9. Safari® Books Online
      10. Acknowledgments
    4. 1. The Web As a Platform for Building Distributed Systems
      1. Architecture of the Web
      2. Thinking in Resources
      3. From the Web Architecture to the REST Architectural Style
      4. The Web As an Application Platform
      5. Web Friendliness and the Richardson Maturity Model
      6. GET on Board
    5. 2. Introducing Restbucks: How to GET a Coffee, Web Style
      1. Restbucks: A Little Coffee Shop with Global Ambitions
      2. Toolbox
      3. Here Comes the Web
    6. 3. Basic Web Integration
      1. Lose Weight, Feel Great!
      2. A Simple Coffee Ordering System
      3. URI Templates
      4. URI Tunneling
      5. POX: Plain Old XML over HTTP
      6. We Are Just Getting Started
    7. 4. CRUD Web Services
      1. Modeling Orders As Resources
      2. Building CRUD Services
      3. Aligning Resource State
      4. Consuming CRUD Services
      5. Consuming Services Automatically with WADL
      6. CRUD Is Good, but It’s Not Great
    8. 5. Hypermedia Services
      1. The Hypermedia Tenet
      2. Hypermedia Formats
      3. Contracts
      4. Hypermedia Protocols
      5. Implementing a Hypermedia Service
      6. Building the Ordering Service in Java
      7. Building the Ordering Service in .NET
      8. Ready, Set, Action
    9. 6. Scaling Out
      1. GET Back to Basics
      2. Caching
      3. Making Content Cacheable
      4. Implementing Caching in .NET
      5. Consistency
      6. Extending Freshness
      7. Stay Fresh
    10. 7. The Atom Syndication Format
      1. The Format
      2. Common Uses for Atom
      3. Using Atom for Event-Driven Systems
      4. Building an Atom Service in Java
      5. Building an Atom Service in .NET
      6. Atom Everywhere?
      7. After the Event
    11. 8. Atom Publishing Protocol
      1. Atom Publishing Protocol
      2. Implementing Order Fulfillment Using AtomPub
      3. Implementing AtomPub in .NET
      4. A Versatile Protocol
    12. 9. Web Security
      1. HTTP Security Essentials
      2. Identity and the OpenID Protocol
      3. The OAuth Protocol
      4. Service Hacks and Defenses
      5. Final Thoughts
    13. 10. Semantics
      1. Syntax Versus Semantics
      2. Structure and Representation of Information
      3. The Semantic Web
      4. Microformats
      5. Linked Data and the Web
      6. Guidance
    14. 11. The Web and WS-*
      1. Are Web Services Evil?
      2. SOAP: The Whole Truth
      3. WSDL: Just Another Object IDL
      4. Two Wrongs Don’t Make a Right
      5. Secure, Reliable, Transacted
      6. A Requiem for Web Services?
    15. 12. Building the Case for the Web
      1. No More Silver Bullets
      2. Building and Running Web-Based Services
      3. No Architecture Without Measurement
      4. Selling the Web
      5. Go Forth and Build
    16. Index
    17. About the Authors
    18. Colophon
    19. SPECIAL OFFER: Upgrade this ebook with O’Reilly
O'Reilly logo

Building CRUD Services

When you’re building a service, it helps to think in terms of the behaviors that the service will implement. In turn, this leads us to think in terms of the contract that the service will expose to its consumers. Unlike other distributed system approaches, the contract that CRUD services such as Restbucks exposes to customers is straightforward, as it involves only a single concrete URI, a single URI template, and four HTTP verbs. In fact, it’s so compact that we can provide an overview in just a few lines, as shown in Table 4-1.

Table 4-1. The ordering service contract overview

Verb

URI or template

Use

POST

/order

Create a new order, and upon success, receive a Location header specifying the new order’s URI.

GET

/order/{orderId}

Request the current state of the order specified by the URI.

PUT

/order/{orderId}

Update an order at the given URI with new information, providing the full representation.

DELETE

/order/{orderId}

Logically remove the order identified by the given URI.

The contract in Table 4-1 provides an understanding of the overall life cycle of an order. Using that contract, we can design a protocol to allow consumers to create, read, update, and delete orders. Better still, we can implement it in code and host it as a service.

Note

What constitutes a good format for your resource representations may vary depending on your problem domain. For Restbucks, we’ve chosen XML, though the Web is able to work with any reasonable format, such as JSON or YAML.

Creating a Resource with POST

We first saw HTTP POST in Chapter 3, when we used it as an all-purpose transfer mechanism for moving Plain Old XML (POX) documents between clients and servers. In that example, however, the semantics of POST were very loose, conveying only that the client wished to “transfer a document” to the server with the hope that the server would somehow process it and perhaps create a response document to complete the interaction.

As the Restbucks coffee ordering service evolves into a CRUD service, we’re going to strengthen the semantics of POST and use it as a request to create an order resource within the service. To achieve this, the payload of the POST request will contain a representation of the new order to create, encoded as an XML document. Figure 4-4 illustrates how this works in practice.

Creating an order via POST

Figure 4-4. Creating an order via POST

In our solution, creating an order at Restbucks requires that we POST an order representation in XML to the service.[28] The create request consists of the POST verb, the ordering service path (relative to the Restbucks service’s URI), and the HTTP version. In addition, requests usually include a Host header that identifies the receiving host of the server being contacted and an optional port number. Finally, the media type (XML in this case) and length (in bytes) of the payload is provided to help the service process the request. Example 4-1 shows a network-level view of a POST request that should result in a newly created order.

Example 4-1. Creating a coffee order via POST

POST /order HTTP/1.1
Host: restbucks.com
Content-Type: application/xml
Content-Length: 239

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <name>latte</name>
      <quantity>1</quantity>
      <milk>whole</milk>
      <size>small</size>
    </item>
  </items>
</order>

Once the service receives the request, its payload is examined and, if understood, dispatched to create a new order resource.

Note

The receiving service may choose to be strict or lax with respect to the syntactic structure of the order representation. If it is strict, it may force compliance with an order schema. If the receiving service chooses to be lax (e.g., by extracting the information it needs through XPath expressions), its processing logic needs to be permissive with respect to the representation formats that might be used.

Robust services obey Postel’s Law,[29] which states, “be conservative in what you do; be liberal in what you accept from others.” That is, a good service implementation is very strict about the resource representations it generates, but is permissive about any representations it receives.

If the POST request succeeds, the server creates an order resource. It then generates an HTTP response with a status code of 201 Created, a Location header containing the newly created order’s URI, and confirmation of the new order’s state in the response body, as we can see in Example 4-2.

Example 4-2. Response to successful order creation

HTTP/1.1 201 Created
Content-Length: 267
Content-Type: application/xml
Date: Wed, 19 Nov 2008 21:45:03 GMT
Location: http://restbucks.com/order/1234

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <name>latte</name>
      <quantity>1</quantity>
      <milk>whole</milk>
      <size>small</size>
    </item>
  </items>
  <status>pending</status>
</order>

The Location header that identifies the URI of the newly created order resource is important. Once the client has the URI of its order resource, it can then interact with it via HTTP using GET, PUT, and DELETE.

While a 201 Created response is the normal outcome when creating orders, things may not always go according to plan. As with any computing system—especially distributed systems—things can and do go wrong. As service providers, we have to be able to deal with problems and convey helpful information back to the consumer in a structured manner so that the consumer can make forward (or backward) progress. Similarly, as consumers of the ordering service, we have to be ready to act on those problematic responses.

Fortunately, HTTP offers a choice of response codes, allowing services to inform their consumers about a range of different error conditions that may arise during processing. The Restbucks ordering service has elected to support two error responses when a request to create a coffee order fails:

  • 400 Bad Request, when the client sends a malformed request to the service

  • 500 Internal Server Error, for those rare cases where the server faults and cannot recover internally

With each of these responses, the server is giving the consumer information about what has gone wrong so that a decision can be made on how (or whether) to make further progress. It is the consumer’s job to figure out what to do next.

Note

500 Internal Server Error as a catchall error response from the ordering service isn’t very descriptive. In reality, busy baristas might respond with 503 Service Unavailable and a Retry-After header indicating that the server is temporarily too busy to process the request. We’ll see event status codes and how they help in building robust distributed applications in later chapters.

When the ordering service responds with a 400 status, it means the client has sent an order that the server doesn’t understand. In this case, the client shouldn’t try to resubmit the same order because it will result in the same 400 response. For example, the malformed request in Example 4-3 doesn’t contain the drink that the consumer wants, and so cannot be a meaningful coffee order irrespective of how strict or lax the server implementation is in its interpretation. Since the <name> element is missing, the service can’t interpret what kind of drink the consumer wanted to order, and so an order resource cannot be created. As a result, the service must respond with an error.

Example 4-3. A malformed order request

POST /order HTTP/1.1
Host: restbucks.com
Content-Type: application/xml
Content-Length: 216

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <quantity>1</quantity>
      <milk>whole</milk>
      <size>small</size>
    </item>
  </items>
</order>

On receiving the malformed request, the server responds with a 400 status code, and includes a description of why it rejected the request,[30] as we see in Example 4-4.

Example 4-4. Response to a malformed order request

HTTP/1.1 400 Bad Request
Content-Length: 250
Content-Type: application/xml
Date: Wed, 19 Nov 2008 21:48:11 GMT

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <!-- Missing drink type -->
      <quantity>1</quantity>
      <milk>whole</milk>
      <size>small</size>
    </item>
  </items>
</order>

To address this problem, the consumer must reconsider the content of the request and ensure that it meets the criteria expected by the ordering service before resubmitting it. If the service implementers were being particularly helpful, they might provide a textual or machine-processable description of why the interaction failed to help the consumer correct its request, or even just a link to the service’s documentation. The ordering service won’t create an order in this case, and so retrying with a corrected order is the right thing for the consumer to do.

In the case of a 500 response, the consumer may have no clear understanding about what happened to the service or whether the request to create an order succeeded, and so making forward progress can be tricky. In this case, the consumer’s only real hope is to try again by repeating the POST request to lodge an order.

Note

In the general case, consumers can try to recompute application state by GET ting the current representations of any resources whose URIs they happen to know. Since GET doesn’t have side effects (that consumers can be held accountable for), it’s safe to call repeatedly until a sufficiently coherent picture of the system state emerges and forward or backward progress can be made. However, at this stage in our ordering protocol, the consumer knows nothing other than the entry point URI for the coffee ordering service, http://restbucks.com/order, and can only retry.

On the server side, if the ordering service is in a recoverable state, or may eventually be, its implementation should be prepared to clean up any state created by the failed interaction. That way, the server keeps its own internal order state consistent whether the client retries or not.

Implementing create with POST

Now that we have a reasonable strategy for handling order creation, let’s see how to put it into practice with a short code sample (see Example 4-5).

Example 4-5. A Java servlet implementation for creating a coffee order

protected void doPost(HttpServletRequest request, HttpServletResponse response) {
  try {
    Order order = extractOrderFromRequest(request);
    if(order == null) {
      response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
    } else {
      String internalOrderId = saveOrder(order);
      response.setHeader("Location", computeLocationHeader(request,
                         internalOrderId));
      response.setStatus(HttpServletResponse.SC_CREATED);
    } catch(Exception ex) {
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }
}

The Java code in Example 4-5 captures the pattern we’re following for processing a POST request on the service side. We extract an order from the POST request content and save it into a database. If that operation fails, we’ll conclude that the request from the consumer wasn’t valid and we’ll respond with a 400 Bad Request response, using the value SC_BAD_REQUEST. If the order is successfully created, we’ll embed that order’s URI in a Location header and respond with a 201 status (using the SC_CREATED value) to the consumer. If anything goes wrong, and an Exception is thrown, the service returns a 500 response using the SC_INTERNAL_SERVER_ERROR value.

Reading Resource State with GET

We’ve already seen how GET can be used to invoke remote methods via URI tunneling, and we’ve also seen it being used to recover from 500 response codes during order creation. From here onward, we’ll be using GET explicitly for retrieving state information—resource representations—from services. In our case, we are going to use GET to retrieve coffee orders from Restbucks that we’ve previously created with POST.

Using GET to implement the “R” in CRUD is straightforward. We know that after a successful POST, the service creates a coffee order at a URI of its choosing and sends that URI back to the consumer in the HTTP response’s Location header. In turn, that URI allows consumers to retrieve the current state of coffee order resources, as we see in Figure 4-5.

Reading a coffee order with GET

Figure 4-5. Reading a coffee order with GET

Performing GET on an order’s URI is very simple. At the HTTP level, it looks like Example 4-6.

Example 4-6. Requesting an order via GET

GET /order/1234 HTTP/1.1
Host: restbucks.com

If the GET request is successful, the service will respond with a 200 OK status code and a representation of the state of the resource, as shown in Example 4-7.

Example 4-7. Reading an order with GET

HTTP/1.1 200 OK
Content-Length: 241
Content-Type: application/xml
Date: Wed, 19 Nov 2008 21:48:10 GMT

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <name>latte</name>
      <quantity>1</quantity>
      <milk>whole</milk>
      <size>small</size>
    </item>
  </items>
</order>

The response from the server consists of a representation of the order created by the original POST request, plus some additional information such as the status, and a collection of useful metadata in the headers. The first line includes a 200 OK status code and a short textual description of the outcome of the response informing us that our GET operation was successful. Two headers follow, which consumers use as hints to help parse the representation in the payload. The Content-Type header informs us that the payload is an XML document, while Content-Length declares the length of the representation in bytes. Finally, the representation is found in the body of the response, which is encoded in XML in accordance with the Content-Type header.

A client can GET a representation many times over without the requests causing the resource to change. Of course, the resource may still change between requests for other reasons. For example, the status of a coffee order could change from “pending” to “served” as the barista makes progress. However, the consumer’s GET requests should not cause any of those state changes, lest they violate the widely shared understanding that GET is safe.

Since Restbucks is a good web citizen, it’s safe to GET a representation of an order at any point. However, clients should be prepared to receive different representations over time since resource state—that is, the order—changes on the server side as the barista prepares the coffee. To illustrate the point, imagine issuing the GET request from Example 4-6 again a few minutes later. This time around, the response is different because the order’s status has changed from paid to served (in the <status> element), since the barista has finished preparing the drink, as we can see in Example 4-8.

Example 4-8. Rereading an order with GET

HTTP/1.1 200 OK
Content-Length: 265
Content-Type: application/xml
Date: Wed, 19 Nov 2008 21:58:21 GMT

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <name>latte</name>
      <quantity>1</quantity>
      <milk>whole</milk>
      <size>small</size>
    </item>
  </items>
  <status>served</status>
</order>

In our CRUD ordering service, we’re only going to consider two failure cases for GET. The first is where the client requests an order that doesn’t exist, and the second is where the server fails in an unspecified manner. For these situations, we borrow inspiration from the Web and use the 404 and 500 status codes to signify that an order hasn’t been found or that the server failed, respectively. For example, the request in Example 4-9 identifies an order that does not (yet) exist, so the service responds with the 404 Not Found status code shown in Example 4-10.

Example 4-9. Requesting an order that doesn’t exist via GET

GET /order/123456789012345667890 HTTP/1.1
Host: restbucks.com

Example 4-10. Ordering service does not recognize an order URI and responds with 404 Not Found

HTTP/1.1 404 Not Found
Date: Sat, 20 Dec 2008 19:01:33 GMT

The 404 Not Found status code in Example 4-10 informs the consumer that the specified order is unknown to the service.[31] On receipt of a 404 response, the client can do very little to recover. Effectively, its view of application state is in violent disagreement with that of the ordering service. Under these circumstances, Restbucks consumers should rely on out-of-band mechanisms (such as pleading with the barista!) to solve the problem, or try to rediscover the URI of their order.

If the consumer receives a 500 Internal Server Error status code as in Example 4-11, there may be a way forward without having to immediately resort to out-of-band tactics. For instance, if the server-side error is transient, the consumer can simply retry the request later.

Example 4-11. Ordering service indicates unexpected failure with a 500 response

HTTP/1.1 500 Internal Server Error
Date: Sat, 20 Dec 2008 19:24:34 GMT

This is a very simple but powerful recovery scenario whose semantics are guaranteed by the behavior of GET. Since GET requests don’t change service state, it’s safe for consumers to GET representations as often as they need. In failure cases, consumers simply back off for a while and retry the GET request until they give up (and accept handing over control to some out-of-band mechanism) or wait until the service comes back online and processing continues.

Implementing read with GET

The code in Example 4-12 shows how retrieving an order via GET can be implemented using JAX-RS [32] in Java.

Example 4-12. Server-side implementation of GET with JAX-RS

@Path("/order")
public class OrderingService {
  @GET
  @Produces("application/xml")
  @Path("/{orderId}")
  public String getOrder(@PathParam("orderId") String orderId) {
    try {
      Order order = OrderDatabase.getOrder(orderId);
      if (order != null) {
        // Use an existing XStream instance to create the XML response
        return xstream.toXML(order);
      } else {
        throw new WebApplicationException(Response.Status.NOT_FOUND);
      }
    } catch (Exception e) {
      throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
    }
  }
  // Remainder of implementation omitted for brevity
}

In Example 4-12, the root path where our service will be hosted is declared using the @Path annotation, which in turn yields the /order part of the URI. The getOrder(…) method is annotated with @GET, @Produces, and @Path annotations that provide the following behaviors:

  • @GET declares that the getOrder(…) method responds to HTTP GET requests.

  • @Produces declares the media type that the method generates as its return value. In turn, this is mapped onto the HTTP Content-Type header in the response. Since the ordering service uses XML for order resource representations, we use application/xml here.

  • @Path declares the final part of the URI where the method is registered, using the URI template {/orderId}. By combining this with the root path declared at the class level, the service is registered at the URI /order/{orderId}.

The orderId parameter to the getOrder(…) method is automatically bound by JAX-RS using the @PathParam annotation on the method’s orderId parameter to match the @Path annotation attached to the method. Once this is all configured, the JAX-RS implementation extracts the order identifier from URIs such as http://restbucks.com/order/1234 and makes it available as the String parameter called orderId in the getOrder(…) method.

Inside the getOrder(…) method, we try to retrieve order information from the database keyed by the orderId parameter. If we find a record matching the orderId, we encode it as an XML document using XStream[33] and return the document. This relinquishes control back to JAX-RS, which in turn packages the XML-encoded order into an HTTP response and returns it to the consumer. If we can’t find the order in the database, the implementation throws a WebApplicationException with the parameter NOT_FOUND, which results in a 404 Not Found response code being returned to the consumer. If something unpredicted goes wrong, such as the loss of database connectivity, we throw a WebApplicationException but with a 500 Internal Server Error status code indicated by the INTERNAL_SERVER_ERROR code. Either way, JAX-RS takes care of all the plumbing for us, including the creation of a well-formed HTTP response.

Note

It’s interesting that the JAX-RS implementation for GET in Example 4-10 deals with a substantial amount of plumbing code on our behalf when compared to the bare servlet implementation in Example 4-3. However, it’s also important to note that we don’t have to use frameworks such as JAX-RS to build CRUD services, since servlets (and other HTTP libraries) can work just as well.

Updating a Resource with PUT

For the uninitiated, HTTP can be a strange protocol, not least because it offers two ways of transmitting information from client to server with the POST and PUT verbs. In their landmark book,[34] Richardson and Ruby established a convention for determining when to use PUT and when to use POST to resolve the ambiguity:

  • Use POST to create a resource identified by a service-generated URI.

  • Use POST to append a resource to a collection identified by a service-generated URI.

  • Use PUT to create or overwrite a resource identified by a URI computed by the client.

This convention has become widely accepted, and the Restbucks ordering service embraces it by generating URIs for orders when they’re created by POST ing to the well-known entry point: http://restbucks.com/order. Conversely, when updating orders via PUT, consumers specify the URIs. Figure 4-6 shows how using different verbs disambiguates the two different cases and simplifies the protocol.

PUT request and responses

Figure 4-6. PUT request and responses

In Figure 4-6, consumers know the URI of the order they want to update from the Location header received in the response to an earlier POST (create) request. Using that URI, a consumer can PUT an updated order representation to the ordering service. In accordance with the HTTP specification, a successful PUT request won’t create a new resource or produce a new URI. Instead, the state of the identified resource will be updated to reflect the data in the request representation.

Example 4-13 shows how a request for an update looks on the wire. While the HTTP headers should look familiar, in this case the HTTP body contains an XML representation of the original order with the contents of the <milk> element for the cappuccino changed to be skim rather than whole.

Example 4-13. Updating an order

PUT /order/1234 HTTP/1.1
Host: restbucks.com
Content-Type: application/xml
Content-Length: 246

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <milk>skim</milk>
      <name>cappuccino</name>
      <quantity>1</quantity>
      <size>large</size>
    </item>
  </items>
</order>

Note

PUT expects the entire resource representation to be supplied to the server, rather than just changes to the resource state. Another relatively unknown HTTP verb, PATCH, has been suggested for use in situations—typically involving large resource representations—where only changes are provided. We’ll use PUT for now, but we’ll also cover the use of PATCH in the next chapter.

When the PUT request is accepted and processed by the service, the consumer will receive either a 200 OK response as in Example 4-14, or a 204 No Content response as in Example 4-15.

Whether 200 is used in preference to 204 is largely an aesthetic choice. However, 200 with a response body is more descriptive and actively confirms the server-side state, while 204 is more efficient since it returns no representation and indicates that the server has accepted the request representation verbatim.

Example 4-14. Successful update with a 200 response

HTTP/1.1 200 OK
Content-Length: 275
Content-Type: application/xml
Date: Sun, 30 Nov 2008 21:47:34 GMT

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
    <item>
      <milk>skim</milk>
      <name>cappuccino</name>
      <quantity>1</quantity>
      <size>large</size>
    </item>
  </items>
  <status>preparing</status>
</order>

Example 4-15. Successful update with a 204 response

HTTP/1.1 204 No Content
Date: Sun, 30 Nov 2008 21:47:34 GMT

On receiving a 200 or 204 response, the consumer can be satisfied that the order has been updated. However, things can and do go wrong in distributed systems, so we should be prepared to deal with those eventualities.

The most difficult of the three failure response codes from Figure 4-6 is where a request has failed because of incompatible state. An example of this kind of failure is where the consumer tries to change its order after drinks have already been served by the barista. To signal conflicting state back to the client, the service responds with a 409 Conflict status code, as shown in Example 4-16.

Example 4-16. Order has already been served as a take-away

HTTP/1.1 409 Conflict
Date: Sun, 21 Dec 2008 16:43:07 GMT
Content-Length:271

<order xmlns="http://schemas.restbucks.com/order">
  <location>takeAway</location>
  <items>
     <item>
      <milk>whole</milk>
      <name>cappuccino</name>
      <quantity>1</quantity>
      <size>large</size>
    </item>
  </items>
  <status>served</status>
</order>

In keeping with the HTTP specification, the response body includes enough information for the client to understand and potentially fix the problem, if at all possible. To that end, Example 4-16 shows that the ordering service returns a representation of the current state of the order resource from the service. In the payload, we can see that the <status> element contains the value served, which indicates that the order cannot be altered. To make progress, the consumer will have to interpret the status code and payload to determine what might have gone wrong.

Note

We might reasonably expect that either 405 Method Not Allowed or 409 Conflict would be a valid choice for a response code in situations where PUTting an update to a resource isn’t supported. In this instance, we chose 409 since PUT may be valid for some updates that don’t violate business rules. For example, it might still be permitted to change the order from drink-in to take-away during the order’s life cycle since it’s just a matter of changing cups.

As with errors when processing POST and GET, a 500 response code is equally straightforward when using PUT—simply wait and retry. Since PUT is idempotent—because service-side state is replaced wholesale by consumer-side state—the consumer can safely repeat the operation as many times as necessary. However, PUT can only be safely used for absolute updates; it cannot be used for relative updates such as “add an extra shot to the cappuccino in order 1234.” That would violate its semantics.

Note

PUT is one of the HTTP verbs that has idempotent semantics (along with GET and DELETE in this chapter). The ordering service must therefore guarantee that PUTting the same order many times has the same side effects as PUTting it exactly once. This greatly simplifies dealing with intermittent problems and crash recovery by allowing the operation to be repeated in the event of failure.

If the service recovers, it simply applies any changes from any of the PUT requests to its underlying data store. Once a PUT request is received and processed by the ordering service, the consumer will receive a 200 OK response.

Implementing update with PUT

Now that we understand the update process, implementation is straightforward, especially with a little help from a framework. Example 4-17 shows an implementation of the update operation using the HTTP-centric features of Microsoft’s WCF. The service contract—the set of operations that will be exposed—is captured by the IOrderingService interface. In turn, the IOrderingService is adorned by a [ServiceContract] attribute that binds the interface to WCF so that the underlying framework can expose implementing classes as services.[35] For our purposes, the most interesting aspect of this code is the [WebInvoke] attribute, which, when used in tandem with an [OperationContract] attribute, declares that the associated method is accessible via HTTP.

Example 4-17. WCF ServiceContract for updating an order with PUT

[ServiceContract]
public interface IOrderingService
{
  [OperationContract]
  [WebInvoke(Method = "PUT", UriTemplate = "/order/{orderId}")]
  void UpdateOrder(string orderId, Order order);

  // Remainder of service contract omitted for brevity
}

The [WebInvoke] attribute takes much of the drudgery out of plumbing together URIs, entity body payloads, and the methods that process representations. Compared to lower-level frameworks, the WCF approach removes much boilerplate plumbing code.

In Example 4-17, the [WebInvoke] attribute is parameterized so that it responds only to the PUT verb, at URIs that match the URI template /order/{orderId}. The value supplied in {orderId} is bound at runtime by WCF to the string parameter orderId, which is then used to process the update.

When invoked, the representation in the HTTP body is deserialized from XML and dispatched to the implementing method as an instance of the Order type. To achieve this, we declare the mapping between the on-the-wire XML and the local Order object by decorating the Order type with [DataContract] and [DataMember] attributes, as shown in Example 4-18. These declarations help the WCF serializer to marshal objects to and from XML. Once the WCF serializer completes the deserialization work, all we need to implement is the update business logic, as shown in Example 4-19.

Example 4-18. Marking up an order for use with WCF

[DataContract(Namespace = "http://schemas.restbucks.com/order", Name = "order")]
public class Order
{

  [DataMember(Name = "location")]
  public Location ConsumeLocation
  {
    get { return location; }
    set { location = value; }
  }

  [DataMember(Name = "items")]
  public List<Item> Items
  {
    get { return items; }
    set { items = value; }
  }

   [DataMember(Name = "status")]
   public Status OrderStatus
   {
     get { return status; }
     set { status = value; }
   }
   // Remainder of implementation omitted for brevity
}

Example 4-19. WCF implementation for updating an order

public void UpdateOrder(string orderId, Order order)
{
  try
  {
    if (OrderDatabase.Database.Exists(orderId))
    {
      bool conflict = OrderDatabase.Database.Save(order);
      if (!conflict)
      {
        WebOperationContext.Current.OutgoingResponse.StatusCode =
        HttpStatusCode.NoContent;
      }
      else
      {
        WebOperationContext.Current.OutgoingResponse.StatusCode =
        HttpStatusCode.Conflict;
      }
    }
    else
    {
      WebOperationContext.Current.OutgoingResponse.StatusCode =
      HttpStatusCode.NotFound;
    }
  }
  catch (Exception)
  {
    WebOperationContext.Current.OutgoingResponse.StatusCode =
    HttpStatusCode.InternalServerError;
  }
}

The code in Example 4-19 first checks whether the order exists in the database. If the order is found, it is simply updated and a 204 No Content status code is returned to the consumer by setting the WebOperationContext.Current.OutgoingResponse.StatusCode property.

If there’s a conflict while trying to update the order, a 409 Conflict response and a representation highlighting the inconsistency will be returned to the consumer.

Note

It’s worth noting that the only identifier we have for the order comes from the URI itself, extracted by WCF via the {orderId} template. There’s no order ID embedded in the payload, since it would be superfluous. Following this DRY (Don’t Repeat Yourself) pattern, we avoid potential inconsistencies between the domain model and the resources the service exposes, and keep the URI as the authoritative identifier, as it should be.

If we can’t find the entry in the database, we’ll set a 404 Not Found response to indicate the order resource isn’t hosted by the service. Finally, if something unexpected happens, we’ll catch any Exception and set a 500 Internal Server Error status code on the response to flag that the consumer should take some alternative (recovery) action.

Removing a Resource with DELETE

When a consumer decides that a resource is no longer useful, it can send an HTTP DELETE request to the resource’s URI. The service hosting that resource will interpret the request as an indication that the client has become disinterested in it and may decide that the resource should be removed—the decision depends on the requirements of the service and the service implementation.

Note

Deleting a resource doesn’t always mean the resource is physically deleted; there are a range of outcomes. A service may leave the resource accessible to other applications, make it inaccessible from the Web and maintain its state internally, or even delete it outright.

Figure 4-7 highlights the use of DELETE in the Restbucks ordering service where DELETE is used to cancel an order, if that order is in a state where it can still be canceled. For example, sending DELETE to an order’s URI prior to preparation should be successful and the client should expect a 204 No Content response from the service as a confirmation.

DELETE request and responses

Figure 4-7. DELETE request and responses

Conversely, if the order has already been prepared, which means it can’t be deleted, a 405 Method Not Allowed response would be used. If the service is unavailable to respond to our DELETE request for some other reason, the client can expect a 503 Service Unavailable response and might try the request again later.

On the wire, DELETE requests are simple, consisting only of the verb, resource URI, protocol version, and HOST (and optional PORT) header(s), as shown in Example 4-20.

Example 4-20. Removing an order with DELETE

DELETE /order/1234 HTTP/1.1
Host: restbucks.com

Assuming the ordering service is able to satisfy the DELETE request, it will respond affirmatively with a 204 No Content response, as shown in Example 4-21.

Example 4-21. Order successfully removed

HTTP/1.1 204 No Content
Date: Tue, 16 Dec 2008 17:40:11 GMT

Note

Some services may elect to return the final state of the deleted resource on the HTTP response. In those cases, 204 isn’t appropriate, and a 200 OK response along with Content-Type and Content-Length headers and a resource representation in the body is used.

Failure cases tend to be intricate with DELETE requests, since they might have significant side effects! One such failure case is shown in Example 4-22, where the client has specified a URI that the server cannot map to an order, causing the ordering service to generate a 404 Not Found response.

Example 4-22. The requested order doesn’t exist

HTTP/1.1 404 Not Found
Content-Length: 0
Date: Tue, 16 Dec 2008 17:42:12 GMT

Although this is a simple response to understand—we see it all too often on the human Web, after all—it’s troubling from a programmatic perspective because it means the consumer has stale information about order resource state compared to the service.

We might take one of several different recovery strategies when we get a 404 Not Found response. Ordinarily, we might prefer a human to resolve the problem through some out-of-band mechanism. However, in some situations, it may be practical for the consumer to recompute application state by retrieving representations of the resources it knows about and attempt to make forward progress once it’s synchronized with the service.

Restbucks archives all orders after they have been served for audit purposes. Once archived, the order becomes immutable, and any attempts to DELETE an archived order will result in a 405 Method Not Allowed response from the ordering service, as shown in Example 4-23.

Example 4-23. Order has been archived

HTTP/1.1 405 Method Not Allowed
Allow: GET
Date: Tue, 23 Dec 2008 16:23:49 GMT

The response in Example 4-23 informs the client that while the order resource still exists, the client is not allowed to DELETE it. In fact, the Allow header is used to convey that GET is the only acceptable verb at this point in time and that requests using any other verb will be met with a 405 Method Not Allowed response.

Note

The Allow header can be used to convey a comma-separated list of verbs that can be applied to a given resource at an instant.

An implementation for DELETE using the HttpListener from the .NET Framework is shown in Example 4-24. Like the servlet implementation in Example 4-5, this example shows that it’s possible to develop services with just an HTTP library, and that we don’t always have to use sophisticated frameworks.

Example 4-24. Using HttpListener to delete an order

static void DeleteResource(HttpListenerContext context)
{
  string orderId = ExtractOrderId(context.Request.Url.AbsolutePath);

  var order = OrderDatabase.Retrieve(orderId);

  if (order == null)
  {
    context.Response.StatusCode = HttpStatusCode.NotFound;
  }
  else if (order.CanDelete)
  {
    OrderDatabase.archive(orderId);
    context.Response.StatusCode = HttpStatusCode.NoContent;
  }
  else
  {
    context.Response.StatusCode = HttpStatusCode.MethodNotAllowed;
  }

  context.Response.Close();
}

In Example 4-24, an HTTPListenerContext instance provides access to the underlying HTTP request and response messages. Using the request URI, we extract an order identifier and then determine whether it corresponds to a valid order. If no order is found, we immediately set the HTTP response to 404 and call Close() on the response object to return control to the web server, which in turn returns a well-formed 404 Not Found response message to the consumer.

If we can find the resource, we check whether we’re allowed to delete it. If we are, we logically remove the associated order before returning a 204 No Content response to the client. Otherwise, we set the response code to 405 and let the client know they can’t delete that resource.

Safety and Idempotency

We saw in Chapter 3 that GET is special since it has the properties of being both safe and idempotent. PUT and DELETE are both idempotent, but neither is safe, while POST is neither safe nor idempotent. Only GET returns the same result with repeated invocations and has no side effects for which the consumer is responsible.

With GET, failed requests can be repeated without changing the overall behavior of an application. For example, if any part of a distributed application crashes in the midst of a GET operation, or the network goes down before a response to a GET is received, the client can just reissue the same request without changing the semantics of its interaction with the server.

In broad terms, the same applies to both PUT and DELETE requests. Making an absolute update to a resource’s state or deleting it outright has the same outcome whether the operation is attempted once or many times. Should PUT or DELETE fail because of a transient network or server error (e.g., a 503 response), the operation can be safely repeated.

However, since both PUT and DELETE introduce side effects (because they are not safe), it may not always be possible to simply repeat an operation if the server refuses it at first. For instance, we have already seen how a 409 response is generated when the consumer and service’s view of resource state is inconsistent—merely replaying the interaction is unlikely to help. However, HTTP offers other useful features to help us when state changes abound.



[28] We’ve adopted the convention used in RESTful Web Services (http://oreilly.com/catalog/9780596529260/) by Leonard Richardson and Sam Ruby (O’Reilly), where POST is used for creation and the server determines the URI of the created resource.

[30] This is demanded of us by the HTTP specification.

[31] If we wanted to be more helpful to the consumer, our service could provide a helpful error message in the HTTP body.

[32] Oracle Corp. website. “JAX-RS (JSR 311): The Java API for RESTful Web Services”; see http://jcp.org/en/jsr/detail?id=311.

[34] RESTful Web Services (http://oreilly.com/catalog/9780596529260/), published by O’Reilly.

[35] WCF implements the same model for all kinds of remote behavior, including queues and WS-* Web Services. This lowest-common-denominator approach seeks to simplify programming distributed systems. Unfortunately, it often hides essential complexity, so use it with care!

The best content for your career. Discover unlimited learning on demand for around $1/day.