• No results found

How do you prevent multiple submits due to repeated “refresh button” clicks?

View Helper Pattern

A 27: As shown in Q9 in Enterprise section, writing out.println (…) statements using servlet is cumbersome and hard to maintain, especially if you need to send a long HTML page with little dynamic code content Worse still, every

Q. How do you prevent multiple submits due to repeated “refresh button” clicks?

Problem: Very often a user is completely unaware that a browser resends information to the server when a

“refresh button” in Microsoft Internet Explorer or a “reload button” in Netscape/Mozilla is clicked. Even if a browser warns user, a user cannot often understand the technical meaning of the warning. This action can cause form data to be resubmitted, possibly with unexpected results such as duplicate/multiple purchases of a same item, attempting to delete the previously deleted item from the database resulting in a SQLException being thrown.

Non-idempotent methods are methods that cause the state to change. But some operations like reading a list of

products or customer details etc are safe because they do not alter the state of the model and the database. These methods are known as idempotent methods.

Solution-1: You can use a Post/Redirect/Get (aka PRG) pattern. This pattern involves the following steps:

Step-1: First a user filled form is submitted to the server (i.e. a Servlet) using a “POST” (also a “GET” method). Servlet performs a business operation by updating the state in the database and the business model.

Step-2: Servlet replies with redirect response (i.e. sendRedirect() operation as opposed to the forward() operation) for a view page.

Step-3: Browser loads a view using a “GET” where no user data is sent. This is usually a separate JSP page, which is safe from “multiple submits”. For e.g. reading data from a database, a confirmation page etc.

internet

Post/Redirect/Get pattern to prevent multiple submits due to clicking “refresh button”

<!-- simple JSP Page --> <html>

<title>Thanks for your purchase</title> <h1>Thanks for your purchase</h1> <body> </body>

</html>

Client Application Server

on host “localhost” port:8080 Http response

Http request

public class PurchaseServlet extends HttpServlet {

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

//code to update database and model through a business delegate // & data acess logic classes. Not safe to be repeated unintentionally .

//note that sendRedirect requires an absolute path

resp.sendRedirect("http://localhost:8080/myWebCtxt/display.jsp"); } } <%@page contentType="text/html" %> <!-- simple JSP Page --> <html>

<title>Thanks for your purchase</title> <h1>Thanks for your purchase</h1> <body></body> </html> 4. response Client tier Prese ntatio n Tier

Address bar: http://localhost:8080/ myWebCtxt/display.jsp

2. re direct

Above URL is displayed on the address bar. So repeated “refresh button” clicks calls the display.jsp page, which is safe to do so since it does not change any state. If you forward to display.jsp instead of redirect then URL “http:// localhost:8080/myWebCtxt/purchase.do” is displayed on the address bar and repeated “refresh button” clicks can result in duplicate purchase of the same item.

3. new

(GET)request

Note: If you forward the request from the “PurchaseServlet” to the “display.jsp” instead of the redirect as shown in this diagram, then the URL “http://localhost:8080/myWebCtxt/purchase.do” is displayed on the address bar and repeated “refresh button” clicks can result in duplicate purchase of the same item.

PurchaseServlet.class

Display.jsp html sent to browser from JSP

A link is clicked using the above URL to request for a purchase order form. Address bar: http://localhost:8080/myWebCtxt/

requestForAPurchaseForm.do public class RequestForAPurchaseFormServlet extends HttpServlet { //...

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.getRequestDispatcher("/requestForAPurchaseForm.jsp").forward(req, resp); } } RequestForAPurchaseFormServlet.class <%@page contentType="text/html" %> <html> <h1>Output to Browser</h1> <body>

<form action="/myWebCtxt/purchase.do" method="POST"> <input type="text" value="" />

<input type="submit" value="submit" /> </form> </body> </html> 2.forward() requestForAPurchaseForm.jsp <html> <title>Simple JSP Page</title> <h1>Output to Browser</h1> <body>

<form action="/myWebCtxt/purchase.do" method="POST"> <input type="text" value="" />

<input type="submit" value="submit"/> </form>

</body> </html>

html sent to browser from JSP

1. request (GET) 3. response 1. re quest (P OST) (form submit)

Address bar: http://localhost:8080/myWebCtxt/ requestForAPurchaseForm.do

Advantages: Separates the view from model updates and URLs can be bookmarked. Disadvantage: Extra network round trip.

Solution-2: The solution-1 has to make an extra network round trip. The synchronizer token pattern can be

applied in conjunction with request forward (i.e. instead of redirect) to prevent multiple form submits with unexpected side effects without the extra round trip.

Since this request is for a transactional page, which changes the state of your model and the database, you should generate a use once only token.

Generate a token: 123 (e.g. jsessionid + timestamp is more secured). Save the token: session.setAttribute(TRANSACTION_TOKEN, “123"); internet

Synchronizer Token Pattern

Client Application Server

on host “localhost” port:8080 Http response

Http request

If (tokenExistInRequest && tokenExistInSession &&

tokenStoredInRequest == tokenStoredInSession) { //123=123 so ok //1. reset the token. (i.e. set it to null or increment it to 124) // 2. proceed with database & model update

// 3. forward user to the “display.jsp” page. } else {

// 1. duplicate submit, not okay to proceed. // 2. forward user to the “error.jsp” page. } Client Tier HTML, CSS, JavaScript and images Presentation Tier Servlet, JSP, CSS, Javascript and images

Address bar: http://localhost:8080/myWebCtxt/ requestForAPurchaseForm.do

A link is clicked using the above URL to request for a purchase order form.

Include the token “123" as a hidden field in the requested form and send it to client.

<form action="/myWebCtxt/purchase.do" method="POST"> <input type=”hidden” name=”token” value=”123”> <input type="text" value="" />

<input type="submit" value="submit"/> </form>

1. request (GET)

3. response

PurchaseServlet.class Address bar: http://localhost:8080/

myWebCtxt/requestForAPurchaseForm.do RequestForAPurchaseFormServlet.class 2. forward requestForAPurchaseForm.jsp 1. request Submit the form

(POST)

Display or an error page is sent to the user. display.jsp or error.jsp

2. forward <html>

<title>Thanks for your purchase</title> <h1>Thanks for your purchase</h1> <body> </body>

</html>

Address bar: http://localhost:8080/ myWebCtxt/purchase.do 3. re spo nse 123 123

123 (gets reset to null or increment to 124)

Important: If the “refresh” button is clicked, then the form is resubmitted(duplicate submit) with the same form data to the “PurchaseServlet”. The “if” condition will be evaluated as false since the token in the request is “123” but the token in the session would be null or 124. So the “else” condition is evaluated and the request is forwarded to the error.jsp page. The URL address will still be “http://localhost:8080/ myWebCtxt/purchase.do” but any number of resubmits will result in “error.jsp” page. If you need to intentionally purchase the same item again, then you need to enter via the right flow of control i.e “http://localhost:8080/myWebCtxt/requestForAPurchaseForm.do” where a new token will be generated and same sequence of processing will occur but this time with a different session token.

The basic idea of this pattern is to set a use once only token in a “session”, when a form is requested and the token is stored in the form as a hidden field. When you submit the form the token in the request (i.e. due to hidden field) is compared with the token in the session. If tokens match, then reset the token in the session to null or increment it to a different value and proceed with the model & database update. If you inadvertently resubmit the form by clicking the refresh button, the request processing servlet (i.e. PurchaseServlet) first tests for the presence of a valid token in the request parameter by comparing it with the one stored in the session. Since the token was reset in the first submit, the token in the request (i.e 123) would not match with the token in the session (i.e. null or 124). Since the tokens do not match, an alternate course of action is taken like forwarding to an error.jsp page.

Note: Prohibit caching of application pages by inserting the following lines in your pages: <meta HTTP-EQUIV=”pragma” content=”no-cache” />

Outline

Related documents