Saturday, 13 September 2003
Click Submit Only Once
I shudder when I see these words. Everyone I’ve asked has, at least once, gotten two orders of something online (personally, I’ve had the SonyEricsson store ship *three* duplicate orders); “Click Submit Only Once” is intended to stop that. The problem is, it puts me and every other shopper between a rock and a hard place.
That’s because if there’s any problem with my browser, my computer’s network connection, the Internet itself or the server, I don’t know the status of my order. You know, that blank screen and eternal “waiting” cursor. I don’t have any way of seeing whether the order made it, except by backing out and waiting for some kind of confirmation e-mail (and that sometimes takes days). On the other hand, if I go ahead and resubmit, there’s a real possibility that I’ll get two (or three) of everything.
For the technical-minded, this is all because HTTP isn’t a “reliable” protocol; in other words, there are situations where the server and the client have different ideas of what’s happened, or don’t know what’s happened, with a particular request. For the most common HTTP method, GET, this doesn’t matter; it’s “safe,” which means that GETting doesn’t affect state on the server, and “idempotent,” which means that you can repeat a GET if you’re not sure what’s happened, with no ill effect. However, POST - the method used to submit orders, among other things - does affect state, and often it very much matters how many times you do it.
That said, this is a common problem that’s pretty easy to fix; it doesn’t require any fancy server footwork, browser plug-ins or HTTP extensions. You can avoid it with a very simple design pattern in your Web application. Roughly, it looks like this:
1. The browser requests the Web page that contains the final order form (with the ‘Submit’ button in it),
2. The server sends back a page whose form has a unique ‘action’ link; e.g., “http://shop.example.com/orders/1234”,
3. The user submits the form, which is POSTed to the ‘action’ link, in turn submitting the order.
4. The server sends back a page that says that the order has been successfully submitted.
Here’s the good part: if the server receives more than one POST to any particular ‘action’ link, it should generate a message saying “This order has already been submitted,” perhaps along with a summary of the order and its status. This way, if the user does, for whatever reason, click “submit” twice, it won’t cause a duplicate order.
To make it even better, the server can put a notice on the ‘submit’ page: “If there is a problem with your order, click in the ‘location’ bar and press return.” In most browsers, this will cause the request to be resubmitted to the server, but as a GET, not a POST. The server can then show the status of the order if it was successful, and an error page (perhaps with a link back to the submit page) if it wasn’t. As a bonus, this page can act as the order status page.
There are a few things that the server needs to do to make sure this technique works. First of all, the page containing the ‘submit’ link can’t be cacheable; otherwise, it’ll be difficult to make a second order (the server will think you’re resubmitting the first). This can be accomplished by using POST to get the ‘submit’ page itself, or by using cacheability controls on the response. Secondly, the link to the order page really has to be unique for each logical order; the server might ensure this by using UUIDs, sequential numbers, or orders for each customer (e.g. “/email@example.com/43”. Finally, it needs to properly respond to GET and POST on the order URI.
Note that this isn’t “real” reliability, by some definitions; it doesn’t take care of message ordering, for example. However, it is good enough for making sure your customers don’t get two (or three!) of everything. I’d really like to see this pattern or something like it baked into the software toolkits that people use, so we can get rid of “Click Submit Only Once.”
I’ll leave “Don’t Use Your Browser’s Back Button” for another day; that one *really* bugs me…
UPDATE: See POE, a more formal proposal in this space.