views:

973

answers:

4

Say, you are submitting a form, which affects your database (adding records/ deleting them/ updating them) and this is how your request looks like:

POST /application/action=update

Now, say, you are done with your update, so you would like to take the user to the home page.

Response.sendRedirect /application/action=home

This works wonderfully well. User is sent a redirect after POST, so even if the user tries to refresh the page by hitting F5, you are good. However, this will not work if you did this:

requestDispatcher.forward(/application/action=home)

Given that there is a scenario where you have to display different kinds of error / success messages after you are done with your update, you are most likely doing a forward after POST. In such a scenario, how do you avoid update actions from happening twice?

I find it rather amusing that many secure sites (banks) / payment gateways tend to inform the user by placing text on screen, such as "Please don't press back / refresh buttons".

Is there no better way to handling this? Other than requesting the user not to press these buttons? When I last checked, there was something called the 'Vertical Response Cache'. A Filter that would identify uniqueness of your request in a session and tries to send a cached response if the request is duplicate. Are there any simpler ways to solving this classic problem?

Here is a link to the vertical response cache solution I was talking about: http://www.fingo.info/en/articles/_1.html. I am, However, not sure as to how well this really works.

+1  A: 

One thought that I've had is to embed a unique ID (probably a random string) as a hidden form field in the form that is being POST-submitted. The ID string can be put in the database as a "transaction ID". Now, when you go to update the database, first check whether there's an existing record with the submitted transaction ID, and if so, assume it's a duplicate and don't change the database.

Of course, as I said, this is just a thought. I don't know what methods are actually used in practice. (I suspect that a lot of less-critical sites just ignore the problem and hope their users will be smart... a losing proposition if I ever saw one ;-)

EDIT: as pointed out in the comments, storing transaction IDs in the database might take up a lot of space, but if that's an issue, you could keep an in-memory cache of all transaction IDs processed in the last 5 minutes/1 hour/1 day/whatever. That should work unless you're up against a determined hacker...

David Zaslavsky
I would think that it would be too costly an operation to hit the database every time for transactionIDs. For an application with millions of users, it would be millions of 'just for the sake of it' records - think about clean up and maintenance of this one table.
Jay
@Jay, nah -- you just need one extra UNIQUE field transactionID on whatever table or view is involved, and REPLACE OR IGNORE -- ta-da!-)
Alex Martelli
@Alex How about a non-database approach to this problem? What about cookies?
Jay
Cookies can protect against accidental double-clicks, but if someone wants to circumvent a cookie-based scheme it is, generally speaking, trivially easy to do so.
David Zaslavsky
And anyway, there's always at least a bit of risk involved with assuming any particular behavior on the client's part (like honoring cookie headers).
David Zaslavsky
+4  A: 

Yes, I believe that you should redirect after a POST, with the exception of API requests. Without doing this not only do you have to worry about getting duplicate POSTs when the user uses the back button, but the browser will also give the user annoying dialogs when they try to use the back button.

Response.sendRedirect works in practice, but tecnically speaking this is sending the wrong HTTP response code for this purpose. sendRedirect sends a 302, but the correct code to use to transform a POST into a GET is 303. (most browsers will treat a 302 just like a 303 if they get it in response to a POST, however)

In general you want the redirect to send the user to whatever view will display the effect of their change. For example, if they edit a widget, they should be redirected to the view of that widget. If they delete a widget, they should be redirected to the view that the widget would have appeared in when it existed (perhaps the widget list).

Sometimes it's nice to have a status message to further drive home the fact that an action occurred. A simple way to do this is to have a common parameter to your views that, when set, will display an action completed message. eg:

/widget?id=12345&msg=Widget+modified.

Here the "msg" parameter contains the message "Widget modified". The one downside to this approach is that it may be possible for malicious sites to give your users confusing/misleading messages. eg:

/account?msg=Foo+Corp.+hates+you.

If you're really worried about this you could include an expiring signature for the message as an additional parameter. If the signature is invalid or has expired, simply don't display the message.

Laurence Gonsalves
@Laurence What would you do if had to display multiple messages? Three types of them: Successes, Failures and Warnings? It surely has to be passed on in request scope to your action that renders your Widget View.
Jay
Sure. That's just an extension to the msg parameter that I described.
Laurence Gonsalves
Jay
These types of messages are usually used to indicate the action performed by the POST. When would you need to display multiple messages at the same time? BTW: if you're concerned about the size of the message you can typically get away with a passing message ID numbers in the URL instead of the actual messages themselves. If you're internationalizing you may already have ID numbers handy.
Laurence Gonsalves
+1  A: 

I find it rather amusing that many secure sites (banks) / payment gateways tend to inform the user by placing text on screen, such as "Please don't press back / refresh buttons".

some people find its better to "disable all Back, Refresh event on this critical pages"; I'm not sure if this is good or not.

But your addressed solution "vertical response cache" sounds nice

Ali Abdel-Aziz
@Ali disabling back / refresh events? How do you disable your browser's back button / F5?? I have heard of programmers attempting such things and failing miserably.
Jay
Actually I didn't make it by hand, but I found friends speaking about removing the refresh and back buttons from the browser in the critical pages.And regarding the F5 disabling I found this code snippet but as to disable the F5 key, write this script in the head section:<script language=javascript>document.onkeydown = function(){if(window.event }if(window.event // Must return false or the browser will refresh anyway}}</script>
Ali Abdel-Aziz
the above code posted at http://p2p.wrox.com/javascript-how/35830-disable-browser-f5-key.htmlbut I guess cases like firefox short cuts should be handled Ctrl + Rbut as I told you previously I found "vertical response cache" sounds nice what is your problem with this suggested solution.
Ali Abdel-Aziz
@Ali I see your point. However, relying too much on javascript to do it for you and leaving control on the user side is not a good practice. I was wondering if there is a non-database way of doing it on the server side.
Jay
The Vertical Reponse Cache has limitations. I am not really sure about this coz I read this a while ago, but it can only do one at a time or just one of these two : handle resubmitting of forms when user presses F5 OR handle resubmitting of form when user presses back and submits again. I can't recall the limitation right now, but it was on these lines...
Jay
A: 

Its a little non-obvious but: * create a keyed-object in the user session. * the value is a Request + java Future for the result * return immediately with a client-side redirect. * while the client-side redirect is being handled, have a worker thread work on producing the answer.

So by the time the client browser completes the redirect, getting the new page's images, etc... the results are waiting for the user.

The alternative is to make the user painfully aware of how long the database is taking.

Pat