Error handling in web applications is devilishly complex – we ended up rewriting SIAS’s error handling code in almost every release.
There are two main sources of complexity. First, how do you handle errors that occur after you’ve already started returning a response to the client? The easiest solution, and the one Rails adopts, is to cache the generated result until its complete. If an exception occurs you dump the result and then render an error message – making damn sure your error message generating code is beyond flawless. This works if your results tend to be small, but breaks down if you need to return a lot of data to the client. For this article, let’s stick with Rails assumption (we couldn’t use it for SIAS).
The second issue is how do you return errors to the client – what format and HTTP status codes should you return? Unfortunately, most standards are mum on this point. For example, nowhere in the Atom Publishing Format does it tell you what to do when an error occurs. On the other extreme, the Web Map Server (WMS) standard, which we supported in SIAS, goes overboard. Since a Web Map Server is supposed to return a map, usually in a raster format such as PNG or JPEG, the standard specifies that errors should be returned as bitmaps that contain the error message (bet you never thought of that!). It also requires that you support returning errors as “blank” images (so as not to block out any other maps) and then, for a fun twist, as XML documents.
Its on this second point that Rails is lacking – error messages are always returned in HTML and are mapped to two HTTP status codes – 404 (not found) and 500 (internal server error).
An Error Handling Plugin
The plugin does four things:
- Adds an :exception parameter to render, so you can write this code:
rescue => e render(:exception => e) end
- Adds a method, http_status_code, to the various Rails exceptions. For example:
module ActiveRecord class RecordNotFound def http_status_code 404 end end end
- Overrides rescue_action_locally and rescue_action_in_public to use render(:exception)
- Defines a number of error handling templates – such as error_404.rhtml, error_404.rjson, error_404.ratom, error_406.rhtml, etc.
When an exception is raised, the plugin checks to see if the exception object responds to http_status_code. If it does, and if the value is not 500, then the plugin picks one of the error templates using the content negotiation plugin I blogged about last week. Otherwise, the plugin reverts back to the default Rails error handling.
The end result is that errors are returned using the appropriate mime type and http status code. So if a client asks for an Atom feed of a non-existent record, the server returns 404 and an error message as an Atom entry. Similiarly, if the client asks for an HTML document for a non-existent record, the server returns 404 and an HTML error message.
Using the Plugin
To install the plugin:
- Install the content negotiation plugin.
- Install the render_exception plugin.
- Copy the error templates directory to your views directory (edge Rails introduces a view search path, but I’m assuming you are using Rails 1.2.x). Thus you’ll end up with a directory app/views/errors that contains a number of error handling templates.
- Modify the error templates to suite your needs. Most are fairly generic, so can be used as-is, but others will require some changes
- Don’t forget that the plugin overrides rescue_action_in_public, so make sure that its fits into your global error handling scheme.
And that should do the trick.