November 2007 Entries

To 404 or to redirect, or REST should work both ways

When designing a RESTful interface, remember that while you're using nice & friendly URL semantics going in, your server app should apply the same concept to its response as well.

I've seen (and been guilty of) the following before:

if(!PermissionManager.UserCanViewOrder(_user, _order))
{
    Response.Redirect("~/");
}

A similar case is this one taken from one of the MonoRail guys' blog posts:

RoutingModuleEx.Engine.Add(
    PatternRule.Build("bycondition", "listings/<cond:new|old>", typeof(SearchController), "View"));
RoutingModuleEx.Engine.Add(
    PatternRule.Build("alllisting", "listings", typeof(SearchController), "Index"));
RoutingModuleEx.Engine.Add(
    PatternRule.Build("unmatch", "listings/*", typeof(SearchController), "RedirectToIndex"));

HINT: Look at the 3rd and final rule

Both these examples send a confusing message back to the client: they redirect them to some other page on invalid input.  In my mind this is conceptually similar to the following C# code:

try
{
      // Do something
}
catch
{
      return;
}

Yes, by performing redirects in these examples you're effectively performing one of the biggest software sins: swallowing errors

You see, all the client/user has of your system is your URL structure, and if something goes wrong it's up to you to let them know why it went wrong.  In HTTP terms this means sending back the correct HTTP status code that describes the status of their request.

In both of the above examples we're performing redirects, using Response.Redirect in the first example at least.  This sends a 302 status code to the client, and a 302 status code reads to the client as "Moved temporarily" or "Found" (but at a different URL).  This tells the client nothing about why their request couldn't be handled, just bumps them off to another page (probably without even an error message).

Look back at our two examples.  In case one the resource hasn't moved, the client just doesn't have permission to view that URL.  The correct status code to return in this case would be a 401 Unauthorized response.  In case two the resource isn't even there, so the correct status code to return would be our old friend 404 Not Found.

I don't blame you for not doing this before, I mean the REST page on Wikipedia only has this to say on the subject:

HTTP has a uniform interface for accessing resources, which consists of URIs, methods, status codes, headers, and content distinguished by MIME type.

It then goes on to talk about HTTP methods and REST, but status codes aren't mentioned at all (note to self, edit Wikipedia article on REST).

That sounds like a lot of work to implement, doesn't it?  I mean ASP.NET has the nice, friendly and convenient Response.Redirect method, but there's no Response.Unauthorized method, or Response.FileNotFound method.  Ignoring for the moment that those methods are a stupid idea and don't really belong on the Response object, there's actually a very simple way to send these codes to the client:

throw new HttpException(404, "File not found");

If you don't like the idea of throwing an exception then you can do the same thing manually:

Response.StatusCode = 404;
Response.Status = "404 File not Found";
Response.End();
 

 

See, not that hard is it?