REST, or Representational State Transfer represents an architectural style for building distributed applications. When applied to the world of web services, REST is most commonly used to refer to the transmission of XML over HTTP, and the identification of XML resources via URIs. According to REST, HTTP, XML and URIs provide all the infrastructure for building robust web services, and most developers can therefore safely skip over the pain of learning SOAP and WSDL. If you are new to REST, check out Paul Prescod’s excellent REST articles on xml.com.
A major element of web services is planning for when things go wrong, and propagating error messages back to client applications. However, unlike SOAP, REST-based web services do not have a well-defined convention for returning error messages. In fact, after surveying a number of REST-based web services in the wild, there appear to be four different alternatives for handling errors. Below, I outline the four alternatives, and then provide my opinion on which option or combination of options is best.
Option 1: Stick to HTTP Error Codes
In this scenario, the web service propagates error messages via standard HTTP Error Codes. For example, assume we have the following URL:
This service expects a single parameter: id indicating a book ID. The service extracts the id parameter, does a database look-up and returns an XML representation of the specified book. If the URL specifies an invalid or obsolete id parameter, the service returns an HTTP 404 Not Found Error Code.
Option 2: Return an Empty Set
In this scenario, the web service always returns back an XML document which can have 0 or more subelements. If some error occurs, an XML document with zero elements is returned. The O’Reilly Meerkat news service currently uses this approach. For example, the following URL connects to Meerkat and requests all Linux related articles from the past two days, and formats the results in RSS 0.91:
Now, try specifying an invalid category. For example, set c =ten, like this:
In this case, Meerkat returns an RSS document with zero
item elements. This indicates that there are no matching results, but it does not indicate whether this is a valid category ID which contains no news items, or an invalid category ID.
Option 3: Use Extended HTTP Headers
In this scenario, the web service always returns an HTTP 200 OK Status Code, but specifies an application specific error code within a separate HTTP Header. The Distributed Annotation System (DAS) currently uses this alternative approach. For example, the following URL requests sequence data from Human Chromosome 1 from the Ensembl DAS Server:
Now, try issuing this request for Human Chromosome 30 (there is no human chromosome 30!):
If you click on the link above, you will see an empty page. However, if you have network sniffer, you can see the following HTTP response:
HTTP/1.1 200 OK Server: Resin/2.0.5 Content-Encoding: gzip X-DAS-Version: 1.5 X-DAS-Server: DazzleServer/0.98 (20030508; BioJava 1.3) X-DAS-Capabilities: dsn/1.0; dna/1.0; types/1.0; stylesheet/1.0; features/1.0; encoding-dasgff/1.0; encoding-xff/1.0; entry_points/1.0; error-segment/1.0; unknown-segment/1.0; component/1.0; sequence/1.0 X-DAS-Status: 403 Content-Type: text/plain Content-Length: 10 Date: Sun, 30 Nov 2003 21:02:13 GMT
As you can see, the DAS server has returned an HTTP 200 OK Status code and a required X-DAS-Status code. In this case, the code 403 refers to a DAS Specific error code: “Bad reference object (reference sequence unknown)”.
Option 4: Return an XML Error Document
In this scenario, the web service always returns an HTTP Status Code of 200, but also includes an XML document containing an application specific error message. The XooMLe application currently uses this approach (XooMLe provides a RESTful API wrapper to the existing SOAP based Google API). For example, the Google API requires that you specify a valid developer token. If you specify an invalid token, XooMLe returns an XML error document. As the XooMLe documentation puts it, “If you do something wrong, XooMLe will tell you in a nice, tasty little XML-package.” For example, try this URL:
The request does not include a Google developer token. Hence, XooMLe returns back the following XML document:
<?xml version="1.0" ?> <xoomleError> <method>doGoogleSearch</method> <errorString>Invalid Google API key supplied</errorString> <arguments> <hl>en</hl> <ie>ISO-8859-1</ie> <key></key> <q>oreilly php</q> </arguments> </xoomleError>
The Amazon.com web services API also follows this approach, and each returned XML document can specify an ErrorMsg element. For example, see the Amazon.com Dev-Lite Schema.
Best Practices for REST Error Handling
Assuming you are busy implementing a REST-based web service, which error handling option do you choose? I don’t believe there are (yet) any best practices for REST error handling (for an overview of other best REST practices, see Paul Costello’s REST presentation, in particular [Side 59].)
Nonetheless, here are my votes for most important criteria:
- Human Readable Error Messages: Part of the major appeal of REST based web services is that you can open any browser, type in the right URL, and see an immediate response — no special tools needed. However, HTTP error codes do not always provide enough information. For example, if we take option 1 above, and request and invalid book ID, we get back a 404 Error Code. From the developer perspective, have we actually typed in the wrong host name, or an invalid book ID? It’s not immediately clear. In Option 3 (DAS), we get back a blank page with no information. To view the actual error code, you need to run a network sniffer, or point your browser through a proxy. For all these reasons, I think Option 4 has a lot to offer. It significantly lowers the barrier for new developers, and enables all information related to a web service to be directly viewable within a web browser.
- Application Specific Errors: Option 1 has the disadvantage of not being directly viewable within a browser. It also has the additional disadvantage of mapping all HTTP error codes to application specific error codes. HTTP status codes are specific to document retrieval and posting, and these may not map directly to your application domain. For example, one of the DAS error codes relates to invalid genomic coordinates (sequence coordinate is out of bounds/invalid). What HTTP error code would we map to in this case?
- Machine Readable Error Codes: As a third criteria, error codes should be easily readable by other applications. For example, the XooMLe application returns back only human readable error messages, e.g. “Invalid Google API key supplied”. An application parsing a XooMLe response would have to search for this specific error message, and this can be notoriously brittle — for example, the XooMLe server might simply change the message to “Invalid Key Supplied”. Error codes, such as those provided by DAS are important for programmatic control, and easy creation of exceptions. For example, if XooMLe returned a 1001 error code, a client application could do a quick lookup and immediately throw an InvalidKeyException.
Based on these three criteria, here’s my vote for best error handling option:
- Use HTTP Status Codes for problems specifically related to HTTP, and not specifically related to your web service.
- When an error occurs, always return an XML document detailing the error.
- Make sure the XML error document contains both an error code, and a human readable error message. For example:
<?xml version="1.0" encoding="UTF-8" ?> <error> <error_code>1001</error_code> <error_msg>Invalid Google API key supplied</error_msg> </error>
By following these three simple practices, you can make it significantly easier for others to interface with your service, and react when things go wrong. New developers can easily see valid and invalid requests via a simple web browser, and programs can easily (and more robustly) extract error codes and act appropriately.
What is the best option for propagating error messages from REST-based web services? Are there other options beyond the four described here?