It is crucial that we do not assign functionality to an HTTP method that supersedes the specification-defined boundaries of that method. For example, an HTTP GET on a particular resource should be read-only. It should not change the state of the resource it is invoking on.
Intermediate services like a proxy-cache, a CDN (Akamai), or your browser rely on you to follow the semantics of HTTP strictly so that they can perform built-in tasks like caching effectively.
If you do not follow the definition of each HTTP method strictly, clients and administration tools cannot make assumptions about your services, and your system becomes more complex.
Let’s walk through each method of our object model to determine which URIs and HTTP methods are used to represent them. For better understanding we will consider eCommerce Example. Hence there will be multiple Orders, Products and Customers available in the system.
Browsing All Orders, Customers, or Products
The Order, Customer, and Product objects in our object model are all very similar in how they are accessed and manipulated. One thing our remote clients will want to do is to browse all the Orders, Customers, or Products in the system. These URIs represent these objects as a group:
To get a list of Orders, Products, or Customers, the remote client will call an HTTP GET on the URI of the object group it is interested in. An example request would look like the following:
Our service will respond with a data format that represents all Orders, Products, or Customers within our system. Here’s what a response would look like:
One problem with this bulk operation is that we may have thousands of Orders, Customers, or Products in our system and we may overload our client and hurt our response times. To mitigate this problem, we will allow the client to specify query parameters on the URI to limit the size of the dataset returned:
Here we have defined two query parameters: startIndex and size. The startIndex parameter represents where in our large list of Orders, Products, or Customers we want to start sending objects from. It is a numeric index into the object group being queried. The size parameter specifies how many of those objects in the list we want to return. These parameters will be optional. The client does not have to specify them in its URI when crafting its request to the server.
Obtaining Individual Orders, Customers, or Products
We can use a URI pattern to obtain individual Orders, Customers, or Products:
We will use the HTTP GET method to retrieve individual objects in our system. Each GET invocation will return a data format that represents the object being obtained:
For this request, the client is interested in getting a representation of the Order with an order id of 222. GET requests for Products and Customers would work the same. The HTTP response message would look something like this:
The response code is 200, “OK”, indicating that the request was successful. The Content-Type header specifies the format of our message body as XML, and finally we have the actual representation of the Order.
Creating an Order, Customer, or Product
There are two possible ways in which a client could create an Order, Customer, or Product within our order entry system: by using either the HTTP PUT or POST method. Let’s look at both ways.
Creating with PUT
The HTTP definition of PUT states that it can be used to create or update a resource on the server. To create an Order, Customer, or Product with PUT, the client simply sends a representation of the new object it is creating to the exact URI location that represents the object:
PUT is required by the specification to send a response code of 201, “Created”, if a new resource was created on the server as a result of the request.
The HTTP specification also states that PUT is idempotent. Our PUT is idempotent, because no matter how many times we tell the server to “create” our Order, the same bits are stored at the /orders/222 location. Sometimes a PUT request will fail and the client won’t know if the request was delivered and processed at the server. Idempotency guarantees that it’s OK for the client to retransmit the PUT operation and not worry about any adverse side effects.
The disadvantage of using PUT to create resources is that the client has to provide the unique ID that represents the object it is creating. While it is usually possible for the client to generate this unique ID, most application designers prefer that their servers (usually through their databases) create this ID. In our hypothetical order entry system, we want our server to control the generation of resource IDs. So what do we do? We can switch to using POST instead of PUT.
Creating with POST
Creating an Order, Customer, or Product using the POST method is a little more complex than using PUT. To create an Order, Customer, or Product with POST, the client sends a representation of the new object it is creating to the parent URI of its representation, leaving out the numeric target ID. For example:
Updating an Order, Customer, or Product
We will model updating an Order, Customer, or Product using the HTTP PUT method. The client PUTs a new representation of the object it is updating to the exact URI location that represents the object. For example, let’s say we wanted to change the price of a product from $199.99 to $149.99. Here’s what the request would look like:
As we stated earlier in this article, PUT is great because it is idempotent. No matter how many times we transmit this PUT request, the underlying Product will still have the same final state.
When a resource is updated with PUT, the HTTP specification requires that you send a response code of 200, “OK”, and a response message body or a response code of 204, “No Content”, without any response body. In our system, we will send a status of 204 and no response message.
We could use POST to update an individual Order, but then the client would have to assume the update was non-idempotent and we would have to take duplicate message processing into account.
Removing an Order, Customer, or Product
We will model deleting an Order, Customer, or Product using the HTTP DELETE method. The client simply invokes the DELETE method on the exact URI that represents the object we want to remove. Removing an object will wipe its existence from the system.
When a resource is removed with DELETE, the HTTP specification requires that you send a response code of 200, “OK”, and a response message body or a response code of 204, “No Content” without any response body. In our application, we will send a status of 204 and no response message.
In our system, Orders can be cancelled as well as removed. While removing an object wipes it clean from our databases, cancelling only changes the state of the Order and retains it within the system. How should we model such an operation?
Here, the cancel query parameter would tell our service that we don’t really want to remove the Order, but cancel it. In other words, we are overloading the meaning of DELETE.
While We are not going to tell you not to do this, We will tell you that you shouldn’t do it. It is not good RESTful design. In this case, you are changing the meaning of the uniform interface. Using a query parameter in this way is actually creating a mini-RPC mechanism. HTTP specifically states that DELETE is used to delete a resource from the server, not cancel it.
That’s it for now in assigning HTTP methods in RESTful web services, Keep Learning and Sharing. 🙂