O'Reilly logo

RESTful Web Services by Sam Ruby, Leonard Richardson

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Custom Places

Now I’m ready to go through the resource design procedure all over again. This time I’m designing the custom places clients can create: places that will show up on maps alongside the built-in places. Hopefully you’re getting the hang of the procedure by now (if not, take heart: I’ll do it some more in the next chapter), so this trip through it will be somewhat abbreviated. This time I want to focus on what makes custom places different from user accounts.

Figure Out the Data Set

A web service client can create any number of places on any of the planets for which I have maps. Custom places will show up in lists of search results, just like the built-in places from the previous chapter. Custom places can have the same data as built-in places: a type (“city”), a name (“Springfield”), coordinates of latitude and longitude (“39.81E 89.64W”), and a textual description (“The capital of Illinois”). Many custom places may share the same coordinates (“My house” and “My current location”), and a custom place may share a location with a built-in place.

Every custom place is associated with some user account. Custom places may be public or private. A private place is visible and modifiable only to someone who provides the credentials for the user account that “owns” the place.

Split the Data Set into Resources

Each custom place will be a resource, just as every built-in place is. I also want to let clients get a list of their custom places. In my design, a user account is just a password-protected list of places, so I won’t be exposing the place list as a separate resource. Instead I’ll expand the “user account” resource so it encompasses a user’s list of places. This is analogous to the way a bucket in Amazon S3 is represented as nothing but a list of objects.

Name the Resources with URIs

A custom place is clearly a subordinate resource, but subordinate to what? I could reasonably associate it with a user account, a geographic point on some planet, or an enclosing place like a city, country, or planet. Which of these relationships should I capture with my URIs?

I’ve chosen to name custom places much the same way I name built-in places. Each place is associated with a geographic point, and can be accessed with a URI of the form /user/{username}/{planet}/{latitude},{longitude}/{place name}. The new element is {username}, intended to distinguish between different people’s views of the same place: for instance, Sam’s review of Joe’s Diner at /user/samruby/Earth/45.2,-114.2/Joe’s%20Diner and Leonard’s less glowing review at /user/leonardr/Earth/45.2,-114.2/Joe's%20Diner.

A URI like /Earth/USA?show=Joe's+Diner works like it did before: it returns search results for places called “Joe’s Diner,” anywhere in the U.S. The only difference is that now there are more possible places to search: not only the built-in database of places, but each user’s public list of places, and your own private list.

Built-in places are still privileged. As it happens, there’s a Japanese theme park that includes a one-third scale model of Mount Rushmore. If a client creates a custom place called “Mount Rushmore” north of Tokyo, /Earth/Mount%20Rushmore still points to the original in South Dakota. It doesn’t suddenly become ambiguous which “Mount Rushmore” resource that URI refers to. However, /Earth?show=Mount+Rushmore will show both places.

Expose a Subset of the Uniform Interface

Clients can use GET and HEAD to retrieve representations of built-in places, their own places (whether public or private), and public places created by others. Clients can delete their own places with DELETE, and change the state of their places with PUT.

There are two ways a client might create a map annotation. The client might add a comment to an existing place on the map (“Mount Rushmore”), or it might give a new name to a certain point of latitude and longitude (“the cornfield where I kissed Betty”).

In the first case, the resource being created is “Mount Rushmore (from leonardr’s point of view).” When creating this resource the client shouldn’t have to know exactly where on the map Mount Rushmore is. “Mount Rushmore” is a consensus name and there’s a built-in place by that name. The client can rely on the server to look up the coordinates. In the second case, the resource being created is a brand new place that the server’s never heard of, and the client is responsible for knowing the coordinates.

How can I work this feature into my resource-oriented design? “Mount Rushmore (from leonardr’s point of view)” is a subordinate resource of another resource: the built-in place “Mount Rushmore.” This resource already exists and has a URI: one of them is /Earth/Mount%20Rushmore. If the client wants to reuse the consensus name for a place, it shouldn’t have to look up its location. Instead of figuring out the final URI of the annotation and sending a PUT request to it, the client can send a POST request to the “Mount Rushmore” URI and let the server figure out the ultimate URI.

Similarly, if the client wants to comment on the Alabama capitol building, it can POST to /Earth/USA/AL/State%20capitol instead of figuring out the exact coordinates or street address. Any URI that identifies a built-in place can be the target of a POST request that comments on that place.

What about custom names? What if a client wants to give the name “Mount Rushmore” not to the original in South Dakota, but to the scale model in Imaichi? What if the client wants to create an annotation for “the cornfield where I kissed Betty”?

Here the client must know the latitude and longitude of the place it wants to create. This means it’ll have all the information necessary to create the URI of the new resource: the world, a geographic point on the world, the name of the place, and its own username. The client could make a PUT request to a URI like /user/bob/Earth/42,-93.7/the%20cornfield%20where.... This would work just like creating a user account by sending a PUT request to /user/bob.

Even here, it’s cleaner to use POST. A brand-new place on the map is a subordinate resource: it’s subordinate to some point on the planet, just like a comment on a built-in place is subordinate to a place on the planet. So a client could also put a new place on the map by sending a POST request to /Earth/42,-93.7. It works the same way as a comment on existing places (a POST to /Earth/Mount%20Rushmore), except here the place is identified by latitude and longitude, not by consensus name.

My service will support POST for brand-new places because that’s simpler. The interface will be the same whether you’re adding a brand new place to the planet, or making a comment on some consensus place. Another service might support both methods: PUT to the final URI if the client is willing to figure out that URI, and POST to a parent URI if it’s not.

Finally, note that although I’m using POST, it’s not overloaded POST. Clients of my service use POST only when they want to create a resource “beneath” an existing one. The URI of the new resource (/user/leonardr/Earth/43.9,-103.46/Mount%20Rushmore) may not directly extend the URI of the old (/Earth/Mount%20Rushmore), but the resources have a conceptual relationship.

Design the Representation(s) Accepted from the Client

When the client sticks a pin into a planet and creates a custom place, what information does it need to provide? It must identify a planet and a place on that planet: the spot where the pin goes. The place can be identified either by latitude and longitude, or by reference to a canonical name like “Mount Rushmore.” Call these variables planet, latitude, longitude, and name. The server must know what type of place the client is putting on the map. A place may be public or not, and the client may provide a custom description of the place. The final URI also incorporates a username, but the client is already providing that, in the Authorization header. There’s no need to make the client send that information twice.

These are all key-value pairs. I can have clients represent places the way they represent user accounts: as form-encoded strings. There are no complex data structures here that might call for a JSON or XML representation.

Client requests may choose to send some key-value pairs and omit others. Information that’s in the URI as scoping information doesn’t need to be repeated in the representation. When the client sends a POST to /Earth/43.9,-103.46 it doesn’t need to specify latitude and longitude, because that information’s in the URI. It does need to specify name and type.

When the client sends a POST to /Earth/Mount%20Rushmore it shouldn’t specify latitude, longitude, or name. The client is making a new place based on a well-known existing place, and the new place will inherit the name and location of the existing place. The client may specify a custom type (“national-park,” “political,” “places in North Dakota”) or inherit the default (“monument”).

The client may always choose to omit description and public. My service sets default values for those variables: descriptions are empty by default, and places are public by default.

When the client modifies one of its custom places, anything and everything about the place might change: its name, its location, its type, its description, or its public status. The PUT request that modifies a place can specify the same key-value pairs used to create a place, in any combination. The server will make the appropriate changes, assuming the changes make sense.

Example 6-6 shows a sample HTTP POST request that creates a new custom place. Combined, the form-encoded representation and the scoping information in the URI convey all required states for the new resource. The name and location of the new resource come from the scoping information; its type and description come from the representation. Since the representation doesn’t specify a value for public, the default takes over and this new resource is made public.

Example 6-6. An HTTP request that creates a subordinate resource

POST /Earth/USA/Mount%20Rushmore HTTP/1.1
Host: maps.example.com
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=


Design the Representation(s) Served to the Client

Most of the work here is already done. In Chapter 5 I defined an XHTML-based representation format for places. Custom places look the same as places from the built-in database.

The only new part is this: when an authenticated client requests a representation of one of its custom places, our service will tack onto the representation some hypermedia showing the client how to edit that place (see Example 6-7). I don’t need to tell clients how to delete the place: the uniform interface takes care of that. But I do need to convey the information I wrote in prose above: that a place is defined by planet, latitude, longitude, and so on.

Example 6-7. A hypermedia form showing the client how to edit one of its places

<form id="modifyPlace" method="PUT" action="">
 <p>Modify this place:</p>

  Name: <input name="name" value="Mount Rushmore" type="text" /><br />
  Type: <input name="type" value="national-park" type="text" /><br />
   <input name="latitude" value="43.9" type="text" />,
   <input name="longitude" value="-103.46" type="text" /><br />
   <textarea name="description">We visited on 3/5/2005</textarea><br />
   <input name="public" type="checkbox" value="on"/>
  <input type="submit" />

The caveats from earlier apply here too. This isn’t valid XHTML 4, though it is valid XHTML 5, because it specifies PUT as its method. Also, a client doesn’t know what to do with this form unless it’s been programmed in advance. Computers don’t know what “modifyPlace” means or what data might be a good value for “latitude.”

Because clients have to be programmed in advance to understand these forms, most of today’s services don’t include a form for modifying a resource in that resource’s representation. They either serve all the forms up front (in a WSDL or WADL file), or they specify them in prose (as I did above) and leave it for the service programmer to figure out. It’s debatable whether it’s really helpful to serve forms along with representations, but serving them is better than just specifying the API in prose and making the programmer implement it.

Link This Resource to Existing Resources

I’ve got three kinds of integration to do. The first is data integration. When you DELETE a user account, the account’s custom places—everything under /user/{username}—should also be deleted. URIs to these resources used to work, but now they will return a response code of 410 (“Gone”) or 404 (“Not Found”).

The other kinds of integration should be familiar by now. They involve changing the representations of existing resources to talk about the new one. I want search results to link to custom places. I want points on the globe to show how the user can create a custom place at that point. I want to improve my connectedness by connecting “custom place” resources to the resources I defined already.

The rather empty-looking representation of a user’s account, seen in Example 6-3, badly needs some link-based integration. This is the ideal place to list a user’s custom places. I’ll represent the place list with the same XHTML list of links I use to represent search results.

In the service defined in Chapter 5, a client that searched for places called “Mount Rushmore” (/Earth?show=Mount+Rushmore) would only find places from my built-in place database: probably only the “consensus” location of Mount Rushmore in South Dakota. In the new version of the service, there’s likely to be more than one result. In the new version, that search will also return other users’ annotations for Mount Rushmore, and other places that users have named “Mount Rushmore,” like the scale model in Imaichi.

This is the same case as in Chapter 5, where the built-in place database contained more than one “Joe’s diner.” I present search results in a list, each linking to a specific resource. All I’m doing is expanding the search. A search result may be a place in the built-in database, a custom place created by some other user and exposed publicly, or a custom place created by the authenticated user (which may be public or private).

I also need to show the client how to create its own places on the map. Custom places are created as subordinate resources of existing places. The logical thing to do is to put that information in the representations of those places: places with URIs like /Earth/Mount%20Rushmore and /Earth/42,-93.7.

Example 6-8 is a possible representation of /Earth/43.9,-103.46 that brings together most of what I’ve covered in the past two chapters. This representation abounds in hypermedia. It links to a certain point on several different maps, a place from the built-in database, custom places from other users, and a custom place created by the authenticated user. It also has a hypermedia form that will let the authenticated user create a new custom place at these coordinates. Compare this representation to the smaller representation of /Earth/43.9,-103.46 back in Example 5-9.

Example 6-8. An XHTML representation of 43.9N 103.46W on Earth

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">

 <title>43.9&deg;N 103.46&deg;W on Earth</title>

<p class="authenticated">
 You are currently logged in as 
 <a class="user" href="/user/leonardr">leonardr</a>.

 Welcome to 
 <a class="coordinates" href="/Earth/43.9,-103.46">43.9&deg;N 
 on scenic <a class="place" href="/Earth">Earth</a>.

<p>See this location on a map:</p>

<ul class="maps">
 <li><a class="map" href="/road/Earth/43.9,-103.46">Road</a></li>
 <li><a class="map" href="/satellite/Earth/43.9,-103.46">Satellite</a></li>

<p>Places at this location:</p>

<ul class="places">
  <a class="builtin" href="Mount%20Rushmore">Mount Rushmore</a>
  System data says: 
  <span class="description">The world's largest sculpture</span>

  <a class="custom" href="Mt.%20Rushmore/user1">Mt. Rushmore</a>
  <a class="user" href="/users/user1">user1</a> says: 
  <span class="description">Built on land stolen from the Lakota tribe</span>

  <a class="custom" href="Mount%20Rushmore%20Gift%20Shop/user2">
   Mount Rushmore Gift Shop
  <a class="user" href="/users/user1">user1</a> says: 
  <span class="description">Best fudge I've ever had</span>

  <a class="custom-private" href="Mount%20Rushmore/leonardr">Mt. Rushmore</a>
  You said: <span class="description">We visited on 3/5/2005</span>

<form id="searchPlace" method="get" action="">
  Show nearby places, features, or businesses: 
  <input name="show" repeat="template" /> <input class="submit" />

<form id="createPlace" method="post" action="">
 <p>Create a new place here:</p>

  Name: <input name="name" value="" type="text" /><br />
  Type: <input name="type" value="" type="text" /><br />
   <textarea name="description"></textarea><br />
   <input name="public" type="checkbox" value="on"/>
  <input type="submit" />


What’s Supposed to Happen?

This new resource, the custom place, mostly works like other resources I’ve already defined. A custom place responds to GET just like a built-in place. It responds to PUT (with a representation consisting of key-value pairs) and DELETE (with no representation) just like “user account” resources do. I only have a couple new edge cases to consider here.

When the client creates a custom place, the response code is 201 (“Created”). This works the same way as users. But it was never possible to cause a user’s URI to change, because I prohibited users from changing their usernames. It’s possible to change the name of a place, or to move one (say, a ship) from one point on the map to another. Either of these actions will change the URI.

When the client modifies a custom place without changing its location, the response code will be 200 (“OK”). If the location changes, the response code will be 301 (“Moved Permanently”) and the Location header will contain the place’s new URI. The client is responsible for updating its data structures to keep track of the new URI. This ties into a debate I’ll revisit in Chapter 8, about whether it’s more important to have URIs that contain useful information, or URIs that never change. My URIs describe a custom place using two pieces of resource state: coordinates and name (/user/leonardr/Earth/43.9,-103.46/Mt.%20Rushmore). If either of those changes, the old URI breaks.

Broken URIs are no fun on the human web, and they’re even less fun on the programmable web. If my custom “place” is a ship or something else that’s constantly moving, it effectively has no permanent URI. This is the single biggest design flaw in my system. If I were exposing this as a real web service, I’d probably give a “permalink” to every place: an alternate URI that doesn’t incorporate any changeable resource state. Since everything about a place can change except the planet it’s on and the person who owns it, these URIs will not look very friendly: my annotation of Mount Rushmore might be accessible from /user/leonardr/Earth/36028efa8. But at least they’ll always refer to the same place.

What Might Go Wrong?

This new kind of resource introduces new error conditions, but most of them are variations of ones I’ve already covered, so I’ll pass over them quickly. The client might try to move an existing place off of the map by providing an invalid latitude or longitude: the response code is 400 (“Bad Request”), just as it was in a similar case in Chapter 5. The 400 response code is also appropriate when a client tries to create a place without providing all the information the server needs. This is similar to the 400 response code the server sends if the client tells the server to change a user’s password, but doesn’t actually provide the new password.

My service doesn’t allow a single user to define more than one place with the same name at the same coordinates. /user/leonardr/Earth/43.9,-103.46/Mt.%20Rushmore can only identify one place at a time. Suppose a client has two places called “My car,” and makes a PUT request that would move one to the location of the other. My service rejects this request with a response code of 409 (“Conflict”). There’s nothing wrong with moving a place to a certain set of coordinates; it’s just that right now there happens to be another place with that name there. The same 409 response code would happen if the client had two custom places at the same coordinates, and tried to rename one to match the name of the other. In either case, the client is making a syntactically valid request that would put the system’s resources into an inconsistent state. It’s the same as trying to create a user that already exists.

There’s one totally new error condition worthy of attention: the client may try to access a private place created by someone else. There are two possibilities. The first is to deny access with response code 403 (“Forbidden”). The 403 response code is used when the client provides no authentication, or insufficient authentication; the latter certainly applies in this case.

But a response code of 403 is a tacit admission that the resource exists. The server should not be giving out this information. If client A creates a custom place and marks it private, client B should not be able to figure out anything about it, even its name, even by guessing. When revealing the existence of a resource would compromise security, the HTTP standard allows the server to lie, and send a response code of 404 (“Not Found”).

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required