Matt RaibleMatt Raible is a Java Champion and Developer Advocate at Okta. developer.okta.com

The Angular Mini-Book The Angular Mini-Book is a guide to getting started with Angular. You'll learn how to develop a bare-bones application, test it, and deploy it. Then you'll move on to adding Bootstrap, Angular Material, continuous integration, and authentication.

Spring Boot is a popular framework for building REST APIs. You'll learn how to integrate Angular with Spring Boot and use security best practices like HTTPS and a content security policy.

For book updates, follow @angular_book on Twitter.

The JHipster Mini-Book The JHipster Mini-Book is a guide to getting started with hip technologies today: Angular, Bootstrap, and Spring Boot. All of these frameworks are wrapped up in an easy-to-use project called JHipster.

This book shows you how to build an app with JHipster, and guides you through the plethora of tools, techniques and options you can use. Furthermore, it explains the UI and API building blocks so you understand the underpinnings of your great application.

For book updates, follow @jhipster-book on Twitter.

10+ YEARS


Over 10 years ago, I wrote my first blog post. Since then, I've authored books, had kids, traveled the world, found Trish and blogged about it all.

How does your MVC framework handle duplicate posts?

One of the problems that you'll often see in webapps is that when a record is added - hitting refresh on the browser will cause a 2nd record to be added. This is because the "Save" action usually does a forward, rather than a redirect, and the full post is re-created. I'm curious to know how other MVC Frameworks handle this issue, particularly Spring, WebWork, Tapestry and JSF. In Struts, it's pretty simple to solve. When the form is first displayed, you'll likely go through an Action. In AppFuse, this would be something like UserAction.edit(). In the edit method, you add a saveToken() call to put a token into the session:

// edit method - mark start of operation
saveToken(request);

Then in the save() method, you can check for a duplicate post using the isTokenValid() method:

// save method - check for valid token
if (!isTokenValid(request)) {
    // duplicate submit, continue to success mapping
    return mapping.findForward("success");
else {
    resetToken(request);
}

How does your Java MVC framework handle this? Do you have to programmatically handle it like Struts - or is it built-in to the framework to handle it auto-magically?

Posted in Java at Apr 20 2004, 11:36:29 PM MDT 22 Comments
Comments:

I've always gotten by but just following the standards. Make any request that's going to make changes into a POST, and rely on the browser to know that a post request shouldn't be resubmitted unless the users asks it to be.

Posted by Daniel Sheppard on April 20, 2004 at 11:45 PM MDT #

is it useing some workflow control to limit the 'refresh' like struts workflow....

Posted by jimmy on April 21, 2004 at 12:03 AM MDT #

I know webwork has a token interceptor that prevents this from happening, though I always do a redirect after a save or add to avoid it completely. For info on how webwork does this without a redirect, check out docs on the token interceptor.

Posted by Drew McAuliffe on April 21, 2004 at 01:30 AM MDT #

In WebWork2, you have two options for dealing with duplicate submissions. One option is to catch the invalid token and redirect the user to another page (like your example) by doing the following: 1. include then token tag in your HTML form <ww:token name="'myToken'"/> 2. drop the TokenInterceptor onto your action (or the default stack). it's already predefined as 'token'. Here's an example ... the line with "token" is all that would need to be added. <action name="submitPayment" class="example.PaymentAction"> <result name="success">/payment-success.jsp</result> <result name="invalid.token">/payment-input.jsp</result> <interceptor-ref name="defaultStack"/> <interceptor-ref name="token"/> </action> 3. add a named result called, invalid.token, that will deal with duplicate submission. That's it! The alternate approach is to "redo" the action using returns cached from the previous execution. The only difference in implementation is to replace the "token" interceptor with the "token-session" interceptor and remove the invalid.token named result. With the second approach, if the user hits reload on the payment page: 1. the browser resubmits the app 2. the interceptor catches the request and loads the results of the previously cached action rather than executing a duplicate action. 3. the user sees the same page again :)

Posted by Matt Ho on April 21, 2004 at 02:18 AM MDT #

(whoops ... please ignore the previous post. didn't realize it was htmlized)

In WebWork2, you have two options for dealing with duplicate submissions. One option is to catch the invalid token and redirect the user to another page (like your example) by doing the following:

1. include then token tag in your HTML form

<ww:token name="'myToken'"/>

2. drop the TokenInterceptor onto your action (or the default stack). it's already predefined as 'token'. Here's an example ... the line with "token" is all that would need to be added.

<action name="submitPayment" class="example.PaymentAction">
    <result name="success">/payment-success.jsp</result>
    <result name="invalid.token">/payment-input.jsp</result>
    <interceptor-ref name="defaultStack"/>
    <interceptor-ref name="token"/>
</action>

3. add a named result called, invalid.token, that will deal with duplicate submission.

That's it!

The alternate approach is to "redo" the action using returns cached from the previous execution. The only difference in implementation is to replace the "token" interceptor with the "token-session" interceptor and remove the invalid.token named result.

With the second approach, if the user hits reload on the payment page:

  1. the browser resubmits the app
  2. the interceptor catches the request and loads the results of the previously cached action rather than executing a duplicate action.
  3. the user sees the same page again :)

Posted by Matt Ho on April 21, 2004 at 02:23 AM MDT #

Whilst I agree with Daniel's suggestion, I've found that there are cases where it breaks - for example, if you use an image button to submit a form then there's a small but significant number of users who think that the right thing to do is to double-click it ( or whose mouse co-ordination is sufficiently bad that they generate a double click) - which will generate 2 POSTS, one for each click. Also, if your request takes a long time to process, and you don't provide some kind of a 'hold screen' some users will keep clicking on the submit button just in case 'it didn't hear me the first time' :-) If your users aren't that stupid, then you're luckier than I am.

Posted by Chris May on April 21, 2004 at 02:26 AM MDT #

Some people think that they need to double click in a web application. It is often helpful to use a little javascript to hide or disable the submit button after a click. That way, they are not still clicking when they are waiting for the web application to respond.

Posted by Matt P. on April 21, 2004 at 06:53 AM MDT #

In JSF, you can add a <redirect/> element to any navigation rule defined in jsf-config.xml to force JSF to perform a redirect instead of a forward after having completed an action. However, in Sun's Reference Implementation URL rewriting (which is being used automatically to transfer session information if cookies are turned off) doesn't work with redirects. This results in losing the user session when redirecting if cookies are turned off in the user's browser. I haven't found a way around that yet.

Posted by Wolfgang Wopperer on April 21, 2004 at 09:22 AM MDT #

I agree that using a "redirect" is the best approach. However, Struts has a convenience method to save success messages - saveMessage(request, messages) - which puts the messages in the request. If you do a redirect, you have to stuff them in the session and (using a filter) grab them out of the session and put them back in the request. Kind of a pain and an often asked question on the struts-user mailing list.

How does you framework handle success messages? Do you have convenience methods to set them and taglibs to grab them?

Posted by Matt Raible on April 21, 2004 at 10:03 AM MDT #

What is the element in JSF to add to the navigation rules?

Posted by rich! on April 21, 2004 at 10:07 AM MDT #

Oops - same mistake as in Matt's first post!

The element to added to a navigation case is
<redirect/>
.

Posted by Wolfgang Wopperer on April 21, 2004 at 11:01 AM MDT #

Damn.. Matt beat me to it!!

Posted by Jason Carreira on April 21, 2004 at 11:44 AM MDT #

Anyway, I think this is another case where the framework taking care of things like this is a huge win... Even for stuff we don't take care of out of the box, Interceptors allow you to build this stuff outside of your Actions. This token code HAS to make your Actions more difficult to test...

Posted by Jason Carreira on April 21, 2004 at 11:47 AM MDT #

Nice description of a common problem. As a piece of data regarding the frequency of users double-submitting....

In our home built discussion form we have duplicate submissions to the forum about 10% of the time. I suspect this is due to user's refreshing a page after a submit. (although it might be from a double-click on the button). I've been meaning to fix this; I'll probably use a method similar to what you mentioned.

Posted by Will on April 21, 2004 at 01:25 PM MDT #

Using Cocoon's continuation-based flowscript <http://cocoon.apache.org/2.1/userdocs/flow/continuations.html>, avoiding duplicate posts is easily accomplished by invalidating the current continuation: var k = cocoon.sendPageAndWait(formUri); k.invalidate(); // If you invoke "k", again you get a suitable exception

Posted by Ugo Cei on April 21, 2004 at 02:01 PM MDT #

I use the token interceptor and redirect after submitting, works great in ww2

Posted by Unknown on April 21, 2004 at 03:44 PM MDT #

As I understand it the struts token method falls apart when you allow encourage you users to use multiple windows/tabs which is relatively frequent when writing a webmail and users want to compose more than one message at a time.

Posted by Sandy McArthur on April 21, 2004 at 04:16 PM MDT #

Well, they all do, if you keep using the same token name... In WW2 you could, I suppose, dynamically generate a token name for each form, then they would always be available until they had been posted. If people are interested in that, I could make the ww:token tag have an "autogenerate" attribute to tell it to create a unique token name...

Posted by Jason Carreira on April 21, 2004 at 04:20 PM MDT #

I should probably clarify the bit about just 'relying on POST' - I couple this with locking on resources at a lower level for important records. The only thing that I miss in my technique is that if somebody clicks a link, and that is then clicked on again before the original request finishes, I'd love a way for that second request to 'jump into' the first request and serve the response to the original request. I use a home-made framework, and I can see where to put the hooks to make such a thing happen, but the project isn't being actively developed at the moment. Do any of the frameworks allow such a jump-in mechanism?

Posted by Daniel Sheppard on April 21, 2004 at 06:51 PM MDT #

Yes, WebWork 2.0+ gives you this with the "token-session" interceptor, because it synchronizes looking for the token on the user session.... therefore the first request will complete, then the second one will get the lock and see that it's a duplicate, so it will just re-render the same page as the first request would have drawn.

Posted by Jason Carreira on April 21, 2004 at 07:55 PM MDT #

The "token-session" interceptor sounds interesting, however what happens when the token on the session expires? The user would be free to resubmit.

Posted by Rajesh Patel on September 01, 2004 at 09:03 AM MDT #

While the JSF framework does not provide this, there is a clean solution that does not require any tokens or redirecting. However, the solution only works if there is more than one screen in the submission process. Say we have a submission process (called YYY) where a user must fill out two forms - personal data (1) and other data (2). In JSF, use a YYYHandler with session scope that is tied to action buttons the form 1 and form 2. After form 1 passes validation, calls YYYHandler.form1(), YYYHandler marks form1 information as complete, and JSF navigates to screen 2. After form 2 passes validation, calls YYYHandler.form2(), and YYYHandler completes with no errors (such as a database error) then YYYHandler simply removes itself (and form1 and form2 beans) from the session. Now if the user hits 'Reload' on their browser, JSF will instantiate a new YYYHandler, a new form1 bean, and a new form2 bean. At this point YYYHandler knows that it doesn't have all the forms inputed, and can return a navigation case "No Form 1", at which point JSF will navigate to screen 1 and the form will be blank.

Posted by Hans Sponberg on October 25, 2004 at 03:55 PM MDT #

Post a Comment:
Comments are closed for this entry.