H4X0R City

Sorry, kids. My site has been pummeled with spam comments. As I write this, the bastards have already tried to spam my page about comments, at least once (tail -f is your friend). Thirty minutes ago, it was every 5 seconds.

I suppose it was my fault for not getting an Akismet API key. I have one now, but I’m going to keep comments on my site down for a few days until the mess dies down. I really can’t fix much of anything because I’m too busy with other projects.

Which brings me to my other h4×0r encounter for the week. Someone figured out, as long ago as Sept 12, how to compromise our webserver at KCKCC. They used it to set up an eBay-phishing page. Funny thing is, we didn’t even notice until eBay sent us an email notifying of the site.

When we dug into the directory, we found all kinds of hacks, including numerous PHP scripts that grant shell access. The primary problem was that we were running PHP with Safe Mode Off, it seems. We turned it on and now every PHP script has to run as the Apache user. No login for you!

Where do you REST your head?

Sure, it's nearly effortless to add a new XML-based API to your application using the latest stuff in Rails Edge, but what do you do when the Sandman comes? (Sorry about all the puns!)

Lately, I've been very interested in the stuff coming out in Rails Edge and eventually 1.2, emphasizing the CRUD design technique and using REST based services. A lot of this has been spurred on by DHH's keynote at RailsConf that I watched via the web.

The technique is liberating! All your controllers do basically the same things, which are the major CRUD operations (index/show, create, update, destroy) and the typical special representations (edit, new). However, when it comes down to the nitty gritty, there are a few concerns I still have.

Just to test my understanding of the domain and technique, I set out prototyping a new application this morning. It's based on an old PHP app we have that is used to manage the supplemental online databases that KCKCC Library owns or subscribes to. The basic format is very simple: Databases belong to a Category and each have several simple fields, with a few validations.

I started with the CategoriesController to see if I could figure everything out. I created the seven basic actions and added the default respond_to blocks. Here's a simple example:

  def index
    @categories = Category.find(:all)
    respond_to do |format|
      format.html
      format.js
      format.xml { render :xml => @categories.to_xml }
    end
  end

That one was pretty straightforward. What became a brain-twister were the methods that actually modified something. Ideally, in an XML API you don't have to print out a big response when a record was created, deleted or updated. The way you do that is with status codes, and it is something that showed up in DHH's talk. Here's how I would do the create action:

  def create
    @category = Category.create(params[:category])
    respond_to do |format|
      format.html { redirect_to :action => :index }
      format.js
      format.xml do
          headers["Location"] = category_url(@category)
          render :nothing => true, :status => 201
      end
    end
  end

Simple, right? You just send back the location of the new record in the header, and return a 201 status code (Created). This is all well and good until you have some misbehaving clients. What if the new record wasn't created because it failed validation? With our HTML templates, we might do this:

      format.html do
        if @category.valid?  
          redirect_to category_url(@category) 
        else
          render :action => :new
        end
      end

But what should we do with the XML API? We need to refer to the HTTP 1.1 status codes, of course, specifically the 4xx ones, which refer to errors on the client-side. Here are our options (from here):

400 Bad Request
This is primarily for malformed requests. Personally, I think this is inappropriate because it doesn't say that the record submitted failed validations, but that it had bad syntax -- not exactly the same thing.
406 Not Acceptable
At first, I thought this would be it, but actually it refers specifically to the Accept: header and that the resource cannot be returned in an acceptable format.
409 Conflict
Finally, a good candidate! Quoth W3C, "The request could not be completed due to a conflict with the current state of the resource. This code is only allowed in situations where it is expected that the user might be able to resolve the conflict and resubmit the request." They go on to talk about potential uses for versioned resources, etc. Most importantly, however, they mention that the response should give adequate information about how to resolve the conflict.
412 Precondition failed, 417 Expectation failed
These both looked good at first, but of course they refer to specific HTTP headers just like 406 does. Sorry, boys, you're out.

So in the end, I decided 409 was the best for the create action. Now, we need to provide information about "how to resolve the conflict". Why not return the errors that were added to the object?

      format.xml do
        if @category.valid?
          headers["Location"] = category_url(@category)
          render :nothing => true, :status => 201
        else
          render :xml => @category.errors.to_xml, :status => 409
        end
      end

Obviously, there are other errors to deal with, but for the create and update actions, 409 will work just fine.

I'd love to hear your thoughts on how you tackled this issue, or what you would use for status codes in certain situations.