Using HTTP POST requests to publish new content to the web (how most blogging and wiki software works) is convenient and useful, but the details are subtle.
While something of a hack to get around the read-mostly nature of the Web – largely the result of server and browser software failing to “realize” the true nature of the Web – using POST to publish can be quite effective, but we must be careful to get our implementation right.
Anyone who has written an authoring tool that uses web text forms for data entry – say for a blog or wiki – will have the following problem: after the user POSTs new content to a URI, what do you display to them? The POST URI is usually a script; generally you’d want to redirect to the new rendering of the resource. But it’s not obvious how to do this.
I see three possibilities:
- Use a “thank you” page that links to the new version of the resource. This is ugly and annoying and warrants no further discussion. The extra clicking involved is intolerable.
- Simply render the new resource. In other words, the “entity body” returned by the POST is the HTML of the new page. This is convenient, but incorrect. Refreshing this page will try to re-POST the content, and this is confusing to the user. Also, the HTTP spec says that the response to a POST should describe the result, not be the new result.
- Return a Redirect to the new resource. This is nice, convenient, and correct – but only if we use the proper redirect code. User agents are not supposed to automatically redirect a POST if the server returns a “302 Found” redirect. The correct code is “303 See other”. The details are subtle.
It would seem that using a Redirect response is the best approach, but when we consult the HTTP spec it contradicts itself. There seem to be – with all things Web! – questions of correctness versus backwards-compatibility.
What’s wrong with the “302 Found” (moved temporarily) redirect? The HTTP spec says:
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.
Since we want the user agent to automatically redirect, this sounds like the wrong solution.
Ok, what about the “303 See other” redirect? The spec:
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.
Wow. Great. Sounds perfect. But there’s more:
Note: Many pre-HTTP/1.1 user agents do not understand the 303 status. When interoperability with such clients is a concern, the 302 status code may be used instead, since most user agents react to a 302 response as described here for 303.
Now it’s telling us to use 302 since older user agents don’t understand 303. Sigh.
After careful reading I figured out that the added redirects (303 and 307) reflect the two possible interpretations of 302, and exist so that the server can make clear which one it means. 303 means “yes please follow this redirect even on POST”. 307 means “redirect on GET, but ask the user on POST whether to redirect”. 302 can mean either of these.
I’m using 303 in my wiki code.
The issue of what to return as a response to a POST request is an issue not only for publishing applications, but for commerce as well. If I return a “thank you” page as a result of a POST to a URI that actually charges a credit card, refreshing that page will try to re-do the POST. This is dangerous. Of course, any decent browser will query the user, but a better solution is to render this mistake impossible. By returning instead a Redirect (303) to a thank-you page, we solve this problem. The user can refresh to their heart’s content with no danger of re-POSTing.
Excerpts from the HTTP spec
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.
Note: RFC 1945 and RFC 2068 specify that the client is not allowed to change the method on the redirected request. However, most existing user agent implementations treat 302 as if it were a 303 response, performing a GET on the Location field-value regardless of the original request method. The status codes 303 and 307 have been added for servers that wish to make unambiguously clear which kind of reaction is expected of the client.
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).
Note: Many pre-HTTP/1.1 user agents do not understand the 303 status. When interoperability with such clients is a concern, the 302 status code may be used instead, since most user agents react to a 302 response as described here for 303.
10.3.8 307 Temporary Redirect
The requested resource resides temporarily under a different URI. Since the redirection MAY 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) , since many pre-HTTP/1.1 user agents do not understand the 307 status. Therefore, the note SHOULD contain the information necessary for a user to repeat the original request on the new URI.
If the 307 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.