TSS Article: Redirect After Post

Discussions

News: TSS Article: Redirect After Post

  1. TSS Article: Redirect After Post (68 messages)

    Peculiarities of the POST method combined with idiosyncrasies of different browsers often lead to unpleasant user experience and may produce incorrect state of the server application. In this article, Michael Jouravlev shows you how to design a well-behaved web application using redirection.

    Read Redirect After Post

    Threaded Messages (68)

  2. I think there a simpler way[ Go to top ]

    We need not to split one action into Post/Get respectively. The simplest way is to check to status of the model every time when the data is submitted(POSTED). Since user-session is stored and can be retrieved even when same data is submitted repeatly, when can send the same view back to the browser.

    With MVC model I think it's simple.
  3. Not so simple[ Go to top ]

    The problem is *not* simple, and many web apps ignore this problem completely. I have found this issue in a number of production systems that used MVC design. It is not really a question of "GET" vs. "POST". It is a question of "operations that change system state" vs. "operations that display system state".

    These two don't necessarily map to controller and views. For examples, some controller actions are responsible for pre-loading model data on behalf of the view, making them an "operation that displays system state". Problems arise when a single controller does both.

    Suppose you have an AddProductsToCart action, which adds N items of product id X to a cart:

    AddProductsToCart?quantity=N&productId=X

    The obvious way to implement this action in MVC is to:

    a) Load the cart data (or pull it out of the session cache).
    b) Add the products to the cart.
    c) Pass the already-loaded cart data via a forward operation to a ShowCart view to display the results.

    Implemented this way, the URL in the browser's address bar when the ShowCart page is displayed will still be the AddProductsToCart action itself, because the forward operation is hidden from the browser. A page refresh or back button could easily re-invoke this URL and re-execute the action. The server has no simple way to distinguish these "accidental" executions from perfectly legitimate requests by users that want to add more products to their cart.

    Using a client-side redirect to go from the AddProductsToCart action to the ShowCart view reduces the chance of this happening, because it effectively erases this action from the browser's history. The URL in the browser's address bar will be the ShowCart view, so that back or refresh operations will only re-execute the view and not the action.

    For example, it is possible (and a good idea) to configure Struts to perform a client-side redirect to a view after an update action, as opposed to its default forward operation. It makes things trickier, because now both the action and the view have to load the cart, but a decent caching strategy can address this problem.

    Unfortunately, this POST-REDIRECT-GET technique is still not perfect. Suppose you have a neurotic user that clicks the form's submit button 15 times in a row, using the elevator philosophy: "If I click the button more, it will go faster". The server's REDIRECT messages will be ignored by the browser until the user quits submitting the form, so it is still possible to get accidentally resubmissions even using the technique discussed in the article.

    The only effective solution I have found to this problem is to assign some kind of transaction ID with each request, and to ignore any additional requests for same transaction. Needless to say, this is a real pain to implement, and it is best to use a framework that already tackles these kinds of problems (like Tapestry).
  4. Not so simple[ Go to top ]

    It is not really a question of "GET" vs. "POST". It is a question of "operations that change system state" vs. "operations that display system state".These two don't necessarily map to controller and views.
    I agree that this is not strictly GET vs POST question, because system often can be changed using GET method as well. On the other hand I think, that "operations that change system state" should be strictly maintained by a controller. It should be not possible to combine both operations in one request.
    Unfortunately, this POST-REDIRECT-GET technique is still not perfect. Suppose you have a neurotic user that clicks the form's submit button 15 times in a row, using the elevator philosophy: "If I click the button more, it will go faster". The server's REDIRECT messages will be ignored by the browser until the user quits submitting the form, so it is still possible to get accidentally resubmissions even using the technique discussed in the article.The only effective solution I have found to this problem is to assign some kind of transaction ID with each request, and to ignore any additional requests for same transaction.
    Right, as I pointed out in the article, generally Model should not allow concurrent updates by the same client. Which means that once the server received a [usually POST] request from a client, the Model (well, the part of the model corresponding to that client) must be locked and should not accept other requests until this request is processed (or until some other checkpoint, which may occur before this request is fully processed). As I described in the shopping cart example, when the purchasing subsystem receives the request, the Model is locked and the shopping cart is immediately invalidated. The consecutive click on submit button or click on Refresh browser button would bring an error message telling that the shopping cart does not exist anymore.
    Needless to say, this is a real pain to implement, and it is best to use a framework that already tackles these kinds of problems (like Tapestry).
    I do not know much about Tapestry. Actually I do not know much about anything else but Struts. Couple of days ago I got to know that Spring supports what I call a PRG pattern using RedirectVeiw class. Everything works almost exactly as I wanted, so if you need an out-of-the-box solution for proper after-POST view handling, take a look at Spring. It is easier to use than Struts, where you have to write our own code to handle temporary objects. You can try PetClinic example and see how the form is updated with most recent data when you click Back browser button. You can also refresh the resulting View without side effects. I think that this feature should be included in their advertising campaign when they decide to have one.
  5. built-in functions in Struts[ Go to top ]

    Right, as I pointed out in the article, generally Model should not allow concurrent updates by the same client. Which means that once the server received a [usually POST] request from a client, the Model (well, the part of the model corresponding to that client) must be locked and should not accept other requests until this request is processed (or until some other checkpoint, which may occur before this request is fully processed). As I described in the shopping cart example, when the purchasing subsystem receives the request, the Model is locked and the shopping cart is immediately invalidated. The consecutive click on submit button or click on Refresh browser button would bring an error message telling that the shopping cart does not exist anymore.
    From http://husted.com
    There are methods built into the Struts action to generate one-use tokens. A token is placed in the session when a form is populated and also into the HTML form as a hidden property. When the form is returned, the token is validated. If validation fails, then the form has already been submitted, and the user can be apprised.

        * saveToken(request)
        * on the return trip,
              o isTokenValid(request)
              o resetToken(request
  6. The built-in Struts mechanism works fine if the user only opens one browser window. Suppose that a user from an online bookstore is filling out an HTML form to post a book review. But before he submits his review, he opens a new browser window and starts searching for a book about better writing skills. Once he has found what he was looking for, he orders the book. After this he goes back to the window with the partial book review to complete the review. If this user now submits the form the isTokenValid(request) method will return false.

    I think one token per session is too crude. A good mechanism should have one token per operation that change system state. Or even better, it should store a list of tokens that are already processed. If the server recieves a new POST with a token that is in the list, it must come from a form that has already been submitted.
  7. I think one token per session is too crude. A good mechanism should have one token per operation that change system state. Or even better, it should store a list of tokens that are already processed. If the server recieves a new POST with a token that is in the list, it must come from a form that has already been submitted.
    We've dealt with this exact issue on our current project. We ended up overriding the Struts token handler to have a single token *per page* (specially per form bean). Furthermore we use a synchronized(session) block in our Action base class to prevent a single session from modifying the model in two concurrent requests.
  8. What I have never understood about the Struts token mechanism is "How does identifying a duplicate request solve your problem?" Example: suppose submitting page A does some state modifying operation, then returns to page B with the results of the operation. ActionA, which is called when page A is submitted, does a token check. On the first submit, the database transaction commences. However, if page A is submitted again, the token check fails. Unfortunately, the original request has now been superceded by the second request. The DB transacation is still going on and may commit, but there is no way for the second request to return the results of that transaction because a) it has no handle to it, and b) even if it did, it is possible that the transaction has not completed so the second request can't query for the results and return them. So, you have detected the resubmit, but the best you can do is return an error to the user, even though the transaction may have succeeded.

    Seems to me the only way to deal with this is prevention, using JavaScript, rather than detection of multi-submit.
  9. What I have never understood about the Struts token mechanism is "How does identifying a duplicate request solve your problem?" Example: suppose submitting page A does some state modifying operation, then returns to page B with the results of the operation. ActionA, which is called when page A is submitted, does a token check. On the first submit, the database transaction commences. However, if page A is submitted again, the token check fails. Unfortunately, the original request has now been superceded by the second request. The DB transacation is still going on and may commit, but there is no way for the second request to return the results of that transaction because a) it has no handle to it, and b) even if it did, it is possible that the transaction has not completed so the second request can't query for the results and return them. So, you have detected the resubmit, but the best you can do is return an error to the user, even though the transaction may have succeeded.Seems to me the only way to deal with this is prevention, using JavaScript, rather than detection of multi-submit.
    First of all, well-designed application should not allow resubmits, period. Resubmits can be only possible if the page with HTTP FORM is cached by a browser, which does not care about cache control parameters in the response.
    Second, this is so not my idea of database transactions. I prefer to use very short database transactions, which do not span several pages, requests, etc. For me, there are two basic choices:
    * the submitted data can be persisted without any additional information. Then right after the page is submitted, the data is immediately stored in the database in one short transaction. If a user tries to go back in the browser, he would see updated data _from_ the database or would just see an error that transient data is gone, it depends on the use case.
    * the submitted data needs additional information, say in a multipage wizard. In this case all needed data is accumulated in the memory (or in the beans) during the wizard course, and when the wizard finishes, data is stored in one short transaction. If in an unlikely case (because of the cached browser) the same data is resubmitted, it is easy to change memory data or to reject resubmitted data. Database does not even know about it.
  10. First of all, well-designed application should not allow resubmits, period.
    Easier said than done

    > Resubmits can be only possible if the page with HTTP FORM is cached by a browser, which does not care about cache control parameters in the response.

    That's simply not true. A user can mistakenly double click on a button, submitting the same form twice before the browser has a chance to replace the existing form with the page coming back in response.
  11. > Resubmits can be only possible if the page with HTTP FORM is cached by a browser, which does not care about cache control parameters in the response.
    That's simply not true. A user can mistakenly double click on a button, submitting the same form twice before the browser has a chance to replace the existing form with the page coming back in response.
    Yes, my bad. You are right on that.
  12. The built-in Struts mechanism works fine if the user only opens one browser window. Suppose that a user from an online bookstore is filling out an HTML form to post a book review. But before he submits his review, he opens a new browser window and starts searching for a book about better writing skills. Once he has found what he was looking for, he orders the book. After this he goes back to the window with the partial book review to complete the review. If this user now submits the form the isTokenValid(request) method will return false.I think one token per session is too crude. A good mechanism should have one token per operation that change system state. Or even better, it should store a list of tokens that are already processed. If the server recieves a new POST with a token that is in the list, it must come from a form that has already been submitted.
    A good mechanism should not use tokens at all. Tokens are a web layer solution. Web layer does not not about your Model. Web layer and all these pages is just the means to _obtain and return data_. I/O can be organized differently, it may organized in pages, in notebooks, in separate screens, or in web service requests or something else. Again, web layer is just an inteface between a client and a Model. Web layer does not know and should not not about data. Its job is to transfer data back and forth, period.

    Tokens is _not a good solution_ at all. Tokens are tolerable if the Model cannot properly validate the data. Well, in that case tokens are better than nothing. But using Model itself to validate data is much much better and cleaner and more generic. You should not care about resubmitting a request, you should care about applying certain data to the Model, about applying modifications to your objects.

    I will present my solution in the next article. In brief, an object used by the application for viewing or editing, is temporarily stored in the session in between of requests, wrapped into UI object. The UI object is responsible for representing the business object to a client. UI object is identified by business object ID. It is possible to have many UI objects, what's more, different UI objects may have versions of the same business object.

    When UI object is submitted, the server does not care about tokens. What it cares of is data. It verifies that object with particular ID and with certain state (or with certain timestamp or with certain transaction ID) can be applied to the Model. Depending on the operation, object state changes, or transaction ID is disposed, so when the same object is submitted next time, the state or transactio ID or timestamp would not match. This is it, pure and simple, and no tokens at all.
  13. When UI object is submitted, the server does not care about tokens. What it cares of is data. It verifies that object with particular ID and with certain state (or with certain timestamp or with certain transaction ID) can be applied to the Model. Depending on the operation, object state changes, or transaction ID is disposed, so when the same object is submitted next time, the state or transactio ID or timestamp would not match. This is it, pure and simple, and no tokens at all.
    I've used an "object version" pattern like this previously and it works fine, just auto-increment the object version number each time it's rendered on a form. It wouldn't be hard for struts to add built-in support either, a la token (let the action set an objectVersion property on ActionForm, create an <html:objectVersion/> tag, and have the controller verify they match upon submission).

    Someone above mentioned multiple tokens per session. This would be a big improvement over the unique session-scoped token, but having an object version leads to a cleaner Model-View separation.
  14. Tokens is _not a good solution_ at all. Tokens are tolerable if the Model cannot properly validate the data. Well, in that case tokens are better than nothing. But using Model itself to validate data is much much better and cleaner and more generic. You should not care about resubmitting a request, you should care about applying certain data to the Model, about applying modifications to your objects.
    You're discussing the simple case of editing data, where a version number will prevent editing stale data. But what about adding data? There's no way to know that they don't want to add the data you were just sent. There's no way to know they aren't trying to increment the quantity of gizmos in their shopping cart by 2 again. Tokens have their place, as do redirects.
  15. Tokens is _not a good solution_ at all. Tokens are tolerable if the Model cannot properly validate the data. Well, in that case tokens are better than nothing. But using Model itself to validate data is much much better and cleaner and more generic. You should not care about resubmitting a request, you should care about applying certain data to the Model, about applying modifications to your objects.
    You're discussing the simple case of editing data, where a version number will prevent editing stale data. But what about adding data? There's no way to know that they don't want to add the data you were just sent. There's no way to know they aren't trying to increment the quantity of gizmos in their shopping cart by 2 again. Tokens have their place, as do redirects.
    Actually, editing is the hardest part, because there is already existing data in the Model (database, whatever), so we have to check all these status IDs or transaction IDs. Insert and delete is as simple as INSERT or DELETE. Check the data itself, looks good, apply it to the database. If you are inserting data and it is already there, you get insert exception. If you are deleting data which is not there, you get delete exception. As simple as it gets.

    Now the question, how do you know that this data is alredy inserted? The answer is, that you do not insert whatever client sent you. First you great an empty object and assign it its ID, which would become a database PK. The process of inserting data now split up into creating an empty object and then editing it. This works absolutely the same as editing existing object. If you do not to save it, fine, the memory object would be disposed, nothing would change in the database.

    I didn't get your note about gizmos. If you talking about adding the same item to the shopping cart again and again, then I don't care about that. If a customer wants to add it, let him do it. If you are talking about paying twice for the same cart, then, the solution is very simple:
    * lock the customer's cart in synchronized block, you can conveniently synchronize on HttpSession object;
    * in synchronized block invalidate cart, dispose cart ID, store data
    Now if you have normal browser and click Back, you would not see the cart, because it was disposed. So, you cannot resubmit. If you use caching browser, then you would be able to submit the same cart, but it is not there anymore, it was already stored and it ID was disposed. So, you would just get a "Cart not found" error.

    Tokens are applicable when the Model cannot validate input data itself. I say, it is a pretty bad Model.
  16. I think one token per session is too crude. A good mechanism should have one token per operation that change system state. Or even better, it should store a list of tokens that are already processed. If the server recieves a new POST with a token that is in the list, it must come from a form that has already been submitted.
    I agree... I built the token duplicate-form detection stuff for WebWork, and I made it where you could either use the default token name, or supply a token name. There's one token stored in the session per token name, so you can break it down however you like. I suggest one name per usecase or section of the site.
  17. Not so simple[ Go to top ]

    I agree that this is not strictly GET vs POST question, because system often can be changed using GET method as well. On the other hand I think, that "operations that change system state" should be strictly maintained by a controller. It should be not possible to combine both operations in one request.
    I agree with this 100%. But sometimes the controller layer is also responsible for pre-loading data on behalf of the view, which falls under "operations that display system state". This is why I think it is important to make this distinction.

    You end up with two kinds of controllers:

    * Update Controllers: Controllers that change system state, but display nothing.
    * Display Controllers: Controllers that load data for display, but do not change system state.

    You put your redirect between the Update and the Display Controllers, and the Display Controller forwards to the view.

    My ideal web framework would make a clear distinction between these two types of controller operations, but most don't.
    Right, as I pointed out in the article, generally Model should not allow concurrent updates by the same client. Which means that once the server received a [usually POST] request from a client, the Model (well, the part of the model corresponding to that client) must be locked and should not accept other requests until this request is processed (or until some other checkpoint, which may occur before this request is fully processed).
    I should read more carefully! I missed this part of the article. Your token based approach is pretty much the same as the "transaction id" approach that I was suggesting.
    I do not know much about Tapestry. Actually I do not know much about anything else but Struts. Couple of days ago I got to know that Spring supports what I call a PRG pattern using RedirectVeiw class.
    I am not a big Struts fan. I have found that the default implementation of Struts (Action forwarding to View) actually encourages the "bad" way of doing things. While I believe that the authors of Struts are aware this issue, they don't do enough to address it in either the documentation or the framework itself.

    I will have to take a look at Spring again, and see how it tackles the problem.
  18. Not so simple[ Go to top ]

    I am not a big Struts fan. I have found that the default implementation of Struts (Action forwarding to View) actually encourages the "bad" way of doing things.
    You can think about an action as a combination of an action class and a form bean. Either of these classes may be omitted, thus you can fine-tune an action for a particular behavior. The "input" action may use action class only, it would obtain request parameters and update the Model. The "output" action may use form bean only, it would retrieve data from the session or database, and display it. It is a very clean solution and works very well. The only thing you have to care about, is how and where to store the temporary data, which is used in both "input" and "output" actions. You can use form beans with session scope, but I personally did not find it very convenient.
  19. Not so simple[ Go to top ]

    I do not know much about Tapestry. Actually I do not know much about anything else but Struts.
    If you got a couple of days, maybe it's time to know Tapestry:-)
  20. Not so simple[ Go to top ]

    I do not know much about Tapestry. Actually I do not know much about anything else but Struts.
    If you got a couple of days, maybe it's time to know Tapestry:-)
    I knew this was a wrong phrase the moment I submitted my posting ;)
  21. Not so simple[ Go to top ]

    I do not know much about Tapestry. Actually I do not know much about anything else but Struts.
    If you got a couple of days, maybe it's time to know Tapestry:-)
    I have spent 2 days and definitely decided not to use Tapestry even I like many ideas there. So what?
  22. works perfectly[ Go to top ]

    I've written yet-another-web-framework with the main focus on minimizing the number of files required to build an application (e.g. MVC separated but within one file). Anyhow, it uses redirect-after-post as one of its fundaments and it works perfectly. This is a good solution.
  23. works perfectly[ Go to top ]

    I also have a framework that uses this design pattern (for like five years now I think) http://sourceforge.net/projects/SOFIA. It works great. The only thing that still bugs me is that when the user does a lot of actions on one page (Fills in some of a form, clicks save, get's back to the same page but with some errors, fixes and clicks save again, etc...) clicking the back button leaves them on the same page they were on. They have to keep clicking back the same number of times they had clicked a submit button to really go back to a prior page. Really it would be nice if there was something that could tell the browser not to add a page to it's history sometimes, but I haven't found it. But it's still better then that ugly "page expired" message you sometimes get without the pattern. I don't know what rocket scientist came up with that one.
  24. works perfectly[ Go to top ]

    I also have a framework that uses this design pattern (for like five years now I think) http://sourceforge.net/projects/SOFIA. It works great. The only thing that still bugs me is that when the user does a lot of actions on one page (Fills in some of a form, clicks save, get's back to the same page but with some errors, fixes and clicks save again, etc...) clicking the back button leaves them on the same page they were on. They have to keep clicking back the same number of times they had clicked a submit button to really go back to a prior page. Really it would be nice if there was something that could tell the browser not to add a page to it's history sometimes, but I haven't found it. But it's still better then that ugly "page expired" message you sometimes get without the pattern. I don't know what rocket scientist came up with that one.
    Contrary to your observation, I noted the opposite behavior. With regular technique, a page is displayed in response to POST request. If you submit this page and server finds a mistake, it shows the same logical page again with some error message. It can happen again and again and again. So, you have a series of POST requests and responses to them. I am not sure what standart behavior should be, but apparently browser considers each POST request as potential update to server state, thus each POST request is considered to be different. Hey, they usually are different, because if you change a single value and resubmit, POST request would contain different data. Thus this would be a different request. This is why when browser goes back, it redisplays results for all previous requests. And because all pages except the first one were generated in response to POST request, browser shows warning message, that POSTDATA is resubmitted. Ugly and not user-friendly if you ask me.

    If you use redirection with object ID as request parameter, all requests are the same, so most browsers treat mutliple responses to this request as one logical page. So, when you click Back button, you immediately return on the page preceding your form. Isn't that cool? I tried several browsers, and if I am not mistaken, only Opera returns back step by step, like in the case when result page is rendered in response to POST request. MSIE, Netscape and Firefox return immediately to previous logical page. And this is another benefit of displaying result page with simple GET using object ID as parameter.
  25. SOFIA (was: works perfectly)[ Go to top ]

    I also have a framework that uses this design pattern (for like five years now I think) http://sourceforge.net/projects/SOFIA. It works great. The only thing that still bugs me is that when the user does a lot of actions on one page (Fills in some of a form, clicks save, get's back to the same page but with some errors, fixes and clicks save again, etc...) clicking the back button leaves them on the same page they were on. They have to keep clicking back the same number of times they had clicked a submit button to really go back to a prior page. Really it would be nice if there was something that could tell the browser not to add a page to it's history sometimes, but I haven't found it. But it's still better then that ugly "page expired" message you sometimes get without the pattern. I don't know what rocket scientist came up with that one.
    The correct URL for SOFIA is: http://sourceforge.net/projects/salmon

    You can use document.replace("http://yoururl.com") to prevent the browser history from growing with each navigation. I'm not sure that is something that could be used in your case.
  26. TSS Article: Redirect After Get[ Go to top ]

    Webwork handles this pretty nicely with interceptors. Here's a quote from Matt Ho describing the process:
    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 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. /payment-success.jsp /payment-input.jsp 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 :)
  27. Webwork[ Go to top ]

    Webwork handles this pretty nicely with interceptors.
    Handles exactly what?
    Here's a quote from Matt Ho describing the process:
    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 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. /payment-success.jsp /payment-input.jsp 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 :)
    This is not exactly the same. My goal was to _prevent_ duplicate submissions, not to _deal_ with them.
    * If you have a result page pulled by a separate GET request, then you do not have to _deal_ with resubmission of a previous request when you refresh the result page or go back and the forward, because _there is no resubmission_.
    * If you have a View (say, HTML FORM) which shows the actual Model state and can update its controls depending on the data, then you do not have to _deal_ with resubmission if you return to this View from result page, because you simply won't be _able_ to resubmit the same data.

    Prevention is better than dealing with consequences. I should add it to the Mantra.

    If you pull the View using a separate request, then you almost safe. Almost, because some browsers cache web pages (incliding HTML FORMs) even when clearly instructed not to do so. In this case you would have resubmit if you return back to the form and click Submit button again. This problem can be solved using:
    * better browsers and proxies;
    * tokens (Web layer solution)
    * transaction ID or status ID or timestamp (Business/Persistence layer solution)

    So, if you cannot change the Model, then use tokens. But if you can, use the timestamp or transaction ID or object status. This is the more robust solution than tokens, and it works with any type of client, not only with web client.

    As a general rule, web layer should not intervene with data. Web layer should not know or care, is it a first submission or twenty first. It should pass input to the Model, and render a View. Web layer is just a bell-boy. It is the Model, which should decide is the input data valid and can be applied to the Model.

    The solution that you quoted _deals_ with resubmits instead of _preventing_ them. And do not forget, that data is usually submitted with POST, so at each resubmit browser would show confirmation dialog. Maybe you like to click OK buttons, but I am just hate them.
  28. Webwork[ Go to top ]

    This is not exactly the same. My goal was to _prevent_ duplicate submissions, not to _deal_ with them. * If you have a result page pulled by a separate GET request, then you do not have to _deal_ with resubmission of a previous request when you refresh the result page or go back and the forward, because _there is no resubmission_.* If you have a View (say, HTML FORM) which shows the actual Model state and can update its controls depending on the data, then you do not have to _deal_ with resubmission if you return to this View from result page, because you simply won't be _able_ to resubmit the same data.Prevention is better than dealing with consequences. I should add it to the Mantra.
    Yes, but as was pointed out above, you CAN'T ALWAYS PREVENT DUPLICATE POSTS, because the client-side redirect won't necessarily get there before they click the button more than once.

    WebWork also makes it easy to do a redirect by using a "redirect" result, rather than a "dispatch" result. The developer is then in control of whether the page is rendered directly or if the client is redirected to a different Action.
  29. Webwork[ Go to top ]

    Yes, but as was pointed out above, you CAN'T ALWAYS PREVENT DUPLICATE POSTS, because the client-side redirect won't necessarily get there before they click the button more than once.Why? You can synchronize on session object and persist data in the synchronized block. After next request comes, the object status or transaction ID or timestamp would not match anymore.
  30. Webwork[ Go to top ]

    Oops, the preceding message should use quotes:
    Yes, but as was pointed out above, you CAN'T ALWAYS PREVENT DUPLICATE POSTS, because the client-side redirect won't necessarily get there before they click the button more than once.
    Why? You can synchronize on session object and persist data in the synchronized block. After next request comes, the object status or transaction ID or timestamp would not match anymore.
  31. Webwork[ Go to top ]

    Look, you can get pissy about it, but I was pointing out that Webwork does provide a way to handle situations where tokens are necessary. It is a valid approach. I actually do redirects myself, which again are quite easy in webwork:
    <result name="success" type="redirect">event.view.jspa?currentId=${currentId}</result>
    Also, the second token interceptor described will reload the data you created/edited in your first post. It doesn't force you to _deal_ with OK buttons. It makes it appear that a resubmission has occurred, even if it hasn't, which means it's seamless to the user. So it is another valid way of handling this situation, like redirect, that is easy to the user and does not process a request more than once.
  32. Webwork[ Go to top ]

    Look, you can get pissy about it, but I was pointing out that Webwork does provide a way to handle situations where tokens are necessary. It is a valid approach. I actually do redirects myself, which again are quite easy in webwork:
    <result name="success" type="redirect">event.view.jspa?currentId=${currentId}</result>
    Also, the second token interceptor described will reload the data you created/edited in your first post. It doesn't force you to _deal_ with OK buttons. It makes it appear that a resubmission has occurred, even if it hasn't, which means it's seamless to the user. So it is another valid way of handling this situation, like redirect, that is easy to the user and does not process a request more than once.
    Well, I am not the one who is getting pissy ;) I like the approach with redirect and substituted ID value though. About your second token interceptor, maybe I did not understand it right and you can explain it better. But it looks like a request is processed more than once by the server:
    ...if the user hits reload on the payment page: 1. the browser resubmits the app...
    . Which means:
    * a user sees "Do you want to resubmit POSTDATA" dialog box, so he has to deal with OK button;
    * the same request is submitted, web layer tries to deal with it without notifying the Model;
    * web layer needs to cache the requests for sole purpose of catching resubmits;
    * the user sees the cached results with may not reflect the actual Model state.
  33. Webwork[ Go to top ]

    Sorry, hadn't had my coffee yet.:)

    As for the second interceptor, you should check with Jason and the webwork guys for more info on that; I don't use it because I tend to favor the redirect approach. But from what I understand, it knows that on the first time through, it will perform the action. It also caches the results. So on the second time through, it skips the action and takes you directly to the results, using the cached data. So that way, if you attempt to resubmit, you'll be taken to the results of the first submit directly, without having to click on any buttons.

    What I wanted to make clear about webwork support for the redirect is that it's easy to redirect using dynamic data, by using the parse feature of the result config. This lets you have results like "type=redirect;location=myAction.jspa?id=${currentId}". This is important on a redirect because that information won't be available normally after the redirect like it would on a dispatch, since you're in a new request cycle. Webwork will parse "currentId" based on the contents of the current action BEFORE the redirect to construct the URL, then pass the URL to the redirect. It's been a long time since I've done something similar in Struts but I do recall that this was a bit more difficult there (though I may be remembering wrong, or things may have changed).
  34. Webwork[ Go to top ]

    Sorry, hadn't had my coffee yet.:)As for the second interceptor, you should check with Jason and the webwork guys for more info on that; I don't use it because I tend to favor the redirect approach. But from what I understand, it knows that on the first time through, it will perform the action. It also caches the results. So on the second time through, it skips the action and takes you directly to the results, using the cached data. So that way, if you attempt to resubmit, you'll be taken to the results of the first submit directly, without having to click on any buttons.What I wanted to make clear about webwork support for the redirect is that it's easy to redirect using dynamic data, by using the parse feature of the result config. This lets you have results like "type=redirect;location=myAction.jspa?id=${currentId}". This is important on a redirect because that information won't be available normally after the redirect like it would on a dispatch, since you're in a new request cycle. Webwork will parse "currentId" based on the contents of the current action BEFORE the redirect to construct the URL, then pass the URL to the redirect. It's been a long time since I've done something similar in Struts but I do recall that this was a bit more difficult there (though I may be remembering wrong, or things may have changed).
    Ok, it takes a little knowledge of the WebWork internals... It actually caches the ActionInvocation, which includes the Action, the value stack, and the result. On subsequent duplicate tokens it short circuits the ActionInvocation, replaces it with the cached one, and tells it to skip executing the Action and just render the result. So it doesn't cache the HTML output, that is re-rendered from the JSP / Velocity, it just keeps the Action processing and the rest of the Interceptors from happening.
  35. Webwork[ Go to top ]

    Ok, it takes a little knowledge of the WebWork internals... It actually caches the ActionInvocation, which includes the Action, the value stack, and the result. On subsequent duplicate tokens it short circuits the ActionInvocation, replaces it with the cached one, and tells it to skip executing the Action and just render the result. So it doesn't cache the HTML output, that is re-rendered from the JSP / Velocity, it just keeps the Action processing and the rest of the Interceptors from happening.
    I find it a little to complex. Instead of doing what it was asked to, which is to apply data to the Model, the web layer analyzes a request and tries to fool the business layer. Also, resubmit can happen not only from clicking Submit again, but from innocent page refresh. So, a user would still see nagging messages about resubmitting POSTDATA, trying to understand, what is happening. The browsing process itself, when on the way back and forward browser shows dialog boxes, is not really pleasant too. Well, the user experience can be improved by changing POST to GET, this way browser would not show warning message. This is not very clean from my point of view, but can work. The URL in the address line will sure look ugly though.

    I prefer to make every effort to eliminate resubmits. If for some reason, say because of nervous user clicking Submit ten times before response comes, or because of page was cached in a browser, the data is actually resubmitted, it would be dealt with. But resubmission and its handling should not be a normal course of operation.
  36. Webwork[ Go to top ]

    I find it a little to complex. Instead of doing what it was asked to, which is to apply data to the Model, the web layer analyzes a request and tries to fool the business layer. Also, resubmit can happen not only from clicking Submit again, but from innocent page refresh. So, a user would still see nagging messages about resubmitting POSTDATA, trying to understand, what is happening. The browsing process itself, when on the way back and forward browser shows dialog boxes, is not really pleasant too. Well, the user experience can be improved by changing POST to GET, this way browser would not show warning message. This is not very clean from my point of view, but can work. The URL in the address line will sure look ugly though.I prefer to make every effort to eliminate resubmits. If for some reason, say because of nervous user clicking Submit ten times before response comes, or because of page was cached in a browser, the data is actually resubmitted, it would be dealt with. But resubmission and its handling should not be a normal course of operation.
    This can, of course, be used in conjunction with a redirect, and the redirect will be sent again without re-executing the Action... This isn't trying to "fool" the business layer. The business layer has already done its job, so this is there to keep it from having to worry so much about getting StaleObjectExceptions when you try to update a Hibernate object again and it finds the version is incorrect. Rather than have to handle this error if they click the button twice before the redirect gets sent back, isn't it better to just send the redirect again and let them see the page they should have seen in the first place?
  37. Webwork[ Go to top ]

    This can, of course, be used in conjunction with a redirect, and the redirect will be sent again without re-executing the Action...
    I like redirection :) And a user would not see a dialog box, which is a huge improvement for the user experience.
    Rather than have to handle this error if they click the button twice before the redirect gets sent back, isn't it better to just send the redirect again and let them see the page they should have seen in the first place?
    Um... In my approach resubmits are rare. Basically there are two cases: when a user clickes several times on Submit button before receiving a result page, or when a browser keeps page in cache. In both cases a user should be notified that something wrong have happened.

    In your implementation resubmits are normal, so you just swallow them and make an impression that nothing happened. How would you know when to show the same data, and when to alert about an error? I would prefer to show an error page or "Please, wait" message if button is clicked twice.

    Anyway, if using your approach without redirection, a user will be bugged by warning messages, which I wanted to prevent at the first place when I started to think about redirection. But if you switch to redirection, why all this request caching, if you can just pull the result page using some ID (object id, transaction id)? Which is what you basically do, but you do it using some convoluted way of cached requests, while I do in a very straightforward way: client asks for an object with a particular ID, and server returns one. Which also helps to keep my data in order: only objects can be shown or modified.
  38. Webwork[ Go to top ]

    This can, of course, be used in conjunction with a redirect, and the redirect will be sent again without re-executing the Action...
    I like redirection :) And a user would not see a dialog box, which is a huge improvement for the user experience.
    Rather than have to handle this error if they click the button twice before the redirect gets sent back, isn't it better to just send the redirect again and let them see the page they should have seen in the first place?
    Um... In my approach resubmits are rare. Basically there are two cases: when a user clickes several times on Submit button before receiving a result page, or when a browser keeps page in cache. In both cases a user should be notified that something wrong have happened. In your implementation resubmits are normal, so you just swallow them and make an impression that nothing happened. How would you know when to show the same data, and when to alert about an error? I would prefer to show an error page or "Please, wait" message if button is clicked twice.Anyway, if using your approach without redirection, a user will be bugged by warning messages, which I wanted to prevent at the first place when I started to think about redirection. But if you switch to redirection, why all this request caching, if you can just pull the result page using some ID (object id, transaction id)? Which is what you basically do, but you do it using some convoluted way of cached requests, while I do in a very straightforward way: client asks for an object with a particular ID, and server returns one. Which also helps to keep my data in order: only objects can be shown or modified.
    You're missing the point. In the case where the user clicks the button more than once, your way will result in an error page telling them that they've done something wrong and you'll have a stack trace in your logs for a StaleObjectException.

    In my way, you keep the invocation state of the first request and re-render the result. In the case of a redirect result, this causes the redirect to be sent to the user again, which they missed the first time because they were clicking more than once. Then they are redirected to the correct page and see the result they would have gotten the first time.

    Tokens make this possible, because you are able to identify that these two requests were actually duplicates and you are able to be smart about not causing errors in your service layer code and not having to do database roundtrips to figure out that the data is duplicate.
  39. Webwork[ Go to top ]

    You're missing the point. In the case where the user clicks the button more than once, your way will result in an error page telling them that they've done something wrong and you'll have a stack trace in your logs for a StaleObjectException.

    In my way, you keep the invocation state of the first request and re-render the result. In the case of a redirect result, this causes the redirect to be sent to the user again, which they missed the first time because they were clicking more than once. Then they are redirected to the correct page and see the result they would have gotten the first time.
    I see. This is a really nice feature. But it shines in only one use case: when a user clicked Submit several times, before receiving the result. This does not happen often, and it depends solely on user actions. Still, this use case should be handled and your solution is very nice.

    On the other hand, what would happen, if a user returns back to the cached page and click Submit again? Would he get the same result page, because it is the same request, or a different page, because the result already has been shown? I hope it would be a different result, displaying an error.
    Tokens make this possible, because you are able to identify that these two requests were actually duplicates and you are able to be smart about not causing errors in your service layer code and not having to do database roundtrips to figure out that the data is duplicate.
    I admit that this particular case (multiple submits before returning the response) is a good application for tokens. This use case seems to be a pure web layer issue, and tokens work great here. But I think that other cases should be handled by the Model/database. Instead of trying to figure out the applicability of the data in the web layer I would rather ask the database about it. This may take longer, but it seems like a cleaner approach to me.

    Side note: each framework has its own niceties. It would be great to have a list of cool features implemented in different frameworks. And maybe even combine them in one super-duper framework ;)
  40. Webwork[ Go to top ]

    You know, the Token technique and the Post-Redirect-Get technique are not mutually exclusive. There is no reason why you can't use both. Personally, that's what I advocate these days.

    The Post-Redirect-Get technique is important for getting a good "webish" application, because it makes the web application better behaved when the user clicks the back and refresh buttons.

    The Token technique is important because it handles which marginal cases like double-submits as well as extended operations (where form processing takes 30 seconds or so). Also, I think the Token-in-a-session technique is important for clustered applications.

    Both in combination give you pretty good protection against accidental resubmission.
  41. Webwork[ Go to top ]

    You know, the Token technique and the Post-Redirect-Get technique are not mutually exclusive. There is no reason why you can't use both.
    I agree with you. I was a little wound up, trying to defend the need for redirection and the importance of using Model to validate input data.
  42. Some code?[ Go to top ]

    It would be nice to get some Java code for this paper. I think it would help to understand the subject.

    Finsals Collons
  43. Some code?[ Go to top ]

    It would be nice to get some Java code for this paper. I think it would help to understand the subject.Finsals Collons
    The next article will discuss the simple web application implementing this pattern with Struts.
  44. Some more code?[ Go to top ]

    It would be nice to get some Java code for this paper. I think it would help to understand the subject.
    The next article will discuss the simple web application implementing this pattern with Struts.
    Since there has been quite a bit of talk about Tapestry, could you also show how to implement the pattern with Tapestry?
    Thanks!
  45. TSS Article: Redirect After Get[ Go to top ]

    FWIW, this model is exactly what the Portlet spec promotes with the action/render separation. The Pluto RI implements it using redirects.
  46. portlet Spec[ Go to top ]

    I did not read anywhere that the portlet spec promotes a redirect (and thus an extra client/server roundtrip) within portlet invocations, i.e. between action and render.
  47. portlet Spec[ Go to top ]

    I did not read anywhere that the portlet spec promotes a redirect (and thus an extra client/server roundtrip) within portlet invocations, i.e. between action and render.
    Yes, it doesn't promote, but it seems redirection is must rational way to implement action->render transition.
  48. Workflow Engine system and redirect[ Go to top ]

    Cool, we used the same pattern in our WF system, a POST change the Status with a transition, then a redirect show me the new status. We also put some Javascript tricks to disable double POST problem (very common indeed....)...
    Anyway I didn't read anything about Cocoon 2 system for mantaining state. I think that is panacea of Web User Interfaces...
    Ciao.
  49. use a filter[ Go to top ]

    its "not so hard" to write a Filter that

    - generates the token
    - makes a copy of the request.getParameterMap()
    - stores this copy in the session scope using the token
    - sends redirect
    - after the redirect, wrap the copy in a RequestWrapper
    - uses a magic parameter to check whether a reload is allowed ("idempotent")
    - delete the data after "success"

    => this removes all POST requests transparently
  50. TSS Article: Redirect After Get[ Go to top ]

    There are two good use cases for redirect in web application:
    1. change protocol (redirect to HTTPS port or back).
    2. change domain or web server (redirect to external app or resource).

    Any redirection use cases ralated to controler is a workaround for broken controler design.
  51. Continuations anyone?[ Go to top ]

    FWIW Cocoon's flow control (http://cocoon.apache.org/2.1/userdocs/flow/index.html) solves this very nicely through the use of continuations (instead of working around the limitations of FSM solutions):

    ... entering a flow control function being mapped to a particular URL ...
    //sending first page
    sendPageAndWait("page1");
    //processing submission of first page
    ...
    //sending second page
    sendPageAndWait("page2");
    ...
    //now capture the continuation
    cont = sendPageAndWait("page3");
    //cont now contains the continuation used for picking up processing when page3 gets submitted
    //after page3 has been submitted the continuation gets invalidated
    cont.invalidate();
    //update the model
    ...
    sendPageAndWait("success");

    The user can hit the back button now to navigate to page1, page2 or page3. The continuations engine magically picks up the processing stack at the right place. But since the continuation has been invalidated after submitting page3 it's not possible to just reload the "success" page. The user first has to reload page3 to let the continuation engine generate a new continuation (and copy the then valid processing stack). If you want to force your user to go through all the pages again, just capture the continuation of page1 instead of page3.

    Isn't this much simpler? Opening multiple browser windows isn't an issue as each window gets its own continuation.

    Guido
  52. Continuations anyone?[ Go to top ]

    ... entering a flow control function being mapped to a particular URL ...
    //sending first page
    sendPageAndWait("page1");
    //processing submission of first page
    ...
    //sending second page
    sendPageAndWait("page2");
    ...
    //now capture the continuation
    cont = sendPageAndWait("page3");
    //cont now contains the continuation used for picking up processing when page3 gets submitted
    //after page3 has been submitted the continuation gets invalidated
    cont.invalidate();
    //update the model
    What if I want to enter data on the second page before entering data on the first page? But this should be a different topic about flow engines and I have my own solution for that as well ;)
  53. Continuations anyone?[ Go to top ]

    ... entering a flow control function being mapped to a particular URL ...
    //sending first page
    sendPageAndWait("page1");
    //processing submission of first page...
    //sending second page
    sendPageAndWait("page2");
    ...
    //now capture the continuation
    cont = sendPageAndWait("page3");
    //cont now contains the continuation used for picking up processing when page3 gets submitted
    //after page3 has been submitted the continuation gets invalidated
    cont.invalidate();
    //update the model
    What if I want to enter data on the second page before entering data on the first page?
    No, I never had that use case yet. My point wasn't to criticize the article. I think the article is excellent.

    I just wanted to point out that the use of continuations for webapps in many situations is superior to a FSM approach, no matter how sofisticated your workarounds are. The double submit is just one out of many "problems" nicely solved with continuations. In some sense continuation are the token approach taken to the next step but in a very transparent way. One way to invoke the continuations engine may be via:
    <form action="${continuation.id}">

    Each token (be it a one time token or not) is assigned to a particular execution stack and the continuations engine resumes execution on submit. This model brings the single threaded view to your webapp (like any other app), no need for FSMs anymore.

    Guido
  54. FYI, redirects on certain mobile devices will always display a message to the end user that they are being redirected to another page. Unfortunately this makes for some very ugly transitions making the PRG technique a problematic solution in those cases.
  55. FYI, redirects on certain mobile devices will always display a message to the end user that they are being redirected to another page. Unfortunately this makes for some very ugly transitions making the PRG technique a problematic solution in those cases.
    This problem was mentioned in the article. If this happens after receiving 302 response, then these mobile devices work according to HTTP spec. But the implementation in popular browsers is different (no confirmation), and a lot of applications already use this de-facto behavior. Someone has to bend over. Another choice is to return 303 response code instead of 302. See http://ppewww.ph.gla.ac.uk/%7Eflavell/www/post-redirect.html, this page is included in the reference list.
  56. Good blog entry[ Go to top ]

    Larry Williams wrote up a good blog entry on doing this stuff with WebWork and Hibernate:

    http://jroller.com/comments/larrywilliams?anchor=secure_and_successful_posting_with
  57. Same task using Forward[ Go to top ]

    I've tried to solve this problem last week.
    I'm using Struts (actually, I'm quite new to Struts...) and my input/output architecture is structured with "wizard" forms:
    - call to action populates default for the form and forward to jsp view
    - post from jsp to an action that validates input and populates defaults from the next from of the wizard.
    Obviously, the process of validation of form N will also produce default for form N+1.
    My ActionForms are request-scoped: only my business objects are session scoped;
    the business object properties could be entered with 3 o 4 different html forms:
    - input from an action form is copied into business object (after syntax validation of numbers, dates and so on.)
    - a validation on business object is called to perform business validation on properties entered on form N and populates default properties values in the b.o.
    - copy properties (with Apache BeanUtils or similar) from b.o. to a newly created ActionForm that represents view N+1
    - forward to view N+1 (jsp page).

    All my action mappings contain a list of legal "previous" action; ex: you could arrive to ActionC only from ActionA or ActionB, otherways you are trying to make refresh or you made bak and resubmit.
    Every action can detect previous executed action and determine if do input processing, business logic and forward to view or only forward to view.
    Of course, you are forced to write actions with a certain pattern.
    I wrote a simple Struts application that do a demo of if and everything seems to work well; you can also make things like CTRL+N and obtain a new page and the flow execution could be performed alternatively on the first page and on the "cloned" page leading to the correct result.

    Davide
  58. Is this a bad dream?[ Go to top ]

    Come on now folks, are you kidding? This pattern is well known since the days of "Alex and Philips Guide to web publishing" (http://philip.greenspun.com/panda/). So the loads of "engineers" and holders of "M.Sc. in Computer Science", after 5 - 10 years on the job, have discovered the fact that there is something wrong with Post & Back and the state of the form.

    I am truly thrilled. And still there is bad bad advice in the article, for example that the back button should never show a cached representation and always shows a model. Well the page may well be out of sync, but if it is, say an input form for a customer comment or a listing of a mailbox there are also good reasons not to bother wasting the clients bandwidth and money, and make him rerender the page.
  59. Is this a bad dream?[ Go to top ]

    Come on now folks, are you kidding? This pattern is well known since the days of "Alex and Philips Guide to web publishing" (http://philip.greenspun.com/panda/).
    Oh, so it is well-known? Great news, I don't have to call it in my name then. I would appreciate if you list other must-read books.
    So the loads of "engineers" and holders of "M.Sc. in Computer Science", after 5 - 10 years on the job, have discovered the fact that there is something wrong with Post & Back and the state of the form.
    I knew that I haven't invented anything, I said it in the article. I've discovered the secret de polichinelle, but I did it myself. Loads of web developers have not yet. And thanks for finding a typo, of course it should read: MS in Computer Science.And still there is bad bad advice in the article, for example that the back button should never show a cached representation and always shows a model. Well the page may well be out of sync, but if it is, say an input form for a customer comment or a listing of a mailbox there are also good reasons not to bother wasting the clients bandwidth and money, and make him rerender the page.What is better: Buddhism or Christianity? I explained the reasons why I think that View always should represent current Model state. If you think differently, you may want to share your vision.
  60. Is this a bad dream?[ Go to top ]

    Missed the quote tag:
    And still there is bad bad advice in the article, for example that the back button should never show a cached representation and always shows a model. Well the page may well be out of sync, but if it is, say an input form for a customer comment or a listing of a mailbox there are also good reasons not to bother wasting the clients bandwidth and money, and make him rerender the page.
    What is better: Buddhism or Christianity? I explained the reasons why I think that View always should represent current Model state. If you think differently, you may want to share your vision.
  61. poorly written article[ Go to top ]

    [disclaimer]The subject of my comment has nothing to do with the content of the article, so please don't confuse the purpose of my complaint.[/disclaimer]

    This article is so poorly written in terms of grammer that I can hardly stand to read it. I understand that English is not necessary the primary language of all of this site's members, but if you know you are going to publish an article, at least allow someone else to PROOFREAD it for errors! As programmers, everyone here should appreciate the need for delivering quality work, even if it means having someone else review your code. In the same light, it should not be too much effort to have someone else read over your articles before publishing them.

    Having an article such as this published reflects poorly on this otherwise excellent site and programmers in general.
  62. C`mon[ Go to top ]

    C`mon... ZzZzZZZzzz...
  63. TSS Article: Redirect After Post[ Go to top ]

    This approach forces you to store transactional information in Session, this is something that might not be advisable/acceptable in all situations. Typically we only store User Credentials in Session. Secondly it adds one more step before you get to see the result of your action. That means two server trips instead of one - more wait. I believe if we refine the Token approach, it would make it much more convenient.
  64. Secondly it adds one more step before you get to see the result of your action. That means two server trips instead of one - more wait. I believe if we refine the Token approach, it would make it much more convenient.
    More wait? I would not care even with dialup. A user stares at the screen much longer than it takes to load a page. And, even with refined token approach, what would you do about "POSTDATA will be resent" message? Do you like clicking OK, while browsing back and forth? Another nicety of using separate page for View, is that most browsers consider it the same despite of how ofter it was displayed. Say, you filling out a form and made a mistage, and another one, and another one. With POST, if you click Back, browser would show your previous attempts. But with GET most browsers immediately return to the previous logical page.
  65. I would not care even with dialup. A user stares at the screen much longer than it takes to load a page.
    Cannot be a rule.
    And, even with refined token approach, what would you do about "POSTDATA will be resent" message? Do you like clicking OK, while browsing back and forth?
    Agreed, but only when you refresh.
    Another nicety of using separate page for View, is that most browsers consider it the same despite of how ofter it was displayed. Say, you filling out a form and made a mistage, and another one, and another one. With POST, if you click Back, browser would show your previous attempts. But with GET most browsers immediately return to the previous logical page.
    That is all about GET/POST. The essence of this pattern is redirection.
    <bold>PlusPoint : Neater MVC Implementation.</bold>
  66. TSS Article: Redirect After Post[ Go to top ]

    Thanks for writing this. It is possibly the best thought-out article I have read on TSS.
  67. Correction on Mozilla/Firefox[ Go to top ]

    I bashed Mozilla/Firefox for not honoring "no-cache" response header. This turned out not to be true, Mozilla works correctly with this header. Mozilla does not use "no-cache" when it returns back on a history list (that is, when a user clicks the Back button). This adheres to part 13.13 of the spec, which instructs browser to show old page with the same exact content. But MSIE and older Netscape browsers reload a "no-cache" page if Back button is clicked, this behavior is exploited in the suggested approach.

    HTTP 1.1 (RFC 2616) specifies another cache-control header: "no-store", which instructs cache not to store a response. So, to instruct Mozilla to reload a page when a Back button is clicked, HTTP response must contain "no-store" header.
  68. The live example and the source code[ Go to top ]

    Ok, I was there and back about posting links to my site. I also waited for the second article with implementation details to be published here on TSS (still waiting ;), but anyway here are couple of links:

    This is the page where you can try both traditional and redirecting applications and download the sample source code: [url]http://www.superinterface.com/projects/prgtoolkit.htm[/url]

    This is the traditional forwarding application.
    This is the improved redirecting application.
    This is the sample source code.

    Refrain from using Firefox because it still has a bug as of 1.0 Preview Release: it does not reload pages marked as "no-store". It should be fixed in public release. For now I prefer either MSIE or older versions of Netscape.
  69. double submit on Refresh button[ Go to top ]

    Hi, i wrote this piece of code for double submit problem on click of refresh button. Its working fine so far. Try this code String header = request.getHeader( "accept" ); if(header.equalsIgnoreCase("*/*")){ // handle your code }