Defining Custom HTTP Methods

Earlier this week I saw the following comment:

HTTP defines a limited set of verbs, but APIs should be allowed to define their own verbs. Oftentimes, "get" or "post" or "delete" simply isn’t what you really. Then one of the following usually happens – either we use an HTTP verb that doesn’t accurately describe what’s happening, or we put the real verb somewhere else (e.g. in the URI or JSON body).

It is true that HTTP defines a small set of verbs, or ‘methods’ in the words of the standard. But developers can define their own, yet I rarely see anyone take advantage of this. So today I will show you how and why you may wish to do so.

Note: I’m not going to show any actual code in this article, under the assumption that you know how to use the language and web framework of your choice in order to return HTTP headers in requests, handle requests based on specific HTTP methods, and so on.

Very, Very, Very Brief Refresher on HTTP Methods

Section 5.1.1 of the HTTP 1.1 standard defines the following methods:

  • OPTIONS
  • GET
  • HEAD
  • POST
  • PUT
  • DELETE
  • TRACE
  • CONNECT

Interestingly HTTP servers are only required to support HEAD and GET, but I’ve never seen a (non-joke) server which doesn’t support most, if not all, of the above.

If a server does not support a method at all then it will return the 501 ‘Not Implemented’ status code. We can see this by using httpie, a great command-line tool for communicating with web servers. Let’s say we run the following command:

http --headers RESET "example.com"

This is what we get back:

HTTP/1.1 501 Not Implemented
Connection: close
Content-Length: 357
Content-Type: text/html
Date: Thu, 22 Oct 2015 04:12:05 GMT
Server: ECSF (fty/2FA6)

So we can see from the first line that the server doesn’t recognize or support the RESET method at all. If it does support the method though, that still does not mean every resource supports it. If we try to use a method which the server recognizes but does not allow for a given resource then we receive a 405 ‘Method Not Allowed’ response. For example:

http --headers PUT "example.com"

The result:

HTTP/1.1 405 Method Not Allowed
Cache-Control: max-age=604800
Content-Length: 0
Date: Thu, 22 Oct 2015 04:13:56 GMT
Expires: Thu, 29 Oct 2015 04:13:56 GMT
Server: EOS (lax004/2816)

In this case the server recognizes PUT but it does not allow it on the given resource.

These two error codes will be important to implementing our own HTTP method, so keep them in mind.

Implementing a Custom Method: RESET

Why?

I’m workng on a web-based collectible card game (CCG). Data such as players, cards, their decks, the game world, etc., are all accessible through a RESTful API via HTTP. It makes sense to be able to ‘reset’ some resources. For example, resetting the state of a position in the game world, resetting the stats of a player, and so on.

One way to implement this action would be to allow POST requests for those resources where the request body contains something like this:

{
    "action": "reset"
}

Then the server code would have to parse the response—and I’m only using JSON here because it’s so common these days—extract the action key from the response, and decide what to do from there. In my experience this is extremely common in web APIs: the use of the POST method that contains the real method/verb in the request body.

I don’t want to do this for my game’s API. I would rather have the HTTP method itself convey the specific action instead of tucking it away inside POST or PUT. Savy readers might be wondering why not just use [PATCH][]. Couldn’t I use that method to reset a resource to its original state? Absolutely. But consider the following from the standard:

With PATCH, however, the enclosed entity contains a set of instructions describing how a resource currently residing on the origin server should be modified to produce a new version.

That means I would have to implement how to reset different types of resources on the client-side. I’d rather do that on the server-side, so I can simply tell the server, ‘Hey reset this thing’, without having to worry about client-side code breaking whenever I need to change the logic for resetting a resource. Or in more simple terms: I want more encapsulation than PATCH would provide me in this scenario.

How?

Implementing the behavior for the RESET method is easy using modern web frameworks, by which I mean detecting the method in a request for a given URI.

The more difficult question is this: how do I tell clients they can even use the RESET method in the first place? By using the Allow response header, defined in section 14.7 of the HTTP standard. The Allow header tells the client all of the methods acceptable for a given entity. Here is a real-world example:

http --headers OPTIONS "example.com"

Here’s the response header:

HTTP/1.1 200 OK
Allow: OPTIONS, GET, HEAD, POST
Cache-Control: max-age=604800
Date: Thu, 22 Oct 2015 05:18:04 GMT
Expires: Thu, 29 Oct 2015 05:18:04 GMT
Server: EOS (lax004/45BF)
x-ec-custom-error: 1
Content-Length: 0

We can see Allow: OPTIONS, GET, HEAD, POST, which tells us every method that resource supports; this explains why we got a 405 ‘Method Not Allowed’ response earlier when we used PUT on this same URI. So with all of this in mind, one thing I must do when implementing my RESET method is to return an appropriate Allow header in the response for every entity which supports the method. Every resource which does not allow RESET would return the 405 status code.

And on the subject of status codes, an important aspect of the implementation of my custom RESET method is to never return the 501 ‘Not Implemented’ response. It would be incorrect to ever return that code because the server does implement RESET, just not for every resource. By never returning a 501 I am telling clients, ‘Hey, the RESET method is actually valid, but just not for this URI!’

Benefits?

If I implemented the behavior of RESET by using POST then I have to write code on the server to make sure that every resource supporting POST is aware of the possibility of data asking for a reset in the request body. In contrast, by implementing RESET as an HTTP method proper I can simply accept the method for all resources which Allow it and return the 405 code everywhere else.

A related benefit is that, in contrast to using POST, requests using RESET could probably get by with an empty request body. That’s because the request headers contain all of the necessary information. That saves me work on the server-side since I have no request body data to parse. And it most likely saves me bandwidth because I can get away with an empty request body, not needing to populate it with JSON or anything else to tell the server what I want it to do.

The last major benefit is that by using a custom HTTP method, especially one that affords me the use of empty request bodies, I can more easily modify the server-side implementation without breaking clients. If used POST requests with JSON in their body then I’m stuck with supporting JSON, and I can’t move away from using JSON without breaking existing clients. But a request with RESET for its method and an empty body? If I want to stop supporting JSON in request bodies then I have a few less changes to worry about making on the server-side.

Conclusion

Defining your own HTTP methods is a great tool in the arsenal of any developer creating a web API. Yet I rarely see anyone take advantage of that feature of HTTP. So the next time you’re creating a RESTful API think about creating any custom methods your API may need, especially if you’re using lots of POST requests with data in the body that tells the server what you actually want it to do. Instead of hiding that crucial information in a POST, express it more clearly as a method.

Advertisements

One thought on “Defining Custom HTTP Methods

  1. One advice: don’t do this. All intermediate servers between the client and server must support your method. In practise most drop them, especially if they are there for security purposes.

Add Your Thoughts

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s