CSC/ECE 517 Fall 2009/wiki2 3 bd

From Expertiza_Wiki
Jump to navigation Jump to search

Synchronizer token pattern

Introduction

Developers of web sites face problem with maintaining normal control flow when there is multiple form submissions The issue occurs when the user clicks on the submit button twice and the data is sent back to the client twice or when client access a previously bookmarked page . Control flow sequence is particularly important to preserve when form submission involves transaction processing on the server. There are many solutions to the situation ,some websites just warn the user not to submit twice once a submission is made and to wait for the response .In other cases we have client side and server side solutions to the problem .Server side solution being more reliable than client side .In the client side model the button is disabled based on a flag which is set once first submission is performed .Synchronizer token pattern is one such server side method in which once a submission is made a flag is set and on re-submission the stored value is compared and if it is a duplicate no action is taken . Below we discuss some the ways this is handled in different in different languages -

Java

Java uses the server side solution to solve this problem .The basic idea is to set a token in a session variable before returning a transactional page to the client. This page carries the token inside a hidden field. Upon submission, request processing first tests for the presence of a valid token in the request parameter by comparing it with the one registered in the session. If the token is valid, processing can continue normally, otherwise an alternate course of action is taken. After testing, the token resets to null to prevent subsequent submissions until a new token is saved in the session, which must be done at the appropriate time based on the desired application flow of control. In other words, the one-time privilege to submit data is given to one specific instance of a view.

Based on the above, the solution appears complete. But an element is missing: how do we specify/implement the alternate course of action when an invalid token is detected. In fact, given the case where the submit button is reclicked, the second request will cause the loss of the first response containing the expected result. The thread that executes the first request still runs, but has no means of providing its response to the browser. Hence, the user may be left with the impression that the transaction did not complete, while in reality, it may have successfully completed.This tip's proposed strategy builds on the Struts framework to provide a complete solution that prevents duplicate submission and still ensures the display of a response that represents the original request's outcome. The proposed implementation involves the abstract class SynchroAction, which actions can extend to behave in the specified synchronized manner. This class overrides the Action.perform() method and provides an abstract performSynchro() method with the same arguments. Below is the example implementation in Java using struts

Eg Code:

public final ActionForward perform(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException 
{ HttpSession session = request.getSession();
 ActionForward forward = null;
 if (isTokenValid(request)) 
{ 
 try { 
session.setAttribute(FORM_KEY, form);
 session.setAttribute(FORWARD_KEY, forward);
 ActionErrors errors = (ActionErrors) request.getAttribute(Action.ERROR_KEY);
 if (errors != null && !errors.empty()) 
{ 
 saveToken(request); 
 } 
 session.setAttribute(ERRORS_KEY, errors); 
 session.setAttribute(COMPLETE_KEY, "true"); } 
catch (IOException e)
 { 
 throw e; 
 } 
catch (ServletException e) 
{ 
 session.setAttribute(COMPLETE_KEY, "true"); 
 throw e; } } 

else { 
 { 
throw it if (e != null) { if (e instanceof IOException) { throw (IOException) e; } 
else if (e instanceof ServletException) 
{ throw (ServletException) e; 
} } context if ("request".equals(mapping.getScope())) 
{ request.setAttribute(mapping.getAttribute(), f); } 

else { session.setAttribute(mapping.getAttribute(), f); } 
(ActionErrors) session.getAttribute(ERRORS_KEY)); 
session.getAttribute(FORWARD_KEY); } 
else { 
 return forward; }

As we can see from the above code that for a valid token the protected action is performed only once ,while the action is being performed any other requests are received then they are directed to performInvalidToken() method until the action is completed ,this method simply returns an ActionForward named "synchroerror" ,this should lead to a form with a button for continue , this just resubmits without any parameters and nothing happens until action of first form submission is completed .When the action completes, it stores its forward, form, exception, and errors, if any, in the session, and it sets a flag to indicate it has completed. The first request coming after the action completion will get the forward, form, exception, and errors from the session and continue as if it was the first request itself.

PHP

The following method is one of the ways of creating a Synchronous token pattern in PHP

The below given code needs to be added to ur code


<?php
session_start();
$secret=md5(uniqueid(rand(), true));
$_SESSION['FORM_SECRET']=$secret;
?>

Here uniqueid() is used to create Unique ID. Then md5() is used to create 32 character hash of this Unique ID. This unique id is stored in the session for later use. We also need to use a different session variable for each form.

Then we add a hidden field anywhere in the form using the below given code.
<input type="hidden" name="form_secret" id="form_secret" 
value="<?php echo $_SESSION['FORM_SECRET'];?>" />

we need to compare the value of the hidden field with the value stored in the session. If the value is the same, that means its the first time. Hence process the form data. Now unset the values stored in this session. now when the user refreshes the page, the code for processing form will be skipped. It can be done as given below

<?php
session_start();
//get the hidden value
$form_secret=$_POST['form_secret'];
if(isset($_SESSION['FORM_SECRET'])) {
if(strcasecmp($form_secret,$_SESSION['FORM_SECRET'])===0) {
/*We need to have the form submission code here
*/
unset($_SESSION['FORM_SECRET']);
}else {
//this is the condition of key which is invalid
}
}else {
//this is the case where is secret key is no there
echo 'Form data has been processed once!';
}
?>

JavaScript

Java Script uses two methods to prevent Duplicate form submissions .The first method disables the button and the second method uses cookies to prevent multiple submissions.

Eg:

a.
<SCRIPT LANGUAGE="JavaScript" TYPE="text/JavaScript"> 
 function formControl(submitted)
 { if(submitted=="1") 
 { commentForm.Submit.disabled=true alert
("Thanks for your comments!") } } </SCRIPT>

The JavaScript event that fires this script is the onClick event. You're telling it to disable the SUBMIT button after the button is clicked. At that point, the button value equals "1." We included a JavaScript ALERT box in this example so that you'll know the form is working. To tie this JavaScript to our form, we change the form's SUBMIT button to call the JavaScript function. This is done by adding an onClick event to the appropriate INPUT tag, as shown below.

<FORM action="" method="post" enctype="text/plain" name="commentForm"> 
 How did you find our site? <input type="text" maxlength="30" size="20"><br><br> 
 <input type="submit" name="Submit" value="Send Comments and Get a Cookie" onClick="formControl(1)">
 </FORM>

The problem with this approach is only Internet explorer supports the disable option ,so it is browser dependent.

Below is another approach in java script using cookies in browser

b.

The Below code has to be inserted into head section of our code 

<script> <!-- // give feedback to the respondent about the state of their submission function AllowNoDups() 
{ var cookie_ls = document.cookie; 
 if (cookie_ls.indexOf(document.location) > -1) 
 { alert("You've already submitted your answers. Thank you for your interest! ");
 return false; } 
 else { document.cookie = window.location.href + " from " + document.referrer + "; 
path=/; 
expires=Thu, 23-Aug-2012 00:00:01 GMT;"; return true; }; }; //--> </script>   

When a user submits the form, the function sets an identifying cookie. Before the form processes, the function checks to see if there is already a cookie set. If not, the form handles the submission as normal. If there is a cookie, the user gets an alert box that says "You've already submitted your answers" and the form submission is blocked. This is a good, cross-browser way to avoid duplicate submissions, but we have to be careful that user's browser must accept cookies and have JavaScript turned on for this method to work.

ASP.NET

Here is an implementation in ASP that prevents duplicate form submission. In this implementation, we consider two ways of avoiding this problem. One is the Unsafe button method, where the button does not vanish after the user clicks the button. But at the same time does not consider two or more consecutive clicks, if done within a specified period of time, as a different request from the orginal one. The other method is the safe button method where the button vanishes after the button is clicked once and returns only after the request has been completed or timed out.

<%@ Page Language="VB" %>
<script runat="server">
Dim counter As Integer
Sub Page_Load()
If Session("counter") Is Nothing Then
counter = 0
Else
counter = CInt( Session("counter") )
End If
If IsPostBack Then
counter += 1
Session("counter") = counter
End If
Label1.Text = "Counter = " & counter.ToString()
SafeButton.Attributes.Add("onclick", "disableButton();return true")
End Sub

Sub Process_Click(sender As Object, e As EventArgs)
Dim i As Long
' 2-second delay
Dim endTime As DateTime = DateTime.Now.AddSeconds(2)
While DateTime.Now < endTime
End While
Label2.Text = "Finished @ " & DateTime.Now.ToString()
End Sub

Sub Button2_Click(sender As Object, e As EventArgs)
Session("counter") = 0
Label1.Text = "Counter = " & Session("counter").ToString()
End Sub

</script>
<html>
<head>
<script language="javascript">
function disableButton()
{
document.form1.SafeButton.style.visibility = "hidden";
}
</script>
</head>
<body>
<form runat="server" id="form1">
<p>
<asp:Label id="Label1" runat="server">Label</asp:Label>
</p>
<p>
<asp:Label id="Label2" runat="server">Label</asp:Label>
</p>
<p>
<asp:Button id="UnsafeButton" onclick="Process_Click" runat="server" Text="Unsafe Button"></asp:Button>
</p>
<p>
<asp:Button id="SafeButton" onclick="Process_Click" runat="server" Text="Safe Button"></asp:Button>
</p>
<p>
 <asp:Button id="Button2" onclick="Button2_Click" runat="server" Text="Clear Counter"></asp:Button>
</p>
<span id="span1"></span>
</form>
</body>
</html>

As explained earlier, the Unsafe button allows users to click on submit multiple times. But clicking on the button again doesnt do anything, a two-second delay has been put into the button handler to emulate the time that a back-end process might take. So unless that time is up it doesnt effective count as a click.

Even for the Safe button the same process runs. But in this case as soon as the button is clicked, it disappears, preventing you from being able to click it again until the processing (postback) is complete. This ensures that multiple form request is not sent by completely taking out the submit button.

Ruby On Rails

Ruby on Rails uses the Submit button disabling method to prevent Multiple form submissions .It uses the submit_tag option .Below is the code that demonstrate this

submit_tag "Submit", :disable_with => "Saving..."

This adds behavior to the submit button to disable it once clicked, and to display "Saving..." instead of "Submit".

Conclusion

As illustrated in the above wiki, various languages handle the problem of multiple form submission either by client side or server side methods. Server side handling is more reliable as some web browsers do not support the disabling of button feature. Synchronizer token pattern is best solution as illustrated in Java .In most languages, for server side handling, a token is set once on submission and compared again on resubmission to check for duplicates ,this prevents the data from being inserted again into the database. Some websites just pop message to user to wait until action is completed. Overall Java script is best way to handle duplicate form submission

References

Below are some of the references for further reading

Java struts solution - http://www.javaworld.com/javaworld/javatips/jw-javatip136.html?page=1

ASP .NET solution - http://forums.asp.net/t/447620.aspx?PageIndex=1

General HTML solution client side - http://drupal.org/node/69048

PHP solution -http://phpsense.com/php/prevent-duplicate-form-submission.html

ASP.NET solution - http://forums.techarena.in/software-development/1149257.htm

Ruby on rails solution - http://www.stackoverflow.com

Struts solution http://www.sbouchard.com/2009/01/19/synchronizer-token-pattern-in-struts