Techblog

Tech Blog

Our latest geek adventures!

11 March Putting HTTP status codes to use with Rails


Note: I have released a Rails plugin that creates an exception class for every HTTP status to your Rails application and adds a default handler for these exceptions, based on the examples in this post.

We are currently implementing an API for Floorplanner, so other sites can use the service Floorplanner offers for their own needs. The Floorplanner website is developed with Rails, so we are trying to be a good Rails citizen and create a API based on REST.

REST embraces the HTTP protocol by coupling URIs to resources and HTTP methods GET, PUT, POST and DELETE) to actions for manipulating them. Today, I tried to embrace the HTTP protocol even more by using its various status codes for Floorplanner’s particular needs.

Most people will know the HTTP 404 status code, which a server returns if you request a page that does not exist. But there are many more interesting codes that can be put to good use as well. The Forbidden status code (403) can for instance be returned if you try to access a another user’s floorplan. Moreover, Floorplanner offers paid subscriptions that include additional functionality. If you try to access this functionality with an account without the necessary privileges, an Upgrade Required status code (426) can be returned. And if you forgot to pay your subscription fee, a Payment Required code (402) can be returned to indicate this.

However, in the end, users wants to see a nice and informative page that tells them what is wrong without cryptic error codes. This is easily possible by leveraging some new features in Rails 2. First of all, we define some custom exceptions that can be raised if some of these conditions occur is wrong:

class PermissionDenied < StandardError
  # no further implementation necessary
end
 
class AccountExpired < StandardError
  # no further implementation necessary
end

Now, we can raise these exceptions when needed in our controllers:

def show
  @plan = Floorplan.find(params[:id])
  raise PermissionDenied unless @plan.user == current_user
  # ...
end
 
def login
  # ...
  raise AccountExpired if current_user.account_expired?
  # ...
end

Normally, raised exceptions will trigger an Internal server error (HTTP status code 500). The new exceptions will be handled manually to return the intended status code and serve a pretty page explaining what is wrong. Exceptions can be caught using the rescue_from method. If we put the code in the Application controller, it will automagically work for all our controllers. DRY.

class ApplicationController < ActionController::Base
 
  rescue_from PermissionDenied { |e| http_status_code(:forbidden, e) }
  rescue_from AccountExpired   { |e| http_status_code(:payment_required, e) }
 
  # Returns a HTTP status code, with a nice error page
  def http_status_code(status, exception)
    # store the exception so its message can be used in the view
    @exception = exception
 
    # Only add the error page to the status code if the reuqest-format was HTML
    respond_to do |format|
      format.html { render :template => "shared/status_#{status.to_s}", :status => status }
      format.any  { head status } # only return the status code
    end
  end
 
end

Now, it is easy to create super fancy pages by editing the views file like app/views/shared/status_forbidden.html.erb.

Note that you cannot and should not use this method for handling internal server errors with status code 500. These should not occur because we have thought of every possible way our code will be used and misused… in theory. In practice, we installed the exception notifier plugin, so we receive a message if one of these occurs and get our asses back to work.

Tags: , , ,

9 Responses to “Putting HTTP status codes to use with Rails”

  1. HTTP status exception handling plugin | Floorplanner Tech Blog Says:

    [...] time ago, I wrote about putting HTTP status code to use for your Rails application. For my reinvigorated project, I wanted to apply the same technique. [...]

  2. fractious Says:

    Good post, just what I was looking for thanks.

    Here’s another that I found that lists all the codes that rails knows about along with their symbol representations. It also lists what status codes certain uncaught exceptions will generate.

    http://blog.restphone.com/2007/12/17/rails-and-http-status-codes

  3. Willem van Bergen Says:

    Hey fractious,
    Some time after after I wrote this piece, I found the list of HTTP statuses in Rails as well. I have written a Rails plugin that creates exception classes for all these statuses, and handles these exceptions like they are handled in this post. Check it out if you’re interested: http://github.com/wvanbergen/http_status_exceptions/tree/master

  4. fractious Says:

    Nice one Willem, will check it out, cheers.

  5. Cyrile Says:

    Hi there,

    thanks for writing this piece of code. However, I wanted to warn you that, after adding in my environment.rb:

    config.gem ‘wvanbergen-http_status_exceptions’, :lib => ‘http_status_exceptions’, :source => ‘http://gems.github.com’

    and restart my server, I began to receive 404 errors on my JS, CSS and image files…

    I am not quite sure what the problem is (I am still looking). I’ll let you know if I find the cause of the problem.

    I use Rails 2.1.1 and Ruby ruby 1.8.6 (2008-08-08 patchlevel 286) [i686-darwin8.11.1] (Entreprise Edition)

  6. Willem van Bergen Says:

    That’s weird. For some reason, looking for these static files in the public directory seems to be bypassed. I assume this occurs in development mode. Can you copy and paste the log output when you request such a file? Do you have a generic route that catches all URLs by any chance?

    If you can resolve this issue, I would appreciate it if you can tell me what was causing it.

  7. Jan-Willem Says:

    There is a little “mistake” in your examplecode. All your rails errors should be raised from StandardError instead of Exception. Also you should rescue StandardError instead of Exception.

    StandardError is the base error class from rails. Which derives from Exception. Exception is ment for exceptions occurring outside the Rails framework.

  8. Willem van Bergen Says:

    You’re right. After looking into it, I found that rescue without a class will only catch StandardErrors, and no Exceptions. I thought it was the other way around.

    This has nothing to do with Rails though: it’s a Ruby issue. The code example works just fine, because I use rescue_from with a specific class. However, deriving from StandardError is better, so I will adjust the sample code and the http_status_exceptions plugin.

  9. skelkingur Says:

    Very nice! Helped us a lot in a small browser game we’re currently developing. We had some serious trouble with those HTTP 500 errors. Thankfully they are now gone :)

    Keep posting!
    skel

Leave a Reply