Why you should learn to love the 303 HTTP status code

By Lucy April 19th, 2008

Typically, a well designed web application will respond to a POST request with a redirection to a suitable URL (e.g. one that shows the contents of their e-mail inbox, their Facebook homepage etc.). The main advantage of this approach compared to sending whatever resourse the user thinks they are requesting in the body of the response to the POST request itself is that the latter technique breaks things like the ‘back’ and ‘reload’ buttons (’This request contains POST data…’), as well as making the resulting page unbookmarkable. This happens because, if the HTTP spec is to be taken literally, POST requests are not guaranteed to be ‘idempotent’ (i.e. the order in which they are sent or resent matters because they may perform some action which alters the server’s internal state).

As a sort of historical accident, the standard way of accomplishing this redirection seems to have become responding to the original request with a 302 (Found) status code, and providing the URL of what I call the ‘result page’ in the ‘Location’ header. An informal consensus seems to have developed around the convention that the new URL should automatically be retrieved using a GET request, which is exactly what we wanted, but contravenes the HTTP/1.1 (RFC2616) specification:

 10.3.3 302 Found

The requested resource resides temporarily under a different URI. Since the redirection might be altered on occasion, the client SHOULD continue to use the Request-URI for future requests. This response is only cacheable if indicated by a Cache-Control or Expires header field.

The temporary URI SHOULD be given by the Location field in the response. Unless the request method was HEAD, the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s).

If the 302 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.

RFC2616 helpfully points out that older versions of the specification go even further and explicitly prohibit changing the request method at all. Either way, the more correct status code is 303 (See Other):

10.3.4 303 See Other

The response to the request can be found under a different URI and SHOULD be retrieved using a GET method on that resource. This method exists primarily to allow the output of a POST-activated script to redirect the user agent to a selected resource. The new URI is not a substitute reference for the originally requested resource. The 303 response MUST NOT be cached, but the response to the second (redirected) request might be cacheable.

The different URI SHOULD be given by the Location field in the response. Unless the request method was HEAD, the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s).

Now, 99.9% of the time it doesn’t make any practical difference which response code you use, and, as even the HTTP spec itself conceeds, using 302 redirects can improve compatibility with older user agents which may not understand the newer 303 response code. In practice, the vast majority of web applications use 302 redirects, but this may be partly due to their use of certain web development languages or frameworks that don’t easily support anything else - e.g. the sendRedirect() method of J2EE’s HttpServletResponse interface uses a 302, and, unless you explicitly tell it otherwise, calling header(’Location: www.mynewlocation.com’) in PHP automagically rewrites the response code with, you guessed it, a 302. There is also the fact that most people who call themselves web developers have never actually looked at the HTTP specification.

The reason I’m so annoyed about this at the moment is that I recently found myself trapped in that 0.1% of situations where the distinction actually matters. I was working on a mobile web application being developed in Java, which made heavy use of the ‘response.sendRedirect(…)’ method. When running the app on a Nokia 6300, I was surprised to find that the device actually popped up a dialog along the lines of ‘Data will be sent to another server…’. That wasn’t actually the case - the data was being sent to the same server - but testing confirmed that the handset really did resend all the POST data to the new URL. Switching to 303 redirects solved the problem on the Nokia, but it turned out that about 20% of all the handsets we tested simply didn’t understand the new response code (despite the fact that they all identified themselves as HTTP/1.1 compliant).  The result is that we are stuck using the ugly kludge that is user agent sniffing in order to work around a problem that shouldn’t exist in the first place.

This entry was posted on Saturday, April 19th, 2008 at 9:49 pm and is filed under Development. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

Leave a Reply