robert hahn

a darn good web developer

June 21, 2007

Setting HTTP Response Codes with Exceptions

A client of mine needed an admin tool for their site: a basic file upload script. Anyone who has ever written such a script knows that there are at least a good half dozen potential failure points when writing such a thing. Experience suggests that you should check at a minimum the following types of things:

For some of these tests, it may actually be pretty important to fail loudly enough for the user to notice. And of course, if we want to build RESTful web applications, it would be good to return the correct HTTP response code in a failure scenario.

While writing the script, using Camping, it suddenly occurred to me that with a bit of tooling, I could easily use Ruby’s exception handling tools to make my life easier, and to make the code maintainable. This technique can easily be applied to Rails applications too.

The setup

The Pickaxe, in its description of the Exception class, describes a #status method that is only available to the SystemExit exception. That looks like a useful idea, but I need the status baked into the base Exception class. Why? Well, if the script is going to die because a directory isn’t writable, or for some other reason I couldn’t anticipate, I need the script to return a 500 error code. Time to monkey patch Exception:

class Exception
    def status
        500
    end
end

Now let’s extend this for some other error scenarios, like a user attempting to upload a file larger than I’d like:

class ForbiddenException < RuntimeError
    def status
        403
    end
end

You should compile a big list of these exceptions into its own file and require it in your app.

In Your Application

Ok, so now you’re in Camping (or Rails), and you’ve got your file processing logic sitting in a class in the model, you have an ‘error’ view (among others) and you’re calling this code from the controller. In the model, you might have a #save method, and one of the things you’re going to do before you save your uploaded file is check the file size:

raise( ForbiddenException.new, "File Too Large!") if @file.size > 11000000

Now for the juicy bit: the controller logic:

class Index
    def Post
        file = FileUpload.new(...)

        page = :index

        begin
            @file = file.save
       rescue Exception => @e # oh, noes! my bad!
            @status = @e.status 
            page = :error
        end

        render page
    end
end

In Camping, the @status variable is reserved for setting the HTTP response code, and is smart enough to change it from 200 to 302 when you’re redirecting; that said, you can set it to other codes as we’re doing here.

And the view (the @e is being used here):

def error
    h1 "An Error has occurred"
    p @e
end

After getting this working, there’s two things I couldn’t believe:

  1. How easy it was to get this going
  2. That no one else thought to mention this

One of the niftiest (yes, I like to use the word nifty) things about this method is that you could catch different kinds of exceptions and customize the output accordingly:

def Post
    file = FileUpload.new(...)

    page = :index

    begin
        @file = file.save
    rescue ForbiddenException => @e # silly user!
        @status = @e.status 
        page = :forbidden_page
    rescue Exception => @e # oh, noes! my bad!
        @status = @e.status 
        page = :generic_error
    end

    render page
end

Conclusion

With only a handful of lines, I managed to create a system that easily handled error scenarios in a maintainable fashion, and passed them off to the correct view as required. Better still, I managed to easily ensure that the client was receiving the correct HTTP response code. Are there ways this could be improved still more? Let me know.

decorative image of trees

Copyright © 2009
Robert Hahn.
All Rights Reserved unless otherwise indicated.