Not only does HTTP have a uniform interface, it has a standard set of response codes—possible ways a request can turn out. Though resources can be anything at all, they usually fall into a few broad categories: database tables and their rows, publications and the articles they publish, and so on. When you know what sort of resource a service exposes, you can often anticipate the possible responses to an HTTP request without knowing too much about the resource.
In one sense the standard HTTP response codes (see Appendix B) are just a suggested control flow: a set of instructions about what to do when you get certain kinds of requests. But that’s pretty vague advice, and we can do better. Here I present several prepackaged control flows: patterns that bring together advice about resource design, representation formats, and response codes to help you design real-world services.
These snippets of control flow can be applied to almost any service. I can make very general statements about them because they have nothing to do with the actual nature of your resources. All I’m doing here is picking out a few important HTTP status codes and telling you when to use them.
You should be able to implement these rules as common code that runs before your normal request handling. In Example 7-11 I implemented most of them as Rails filters that run before certain actions, or as Ruby methods that short-circuit a request unless a certain condition is met.
If the client tries to do something without providing the
correct authorization, send a response code of 401 (“Unauthorized”)
along with instructions for correctly formatting the
If the client tries to access a URI that doesn’t correspond to any existing resource, send a response code of 404 (“Not Found”). The only possible exception is when the client is trying to PUT a new resource to that URI.
If the client tries to use a part of the uniform interface that a resource doesn’t support, send a response code of 405 (“Method Not Allowed”). This is the proper response when the client tries to DELETE a read-only resource.
In many web services there’s a strong connection between a resource and something in a SQL database: a row in the database, a table, or the database as a whole. These services are so common that entire frameworks like Rails are oriented to making them easy to write. Since these services are similar in design, it makes sense that their control flows should also be similar.
For instance, if an incoming request contains a nonsensical representation, the proper response is almost certainly 415 (“Unsupported Media Type”) or 400 (“Bad Request”). It’s up to the application to decide which representations make sense, but the HTTP standard is pretty strict about the possible responses to “nonsensical representation.”
With this in mind, I’ve devised a standard control flow for the uniform interface in a database-backed application. It runs on top of the general rules I mentioned in the previous section. I used this control flow in the controller code throughout Chapter 7. Indeed, if you look at the code in that chapter you’ll see that I implemented the same ideas multiple times. There’s space in the REST ecosystem for a higher-level framework that implements this control flow, or some improved version of it.
If the resource already exists, parse the representation and turn it into a series of changes to the state of this resource. If the changes would leave the resource in an incomplete or inconsistent state, send a response code of 400 (“Bad Request”).
If the changes would cause the resource state to conflict with some other resource, send a response code of 409 (“Conflict”). My social bookmarking service sends a response code of 409 if you try to change your username to a name that’s already taken.
If there are no problems with the proposed changes, apply them
to the existing resource. If the changes in resource state mean that
the resource is now available at a different URI, send a response
code of 301 (“Moved Permanently”) and include the new URI in
Location header. Otherwise,
send a response code of 200 (“OK”). Requests to the old URI should now result
in a response code of 301 (“Moved Permanently”), 404 (“Not Found”), or 410 (“Gone”).
There are two ways to handle a PUT request to a URI that doesn’t correspond to any resource. You can return a status code of 404 (“Not Found”), or you can create a resource at that URI. If you want to create a new resource, parse the representation and use it to form the initial resource state. Send a response code of 201 (“Created”). If there’s not enough information to create a new resource, send a response code of 400 (“Bad Request”).
Parse the representation, pick an appropriate URI, and create a new resource
there. Send a response code of 201 (“Created”) and include the URI
of the new resource in the
Location header. If there’s not enough
information provided to create the resource, send a response code of
400 (“Bad Request”). If the provided resource state would conflict
with some existing resource, send a response code of 409
(“Conflict”), and include a
Location header that points to the
Parse the representation. If it doesn’t make sense, send a response code of 400 (“Bad Request”). Otherwise, modify the resource state so that it incorporates the information in the representation. Send a response code of 200 (“OK”).
Earlier I described Atom as an XML vocabulary that describes the semantics of publishing: authors, summaries, categories, and so on. The Atom Publishing Protocol (APP) defines a set of resources that capture the process of publishing: posting a story to a site, editing it, assigning it to a category, deleting it, and so on.
The obvious applications for the APP are those for Atom and online publishing in general: weblogs, photo albums, content management systems, and the like. The APP defines four kinds of resources, specifies some of their behavior under the uniform interface, and defines the representation documents they should accept and serve. It says nothing about URI design or what data should go into the documents: that’s up to the individual application.
The APP takes HTTP’s uniform interface and puts a higher-level uniform interface on top of it. Many kinds of applications can conform to the APP, and a generic APP client should be able to access all of them. Specific applications can extend the APP by exposing additional resources, or making the APP resources expose more of HTTP’s uniform interface, but they should all support the minimal features mentioned in the APP standard.
The ultimate end of the APP is to serve Atom documents to the end user. Of course, the Atom documents are just the representations of underlying resources. The APP defines what those resources are. It defines two resources that correspond to Atom documents, and two that help the client find and modify APP resources.
An APP collection is a resource whose representation is an Atom feed. The document in Example 9-2 has everything it takes to be a representation of an Atom collection. There’s no necessary difference between an Atom feed you subscribe to in your feed reader, and an Atom feed that you manipulate with an APP client. A collection is just a list or grouping of pieces of data: what the APP calls members. The APP is heavily oriented toward manipulating “collection” type resources.
The APP defines a collection’s response to GET and POST requests. GET returns a representation: the Atom feed. POST adds a new member to the collection, which (usually) shows up as a new entry in the feed. Maybe you can also DELETE a collection, or modify its settings with a PUT request. The APP doesn’t cover that part: it’s up to your application.
An APP collection is a collection of members. A member corresponds roughly to an entry in an Atom feed: a weblog entry, a news article, or a bookmark. But a member can also be a picture, song, movie, or Word document: a binary format that can’t be represented in XML as part of an Atom document.
A client creates a member inside a collection by POSTing a
representation of the member to the collection URI. This pattern
should be familiar to you by now: the member is created as a
subordinate resource of the collection. The server assigns the new
member a URI. The response to the POST request has a response code
of 201 (“Created”), and a
Location header that lets the client know
where to find the new resource.
Example 9-5 shows an Atom entry
document: a representation of a member. This is the same sort of
entry tag I showed you in Example 9-2, presented as a standalone XML document.
POSTing this document to a collection creates a new member, which
starts showing up as a child of the collection’s
feed tag. A document like this one might
be how the
entry tag in Example 9-2 got where it is today.
Example 9-5. A sample Atom entry document, suitable for POSTing to a collection
<?xml version="1.0" encoding="utf-8"?> <entry> <title>New Resource Will Respond to PUT, City Says</title> <summary> After long negotiations, city officials say the new resource being built in the town square will respond to PUT. Earlier criticism of the proposal focused on the city's plan to modify the resource through overloaded POST. </summary> <category scheme="http://www.example.com/categories/RestfulNews" term="local" label="Local news" /> </entry>
This vaguely-named type of resource is just a grouping of collections. A typical move is
to serve a single service document, listing all of your collections,
as your service’s “home page.” A service document is an XML document
written using a particular vocabulary, and its media type is
Example 9-6 shows a representation of a typical service document. It describes three collections. One of them is a weblog called “RESTful news,” which accepts a POST request if the representation is an Atom entry document like the one in Example 9-5. The other two are personal photo albums, which accept a POST request if the representation is an image file.
Example 9-6. A representation of a service document that describes three collections
<?xml version="1.0" encoding='utf-8'?> <service xmlns="http://purl.org/atom/app#" xmlns:atom="http://www.w3.org/2005/Atom"> <workspace> <atom:title>Weblogs</atom:title> <collection href="http://www.example.com/RestfulNews"> <atom:title>RESTful News</atom:title> <categories href="http://www.example.com/categories/RestfulNews" /> </collection> </workspace> <workspace> <atom:title>Photo galleries</atom:title> <collection href="http://www.example.com/samruby/photos" > <atom:title>Sam's photos</atom:title> <accept>image/*</accept> <categories href="http://www.example.com/categories/samruby-photo" /> </collection> <collection href="http://www.example.com/leonardr/photos" > <atom:title>Leonard's photos</atom:title> <accept>image/*</accept> <categories href="http://www.example.com/categories/leonardr-photo" /> </collection> </workspace> </service>
How do I know what kind of POST requests a collection will
accept? From the
accept tags. The
accept tag works something like
Accept header, only in
Accept header is
usually sent by the client with a GET request, to tell the server
which representation formats the client understands. The
accept tag is the APP server’s way of
telling the client which incoming representations a collection will
accept as part of a POST request that creates a new member.
My two photo gallery collections specify an
image/*. Those collections will only
accept POST requests where the representation is an image. On the
other hand, the RESTful News weblog doesn’t specify an
accept tag at all. The APP default is to
assume that a collection only accepts POST requests when the
representation is an Atom entry document (like the one in Example 9-5). The
accept tag defines what the collections
are for: the weblog is for textual data, and the photo collections
are for images.
The other important thing about a service document is the
categories tag, which links to a
“category document” resource. The category document says what
categories are allowed.
The APP doesn’t say much about service documents. It specifies their representation format, and says that they must serve a representation in response to GET. It doesn’t specify how service documents get on the server in the first place. If you write an APP application you can hardcode your service documents in advance, or you can make it possible to create new ones by POSTing to some new resource not covered by the APP. You can expose them as static files, or you can make them respond to PUT and DELETE. It’s up to you.
As you can see from Example 9-6, a service document’s representation doesn’t just describe collections: it groups collections into workspaces. When I wrote that representation I put the weblog in a workspace of its own, and grouped the photo galleries into a second workspace. The APP standard devotes some time to workspaces, but I’m going to pass over them, because the APP doesn’t define workspaces as resources. They don’t have their own URIs, and they only exist as elements in the representation of a service document. You can expose workspaces as resources if you want. The APP doesn’t prohibit it, but it doesn’t tell you how to do it, either.
APP members (which correspond to Atom elements) can be put into categories. In Chapter 7, I represented a bookmark’s tags with Atom categories. The Atom entry described in Example 9-5 put the entry into a category called “local.” Where did that category come from? Who says which categories exist for a given collection? This is the last big question the APP answers.
The Atom entry document in Example 9-5 gave its category a “scheme” of
The representation of the RESTful News collection, in the service
document, gave that same URI in its
categories tag. That URI points to the
final APP resource: a category document (see Example 9-7). A category document lists
the category vocabulary for a particular APP collection. Its media
Example 9-7 shows a representation of the
category document for the collection “RESTful News.” This category
document defines three categories: “local,” “international,” and
“lighterside,” which can be referenced in Atom
entry entities like the one in Example 9-5.
Example 9-7. A representation of a category document
<?xml version="1.0" ?> <app:categories xmlns:app="http://purl.org/atom/app#" xmlns="http://www.w3.org/2005/Atom" scheme="http://www.example.com/categories/RestfulNews" fixed="no"> <category term="local" label="Local news"/> <category term="international" label="International news"/> <category term="lighterside" label="The lighter side of REST"/> </app:categories>
The scheme is not fixed, meaning that it’s OK to publish members to the collection even if they belong to categories not listed in this document. This document might be used in an end-user application to show a selectable list of categories for a new “RESTful news” story.
As with service documents, the APP defines the representation format for a category document, but says nothing about how category documents are created, modified, or destroyed. It only defines GET on the category document resource. Any other operations (like automatically modifying the category document when someone files an entry under a new category) are up to you to define.
There’s one important wrinkle I’ve glossed over. It has to do with the “photo gallery” collections I described in Example 9-6. I said earlier that a client can create a new member in a photo gallery by POSTing an image file to the collection. But an image file can’t go into an Atom feed: it’s a binary document. What exactly happens when a client POSTs a binary document to an APP collection? What’s in those photo galleries, really?
Remember that a resource can have more than one representation. Each photo I upload to a photo collection has two representations. One representation is the binary photo, and the other is an XML document containing metadata. The XML document is an Atom entry, the same as the news item in Example 9-5, and that’s the data that shows up in the Atom feed.
Here’s an example. I POST a JPEG file to my “photo gallery” collection, like so:
POST /leonardr/photos HTTP/1.1 Host: www.example.com Content-type: image/jpeg Content-length: 62811 Slug: A picture of my guinea pig [JPEG file goes here]
Slug is a custom HTTP
header defined by the APP, which lets me specify a title for the
picture while uploading it. The slug can show up in several pieces
of resource state, as you’ll see in a bit.
The HTTP response comes back as I described it in Members” earlier in this chapter. The response code
is 201 and the
gives me the URI of the newly created APP member.
201 Created Location: http://www.example.com/leonardr/photos/my-guinea-pig.atom
But what’s at the other end of the URI? Not the JPEG file I uploaded, but an Atom entry document describing and linking to that file:
<?xml version="1.0" encoding="utf-8"?> <entry> <title>A picture of my guinea pig</title> <updated>2007-01-24T11:52:29Z</updated> <id>urn:f1ef2e50-8ec8-0129-b1a7-003065546f18</id> <summary></summary> <link rel="edit-media" type="image/jpeg" href="http://www.example.com/leonardr/photos/my-guinea-pig.jpg" /> </entry>
The actual JPEG I uploaded is at the other end of that
link. I can GET it, of course,
and I can PUT to it to overwrite it with another image. My POST
created a new “member” resource, and my JPEG is a representation of
some of its resource state. But there’s also this other
representation of resource state: the metadata. These other elements
of resource state include:
The title, which I chose (the server decided to use my
Slug as the title) and can
The summary, which starts out blank but I can change.
The “last update” time, which I sort of chose but can’t change arbitrarily.
The URI to the image representation, which the server
chose for me based on my
The unique ID, which the server chose without consulting me at all.
This metadata document can be included in an Atom feed: I’ll
see it in the representation of the “photo gallery” collection. I
can also modify this document and PUT it back to
http://www.example.com/leonardr/photos/my-guinea-pig.atom to change
the resource state. I can specify myself as the
author, add categories, change the title,
and so on. If I get tired of having this member in the collection, I
can delete it by sending a DELETE request to either of its
That’s how the APP handles photos and other binary data as collection members. It splits the representation of the resource into two parts: the binary part that can’t go into an Atom feed and the metadata part that can. This works because the metadata of publishing (categories, summary, and so on) applies to photos and movies just as easily as to news articles and weblog entries.
If you read the APP standard (which you should, since this section doesn’t cover everything), you’ll see that it describes this behavior in terms of two different resources: a “Media Link Entry,” whose representation is an Atom document, and a “Media Resource,” whose representation is a binary file. I’ve described one resource that has two representations. The difference is purely philosophical and has no effect on the actual HTTP requests and responses.
That’s a fairly involved workflow, and I haven’t even covered everything that the APP specifies, but the APP is just a well-thought-out way of handling a common web service problem: the list/feed/collection that keeps having items/elements/members added to it. If your problem fits this domain, it’s easier to use the APP design—and get the benefits of existing client support—than to reinvent something similar (see Table 9-1).
Table 9-1. APP resources and their methods
|Service document||Return a representation (XML)||Undefined||Undefined||Undefined|
|Category document||Return a representation (XML)||Undefined||Undefined||Undefined|
|Collection||Return a representation (Atom feed)||Create a new member||Undefined||Undefined|
|Member||Return the representation identified by this URI. (This is usually an Atom entry document, but it might be a binary file.)||Undefined||Update the representation identified by this URI||Delete the member|
I said earlier that the Atom Publishing Protocol defines only a few resources and only a few operations on those resources. It leaves a lot of space open for extension. One extension is Google’s GData, which adds a new kind of resource and some extras like an authorization mechanism. As of the time of writing, the Google properties Blogger, Google Calendar, Google Code Search, and Google Spreadsheets all expose RESTful web service interfaces. In fact, all four expose the same interface: the Atom Publishing Protocol with the GData extensions.
Unless you work for Google, you probably won’t create any services that expose the precise GData interface, but you may encounter GData from the client side. It’s also useful to see how the APP can be extended to handle common cases. See how Google used the APP as a building block, and you’ll see how you can do the same thing.
The biggest change GData makes is to expose a new kind of resource: the list of search results. The APP says what happens when you send a GET request to a collection’s URI. You get a representation of some of the members in the collection. The APP doesn’t say anything about finding specific subsets of the collection: finding members older than a certain date, written by a certain author, or filed under a certain category. It doesn’t specify how to do full-text search of a member’s text fields. GData fills in these blanks.
GData takes every APP collection and exposes an infinite number of additional resources that slice it in various ways. Think back to the “RESTful News” APP collection I showed in Example 9-2. The URI to that collection was http://www.example.com/RestfulNews. If that collection were exposed through a GData interface, rather than just an APP interface, the following URIs would also work:
http://www.example.com/RestfulNews?q=stadium: A subcollection of the members where the content contains the word “stadium.”
http://www.example.com/RestfulNews/-/local: A subcollection of the members categorized as “local.”
http://www.example.com/RestfulNews?author=Tom%20Servo&max-results=50: At most 50 of the members where the author is “Tom Servo.”
Those are just three of the search possibilities GData
exposes. (For a complete list, see the GData
developer’s guide. Note that not all GData applications
implement all query mechanisms.) Search results are usually
represented as Atom feeds. The feed contains a
entry element for every member of the
collection that matched the query. It also contains OpenSearch
elements (q.v.) that specify how many members matched the query, and
how many members fit on a page of search results.
I mentioned earlier that an Atom feed can contain markup from arbitrary other XML namespaces. In fact, I just said that GData search results include elements from the OpenSearch namespace. GData also defines a number of new XML entities in its own “gd” namespace, for representing domain-specific data from the Google web services.
Consider an event in the Google Calendar service. The collection is someone’s calendar and the member is the event itself. This member probably has the typical Atom fields: an author, a summary, a “last updated” date. But it’s also going to have calendar-specific data. When does the event take place? Where will it happen? Is it a one-time event or does it recur?
Google Calendar’s GData API puts this data in its Atom feeds,
using tags like
gd:recurrence. If the client understands
Google Calendar’s extensions it can act as a calendar client. If it
only understands the APP, it can act as a general APP client. If it
only understands the basic Atom feed format, it can treat the list
of events as an Atom feed.
POST requests are the fly in the ointment that is reliable HTTP. GET, PUT, and DELETE requests can be resent if they didn’t go through the first time, because of the restrictions HTTP places on those methods. GET requests have no serious side effects, and PUT and DELETE have the same effect on resource state whether they’re sent once or many times. But a POST request can do anything at all, and sending a POST request twice will probably have a different effect from sending it once. Of course, if a service committed to accepting only POST requests whose actions were safe or idempotent, it would be easy to make reliable HTTP requests to that service.
POST Once Exactly (POE) is a way of making HTTP POST idempotent, like PUT and DELETE. If a resource supports Post Once Exactly, then it will only respond successfully to POST once over its entire lifetime. All subsequent POST requests will give a response code of 405 (“Method Not Allowed”). A POE resource is a one-off resource exposed for the purpose of handling a single POST request.
You can see the original standard at http://www.mnot.net/drafts/draft-nottingham-http-poe-00.txt.
Think of a “weblog” resource that responds to POST by creating a
new weblog entry. How would we change this design so that no resource
responds to POST more than once? Clearly the weblog can’t expose POST
anymore, or there could only ever be one weblog entry. Here’s how POE
does it. The client sends a GET or HEAD request to the “weblog”
resource, and the response includes the special
HEAD /weblogs/myweblog HTTP/1.1 Host: www.example.com POE: 1
The response contains the URI to a POE resource that hasn’t yet been POSTed to. This URI is nothing more than a unique ID for a future POST request. It probably doesn’t even exist on the server. Remember that GET is a safe operation, so the original GET request couldn’t have changed any server state.
200 OK POE-Links: /weblogs/myweblog/entry-factory-104a4ed
POE-Links are custom HTTP headers defined by
the POE draft.
POE just tells the
server that the client is expecting a link to a POE resource.
POE-Links gives one or more links to POE
resources. At this point the client can POST a representation of its
new weblog entry to
After the POST goes through, that URI will start responding to POST
with a response code of 405 (“Operation Not Supported”). If the client
isn’t sure whether or not the POST request went through, it can safely
resend. There’s no possiblity that the second POST will create a
second weblog entry. POST has been rendered idempotent.
The nice thing about Post Once Exactly is that it works with overloaded POST. Even if you’re using POST in a way that totally violates the Resource-Oriented Architecture, your clients can use HTTP as a reliable protocol if you expose the overloaded POST operations through POE.
An alternative to making POST idempotent is to get rid of POST altogether. Remember, POST is only necessary when the client doesn’t know which URI it should PUT to. POE works by generating a unique ID for each of the client’s POST operations. If you allow clients to generate their own unique IDs, they can use PUT instead. You can get the benefits of POE without exposing POST at all. You just need to make sure that two clients will never generate the same ID.