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

Aligning Resource State

In a distributed application, it’s often the case that several consumers might interact with a single resource, with each consumer oblivious to changes made by the others. As well as these consumer-driven changes, internal service behaviors can also lead to a resource’s state changing without consumers knowing. In both cases, a consumer’s understanding of resource state can become misaligned with the service’s resource state. Without some way of realigning expectations, changes requested by a consumer based on an out-of-date understanding of resource state can have undesired effects, from repeating computationally expensive requests to overwriting and losing another consumer’s changes.

HTTP provides a simple but powerful mechanism for aligning resource state expectations (and preventing race conditions) in the form of entity tags and conditional request headers. An entity tag value, or ETag, is an opaque string token that a server associates with a resource to uniquely identify the state of the resource over its lifetime. When the resource changes—that is, when one or more of its headers, or its entity body, changes—the entity tag changes accordingly, highlighting that state has been modified.

ETag s are used to compare entities from the same resource. By supplying an entity tag value in a conditional request header—either an If-Match or an If-None-Match request header—a consumer can require the server to test a precondition related to the current resource state before applying the method supplied in the request.

Note

ETags are also used for cache control purposes, as we’ll see in Chapter 6.

To illustrate how ETags can be used to align resource state in a multiconsumer scenario, imagine a situation in which a party of two consumers places an order for a single coffee. Shortly after placing the order, the first consumer decides it wants whole milk instead of skim milk. Around the same time, the second consumer decides it, too, would like a coffee. Neither consumer consults the other before trying to amend the order.

To begin, both consumers GET the current state of the order independently of each other. Example 4-25 shows one of the consumer’s requests.

Example 4-25. Consumer GETs the order

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

The service’s response contains an ETag header whose value is a hash of the returned representation (Example 4-26).

Example 4-26. Service generates a response with an ETag header

HTTP/1.1 200 OK
Content-Type: application/xml
Content-Length: 275
ETag: "72232bd0daafa12f7e2d1561c81cd082"

<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>pending</preparing>
</order>

Note

The service computes the entity tag and supplies it as a quoted string in the ETag header prior to returning a response. Entity tag values can be based on anything that uniquely identifies an entity: a version number associated with a resource in persistent storage, one or more file attributes, or a checksum of the entity headers and body, for example. Some methods of generating entity tag values are more computationally expensive than others. ETags are often computed by applying a hash function to the resource’s state, but if hashes are too computationally expensive, any other scheme that produces unique values can be used. Whichever method is used, we recommend attaching ETag headers to responses wherever possible.

When a consumer receives a response containing an ETag, it can (and should) use the value in any subsequent requests it directs to the same resource. Such requests are called conditional requests. By supplying the received entity tag as the value of an If-Match or If-None-Match conditional header, the consumer can instruct the service to process its request only if the precondition in the conditional header holds true.

Of course, consumers aren’t obliged to retransmit ETags they’ve received, and so services can’t expect to receive them just because they’ve been generated. However, consumers that don’t take advantage of ETags are disadvantaged in two ways. First, consumers will encounter increased response times as services have to perform more computation on their behalf. Second, consumers will discover their state has become out of sync with service state through status codes such as 409 Conflict at inconvenient and (because they’re not using ETags) unexpected times. Both of these failings are easily rectified by diligent use of ETags.

An If-Match request header instructs the service to apply the consumer’s request only if the resource to which the request is directed hasn’t changed since the consumer last retrieved a representation of it. The service determines whether the resource has changed by comparing the resource’s current entity tag value with the value supplied in the If-Match header. If the values are equal, the resource hasn’t changed. The service then applies the method supplied in the request and returns a 2xx response. If the entity tag values don’t match, the server concludes that the resource has changed since the consumer last accessed it, and responds with 412 Precondition Failed.

Note

Services are strict about processing the If-Match header. A service can’t (and shouldn’t) do clever merges of resource state where one coffee is removed and another, independent coffee in the same order is changed to decaf. If two parts of a resource are independently updatable, they should be separately addressable resources. For example, if fine-grained control over an order is useful, each cup of coffee could be modeled as a separate resource.

Continuing with our example, the first consumer does a conditional PUT to update the order from skim to whole milk. As Example 4-27 shows, the conditional PUT includes an If-Match header containing the ETag value from the previous GET.

Example 4-27. The first consumer conditionally PUTs an updated order

PUT /order/1234 HTTP/1.1
Host: restbucks.com
If-Match: "72232bd0daafa12f7e2d1561c81cd082"

<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>pending</preparing>
</order>

Because the order hadn’t been modified since the first consumer last saw it, the PUT succeeds, as shown in Example 4-28.

Example 4-28. The conditional PUT succeeds

HTTP/1.1 204 No Content
ETag: "6e87391fdb5ab218c9f445d61ee781c1"

Notice that while the response doesn’t include an entity body, it does include an updated ETag header. This new entity tag value reflects the new state of the order resource held on the server (the result of the successful PUT).

Oblivious to the change that has just taken place, the second consumer attempts to add its order, as shown in Example 4-29. This request again uses a conditional PUT, but with an entity tag value that is now out of date (as a result of the first consumer’s modification).

Example 4-29. The second consumer conditionally PUTs an updated order

PUT /order/1234 HTTP/1.1
Host: restbucks.com
If-Match: "72232bd0daafa12f7e2d1561c81cd082"

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

The service determines that the second consumer is trying to modify the order based on an out-of-date understanding of resource state, and so rejects the request, as shown in Example 4-30.

Example 4-30. The response indicates a precondition has failed

HTTP/1.1 412 Precondition Failed

When a consumer receives a 412 Precondition Failed status code, the correct thing to do is to GET a fresh representation of the current state of the resource, and then use the ETag header value supplied in this response to retry the original request, which is what the second consumer does in this case. Having done a fresh GET, the consumer sees that the original order had been modified. The second consumer is now in a position to PUT a revised order that reflects both its and the first consumer’s wishes.

Our example used the If-Match header to prevent the second consumer from overwriting the first consumer’s changes. Besides If-Match, consumers can also use If-None-Match. An If-None-Match header instructs the service to process the request only if the associated resource has changed since the consumer last accessed it. The primary use of If-None-Match is to save valuable computing resources on the service side. For example, it may be far cheaper for a service to compare ETag values than to perform computation to generate a representation.

Note

If-None-Match is mainly used with conditional GETs, whereas If-Match is typically used with the other request methods, where race conditions between multiple consumers can lead to unpredictable side effects unless properly coordinated.

Both If-Match and If-None-Match allow the use of a wildcard character, *, instead of a normal entity tag value. An If-None-Match conditional request that takes a wildcard entity tag value instructs the service to apply the request method only if the resource doesn’t currently exist. Wildcard If-None-Match requests help to prevent race conditions in situations where multiple consumers compete to PUT a new resource to a well-known URI. In contrast, an If-Match conditional request containing a wildcard value instructs the service to apply the request only if the resource does exist. Wildcard If-Match requests are useful in situations where the consumer wishes to modify an existing resource using a PUT, but only if the resource hasn’t already been deleted.

Note

As well as ETag and its associated If-Match and If-None-Match headers, HTTP supports a timestamp-based Last-Modified header and its two associated conditional headers: If-Modified-Since and If-Unmodified-Since. These timestamp-based conditional headers act in exactly the same way as the If-Match and If-None-Match headers, but the conditional mechanism they implement is accurate only to the nearest second—the limit of the timestamp format used by HTTP. Because timestamps are often cheaper than hashes, If-Modified-Since and If-Unmodified-Since may be preferable in solutions where resources don’t change more often than once per second.

In practice, we tend to use timestamps as cheap ETag header values, rather than as Last-Modified values. By using ETags from the outset, we ensure that the upgrade path to finer-grained ETags is entirely at the discretion of the service. The service can switch from using timestamps to using hashes without upsetting clients.

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