Fleegix.org

Format-specific server errors for your Ajax app using Merb

2008-06-27 00:48:00

Handling server-side errors in Ajaxy Web apps has always been annoying. There is a raft of articles on the subject, and I've seen it brought up in numerous conference talks, too.

In lots of articles, the idea of 'handling' the error is limited to printing some scrap of text for the user (often in our favorite Philip-Glass-mimimalist piece of 'user interface,' the alert box). Super-cool, you're trying to cram something that usually occupies an entire Web page into a postage-stamp-sized square.

It's just not helpful getting back a bunch of formatted HTML instead of the data your Ajax UI is expecting.

What flavor of FAIL?

When something goes south on your server, you need to tell your clients a bit more than "oh, shit." Server-side FAIL comes in multiple flavors, just like 'Cokes' here in Texas. ("What kind of Coke do you want?" Yeah, it's silly, but I still giggle anytime I hear someone say 'pop' or 'soda.')

And as more Web clients begin to implement more RESTful interfaces, and implement them in more depth, this question of what kind of failure starts to be even more important, because there's a broader range of errors to consider, and they don't always mean there's been some kind of catastrophic breakage.

Looking at the XHR object status (or however you can get at the HTTP status code), that's a good start, but that doesn't really tell you that much about what exactly screwed up, or why -- so there's no real way to know what to do about it, or what to show to the end-users.

Hammered error-detail doo-doo

Your next problem then is how to hammer the error details into something usable for your app, and worthwhile for the end-user.

If your Ajax app requests some data, and the request succeeds, you get a nice string of parsable JSON data your code can consume -- but if something goes wrong on the server and you get back ... a big page of HTML markup. To quote Miracle Max: "While you're at it, why don't you give me a nice paper cut and pour lemon juice on it?"

In the early Ajax days, one method I used a lot for debugging was popping up an entirely new browser window and dumping the responseText into it. It's a pretty reasonable approach for development (until Firefox's new-window-in-a-tab breaks it, thanks, guys) but it's not really helpful for end-users in a production app.

Another fun 'solution' involves doing string comparisons or regex checks against that massive HTML hairball. Kind of like groping around incoherently for where you dropped your car keys at the party after your fifth beer. Not just sub-optimal -- more like anti-optimal.

Nice work if you can get it

If your client is really integrated with your server, you can tailor your error responses and client-side handling to work together. Nothing like writing everything yourself on both sides to make sure your shit actually works.

Chandler server returns errors in Atom format, so when I worked at OSAF I could count on JavaScript having access to a nice, brief message to display to end-users -- as well as more detailed info (a big ol' Java stack trace) that we could give the user a link to in the error dialog.

Unfortunately we're out of 'zardoz.xml'

What if you're not so lucky? What if you have to support different, disparate clients, and multiple data formats? Shouldn't your app provide error data in those same formats?

If your local Vietnamese restaurant can take your order in English, then it's pretty reasonble to expect they can tell you in English that they're out of Sriracha sauce or whatever. (Like that would ever happen at any self-respecting Vietnamese place, right -- anyhow, you get the point.)

So if I ask for an XML file that turns out not to exist, it'd be pretty kick-ass to get back some XML that tells me so.

Doing it with Merb

I've been doing a lot of work lately with Merb, the Ruby Web-app development framework. It has pretty smart content-negotiation support, which makes it super-easy to provide data in multiple formats. It's also really customizable, so it turns out to be a snap in Merb to do format-specific error handling.

All you have to do is make some changes to the Exceptions controller in /app/controllers/exceptions.rb, and you can return errors to the client in whatever format the original request was in.

Here's the code:

class Exceptions < Application include Merb::ResponderMixin provides :json, :xml</p> <p># handle NotFound exceptions (404) def not<em>found render</em>for_format end</p> <p># handle NotAcceptable exceptions (406) def not<em>acceptable render</em>for_format end</p> <p>def render<em>for</em>format format = content<em>type if format == :html render :format => :html else except = params[:exception] # Status code, e.g., 404 stat = except.class::STATUS # Status text, e.g., "Not Found" words = except.name.split('</em>') words.each do |w| w.capitalize! end stat<em>text = words.join(' ') # Error message, e.g., "Controller 'Zardoz' not found" msg = except.message data = { :status => stat, :status</em>text => stat_text, :message => msg } begin display data # Handle formats the error-handler doesn't know about rescue NotAcceptable render :format => :html end end end end

A few examples

So a client making requests for a non-existent endpoint of "zardoz," in various formats, would see something like these:

/zardoz.json

{"status":404,"message":"Controller 'Zardoz' not found","status_text":"Not Found"}

/zardoz.xml <?xml version="1.0" encoding="UTF-8"?> <hash> <status type="integer">404</status> <message>Controller 'Zardoz' not found</message> <status-text>Not Found</status-text> </hash>

Content-negotiation goodies

First of all, you have to include ResponderMixin so you can get access to Merb's content-negotiation goodies. Then it's pretty simple to pull together the data you want to include in the response, and spit it out in the desired format.

"I'm sorry, I don't speak FOAF"

The rescue for the NotAcceptable exception is kind of important. I guess it's a bit meta, but if your exception controller itself doesn't provide the requested format, you need to be able to fall back to a plain HTML-format error page.

You might wonder why you'd even bother trying to provide format-specific errors for the not_acceptable action (this is where you end up when the client requests a format that's not supported). Well, there may be cases where your app speaks a particular format, but the requested controller/action does not. In that case, you can tell the client -- in the requested format -- that the requested format isn't supported. Fun.

It's a bit like the French dude I met in Thailand who responded to my question with "I'm sorry, I don't speak English" -- in what seemed to me to be perfect English. At least with the server-side error messages you know it's nothing personal -- and it's probably not because you're an ugly American.

Caveats

By default Merb doesn't actually bother parsing requests when they don't match any routes. This makes perfect sense of course -- but it means there's no content_type for those requests, so you can't serve up your format-specific NotFound errors for those.

If you want to have format-specific errors for all 404s, even for those that don't match any known routes, you can enable the default routes in /config/router.rb.

Okay, wrap it up, buddy ...

Format-specific errors are a nice tool in the toolchest -- getting data in the format you asked for, even when something goes wrong, is kind of nice. Dealing with errors in your Ajaxy Web apps is annoying enough without having to trawl through a big pile of markup in an error HTML page.

As you can see from the code above, Merb makes this super-simple to do, but I'm sure there are all kinds of good ways to accomplish it in your language/framework of choice.

About

This is the blog for Matthew Eernisse. I currently work at Yammer as a developer, working mostly with JavaScript. All opinions expressed here are my own, not my employer's.

Related

Previous posts

All previous posts ยป

This blog is a GeddyJS application.