views:

116

answers:

3

Background: We are in the process of writing a registration/payment page, and our philosophy was to code all validation and error checking on the server side first, and then add client side validation as a second step (un-obstructive jQuery).

We wanted to disable double clicks server side, so we wrote some locking, thread-safe code to handle simultaneous posts/race conditions. When we tried to test this, we realized that we could not cause a simultaneous post or race condition to occur.

I thought that (in older browsers anyway) double clicking a submit button worked as follows:

  • User double clicks submit button.
  • Browser sends a post on the first click
  • On the second click, browser cancels/ignores initial post, and initiates a second post (before the first post has returned with a response).
  • Browser waits for second post to return, ignoring initial post response.

I thought that from the server side it looked like this: Server gets two simultaneous post requests, executes and responds to them both (unaware that no one is listening to the first response).

From our testing (FireFox 3.0, IE 8.0) this is what actually happens:

  • User double clicks submit button
  • Browser sends a post for the first click
  • Browser queues up second click, but waits for the response from the first click.
  • Response returns from first click (response is ignored?).
  • Browser sends a post for the second click.

So from a server side: Server receives a single post which it executes and responds to. Then, server receives a second request wich it executes and responds to.

My question is, has this always worked this way (and I'm losing my mind)? Or is this a new feature in modern browsers that prevents simultaneous posts to be sent to the server?

It seems that for server side double click prevention, we don't have to worry about simultaneous posts or race conditions. Only need to worry about queued up posts.

Thanks in advance for any feedback / comments.

Alex

A: 

As long as the request is in its connecting or sending stage, clicking on submit during the first submission cancels the request, starting a new one without the server 'knowing'.

Delan Azabani
This seems to be the case with IE8, but not with Chrome or FireFox.
Alex Czarto
+1  A: 

this may be a stupid response, but why dont you just disable the submit button with javascript on click, so you dont have to worry about multiple clicks. i usually do this on most forms i make and it seems to solve the problem.

you already said you are using javascript so thats not the issue right?

David Morrow
The strategy is to first do all validation/checks on the server side. Once that is working, we will then implement client side checks. This ensures that even if someone disables their JavaScript, or an client side error occurs, that the application will still function properly. I agree, the client side solution is almost trivial, but we want to ensure our app is rock solid on the server side as well.
Alex Czarto
+2  A: 

A similar situation that you need to handle (that the javascript disable-submit-button solution doesn't cover) is the one where the user clicks Submit, the server processes the request, but while it's processing the user's internet connection goes down (perhaps they're on a train going into a tunnel).

When the train comes out of the tunnel, the user doesn't know whether their transaction succeeded or not - they pressed the button, but nothing changed on the page (or perhaps they got a "Try again" page). The natural thing for them to do is click Submit again (or the "Try again" button).

The best way to handle this situation is to include a unique transaction id in the form (in a hidden field). Generate this id randomly, and when a transaction is successfully processed, store it in the database in a list of completed transactions.

Then when you get a POST, check whether this transaction has already been seen - and if it has, skip straight to the status page. Roughly:

BEGIN TRANSACTION

SELECT *
FROM completedTransactions
WHERE userId = ... AND transactionId = ...

<if we got a result - display results of previous transaction>

<otherwise - process the request as normal>

INSERT INTO completedTransactions (userId, transactionId)
VALUES (....)

END TRANSACTION

This has the advantage that (provided you have a database that properly supports transactions - and since you're processing payments I hope you do!) you don't need to do any sort of threading or locking - things "just work".

(though be careful - some database systems can arbitrarily abort your transactions if there is a concurrency problem - but this (rare) situation is easily dealt with using a retry loop...)

As to testing double clicks from browsers: does it make any difference if you press the "stop" button between the two "submit" clicks?

psmears