Most of my projects over the last several years have included an interactive website component, one designed primarily to capture information from the user. My topic today is about how these websites deal with the browser controls such as the dreaded Back, Forward, Refresh, and Bookmark.
I’ve never encountered a deeper “religious” reaction from engineers (including myself) than on this issue. Many issues reach this level, but none have exceeded it.
Another characteristic of this issue is that it is often dismissed by the business users as minutia. The issue is a bit complex to understand, but I believe the real reason business dismiss it is because they are not driven to fully confront the related questions until it is too late to do something about it.
So what’s the problem?
Technically speaking, the issue’s core is this “POST/FORWARD” (PF) scenario (non-techies, please bare with me):
- User enters data into a page and submits to the server using a POST
- Server processes the data and renders the resulting page by FORWARDING from within the server
- Browser believes the correct path to the result page is to submit the original POST
NOTE: In ASP.NET, Forward is accomplished with Server.Transfer().
This wreaks havoc on the browser’s navigational controls. If the user navigates back to the page using, say, the back button, the browser tries to resubmit the original POST it knows will retrieve that page. This is evidenced thru the all too common “Your browser needs to resend information you have previously submitted” message.
This situation can also cause severe application issues if it is not dealt with in some way. The resubmission of the POST causes the server to process the POST again. If the POST was a request to charge a credit card, or add an item to a shopping cart, you may end up with an unintended duplicate of either of these items.
How people are addressing this problem
I’ve seen several “creative” solutions for this exact problem. I’ll start with the ones I don’t like:
Warn the user to not click the back button
Some text on some page tells the user to not use the back button. I guess this works in some cases, but c’mon… we can to better.
Disable browser navigation
Yes, there are ways to do this; and yes, people try. This one is particularly frustrating to me. The first reason is that you cannot fully disable all browser navigation. The second reason is it goes against the general concepts of web browsing.
Warn the user if they navigate away
A major ramification of POST/FORWARD is that a user cannot reliably navigate to a page using the back button. This includes situations where they accidentally click a link or otherwise navigate to another URL. This can be annoying if the user had nearly completed a multi-page interview, and accidentally clicks the browser’s “Home” button. A common solution for this is to try to intervene when the user leaves a page, using Java script to display a message box allowing them to cancel the “navigate away” command.
My major complaint with this solution is that if the user does navigate away, there is often no way back into the interview, unless the application specifically accommodates for this.
Use some variation of the token solution
Basically, a unique “token” is added to the form when it is first rendered. The server compares it upon submit to something held in session. This allows the application to prevent a page submission from being processed twice, and to take some alternate action when double submission happens.
This solution has a popular following with Java/Struts and has some applicability for this problem. It might be a good solution in some applications for performance reasons. I believe this is not the optimal solution. The browser still attempts to POST data when the user really wants to view. This solution addresses the symptom, not the problem. Proper implementation of this solution requires substantial logic for supporting reentry and determining where to forward the user in various situations.
Use thick-client instead of a browser
Modern thick-client technologies (such as WinForms with remoting or web services) are sometimes employed to provide a custom UI for certain problems. This avoids the need to support browser navigation all together by removing it from the paradigm. If this is an option, it is worth considering.
A better way?
I have always intuitively believed that pages should be displayed using an HTTP GET. I believed this even before I fully understood the difference between GET and POST.
To say this in plain English, what you see in your browser window should depend on the URL you navigated to, and not the decision made by the server when you last submitted data.
The first interactive website I ever wrote REDIRECTED the user to the correct page after processing form submissions. That is, the server told the browser where to go next. As I began working with other web applications, I was surprised to find this was not ‘yet’ the standard way to get the user to the next page. I was surprised that the “POST/FORWARD” scenario discussed above was as prevalent as it actually is.
As I have studied this issue over the years, I’ve found that I am not alone in my beliefs. There are various “movements” encouraging an alternative to “POST/FORWARD”. One movement is named “POST/REDIRECT/GET” (PRG). Another one is called “GET after POST”. Hopefully these names are self-explanatory enough... These are both the same thing:
- User enters data into a page and submits to the server using a POST
- Server processes the data and sends a REDIRECT instruction to the browser
- Browser issues a GET statement back to the server to get the next page
I won’t go into detail about these solutions. I encourage you to read thru the referenced links get a full understanding of the solution. But I will support the conclusion that this model solves most, if not all, browser navigation issues caused by POST/FORWARD.
Concerns with PRG
Like I said, people seem to be religious about this issue. I suppose I’m guilty of this myself. Here are the concerns people have raised with the pattern, and my responses to them:
2 Trips to the server? PERFORMANCE WILL BE HORRID!!!
PRG does, indeed, require 2 trips to the server. The first trip POSTS the form data, and the second GETS the resulting page. This is not as bad as it sounds. The intermediate redirect should cause a negligible performance difference in these kinds of applications. It is possible that this performance difference is not acceptable in all cases, but I encourage a knowledgeable understanding (POC testing) before throwing the baby out with the bath water.
Business data on the query string? SECURITY!! DANGER!! DANGER!! SECURITY!!
PRG usually requires some identification information to be included on the GET query strings. This is not to say that all business data is passed on the query string, only that the GET statement may need to identify what it is GETTING. For example, if you have POSTED changes to one of your bank accounts, the application may want to redirect you to a detail page about that account like this:
https://www.bank.com/DisplayAccount.aspx?account=checking123
In POST/FORWARD, it is not always necessary to display this query string, because the interaction occurs on the server. Some argue that displaying this URL encourages fishing, where a malicious user could attempt to obtain other accounts by experimenting with other account numbers:
https://www.bank.com/DisplayAccount.aspx?account=checking124
https://www.bank.com/DisplayAccount.aspx?account=checking125
https://www.bank.com/DisplayAccount.aspx?account=checking126
… etc.
My response to this important concern is this: in applications like this, IT IS IMPERATIVE that access-control security be employed to manage this security independently from what ever UI is used to display the data. A more advanced hacker can attempt to alter the POSTED forms on similar fishing expeditions. If security is a genuine concern, it MUST be dealt with separately.
Users expect this behavior. WHY ARE YOU OVER ENGINEERING???
In POST/FORWARD, users are confronted with warnings, and usage conditions, and various other unpleasantries. It is true that the savvy user has grown accustomed to these things. But we have not yet reached the point where all users are of this level. In fact, I have found that few users understand web with the depth we might expect.
Also, a simple warning-less site adds to the credibility of the site and the company. It is usually important to provide as simple an experience as possible, especially if it is just as easy to accomplish… which brings me to my next point.
Implementing PRG is no more difficult than implementing PF. In fact, I would argue implementing PRG is more natural and simple than PF. PRG does introduce design questions that must be dealt with, but so does PF.
Strange scenarios still arise. YOU HAVE NOT FIXED ANYTHING!!!
It is true that certain strange situations could happen. Consider this scenario:
- User navigates to the Edit Bank Account page and closes the account with a POST to the server
- The server closes the account and instructs the browser to redirects to the List of Accounts page
- User clicks the back button to get back to the Edit Bank Account page, but the account has been cancelled
This highlights a certain kind of problem with browser navigation in general. The problem does not disappear with PRG, but it is not worsened either. In both cases, programmers must accommodate for this situation during page display. Many other scenarios are fixed with PRG; it is not fair to say that it fixes “nothing”.
Conclusion – Use POST/REDIRECT/GET
Michael Jouravlev describes the PRG mantra as (quote):
Never show pages in response to POST
Always load pages using GET
Navigate from POST to GET using REDIRECT
After a pretty thorough study of the topic and options, I am fully behind this approach. I recognize that it does not fix every problem with using the browser paradigm to supply an interactive UI. But PRG fixes a fundamental problem with the PF approach, and allows application development to begin on a solid foundation.
References
Several of these links are JAVA specific, but the underlying technology we are addressing is Web, not language. These articles are directly applicable to our problem -- for the most part.
Michael Jouravlev has written a good article on PRG. The article suffers slightly from ESL, but the concepts are solid.
http://www.theserverside.com/tt/articles/article.tss?l=RedirectAfterPost
Michael Jouravlev’s article was preceeded with this post
http://www.theserverside.com/patterns/thread.tss?thread_id=20936
AdamV has compiled a brief summary of his experiences:
http://adamv.com/dev/articles/getafterpost
The difference between Response.Redirect() and Server.Transfer() in ASP.NET. Warning, Karl says discusses using Server.Transfer() for Wizards… don’t listen to him :):
http://www.developer.com/net/asp/article.php/3299641
Some related links:
http://www.theserverside.com/news/thread.tss?thread_id=41285
http://keithdevens.com/weblog/archive/2002/Apr/18/GETAfterPOST
http://www.w3.org/QA/Tips/reback