Vous êtes sur la page 1sur 3

Java Tip 136: Protect Web application

control flow
A strategy built on Struts manages duplicate form submission

Web application designers and programmers often face situations where a form
submission must be protected against a rupture in the normal control flow sequence. This
situation typically occurs when a user clicks more than once on a submit button before
the response is sent back or when a client accesses a view by returning to a previously
bookmarked page. Control flow sequence is particularly important to preserve when form
submission involves transaction processing on the server.

This article proposes a well-encapsulated solution to this problem: a strategy


implemented as an abstract class that leverages the Struts framework.

Note: You can download this article's source code from Resources.

Client vs. server solutions

Different solutions can solve this multiple form submission situation. Some transactional
sites simply warn the user to wait for a response after submitting and not to submit twice.
More sophisticated solutions involve either client scripting or server programming.

In the client-only strategy, a flag is set on the first submission, and, from then on, the
submit button is disabled based on this flag. While appropriate in some situations, this
strategy is more or less browser dependent and not as dependable as server solutions.

For a server-based solution, the Synchronizer Token pattern (from Core J2EE Patterns)
can be applied, which requires minimal contribution from the client side. 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.
This Synchronizer Token pattern is used in the Apache Jakarta Project's Struts
framework, the popular open source Model-View-Controller implementation.

A synchronized action

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

Personal
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. The original perform
method dispatches control according to the synchronization status, as shown in the listing
below:

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)) {
// Reset token and session attributes
reset(request);
try {
// Perform the action and store the results
forward = performSynchro(mapping, form, request, response);
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) {
// Store and rethrow the exception
session.setAttribute(EXCEPTION_KEY, e);
session.setAttribute(COMPLETE_KEY, "true");
throw e;
} catch (ServletException e) {
// Store and rethrow the exception
session.setAttribute(EXCEPTION_KEY, e);
session.setAttribute(COMPLETE_KEY, "true");
throw e;
}
} else {
// If the action is complete
if ("true".equals(session.getAttribute(COMPLETE_KEY))) {
// Obtain the exception from the session
Exception e = (Exception)
session.getAttribute(EXCEPTION_KEY);
// If it is not null, throw it
if (e != null) {

Personal
if (e instanceof IOException) {
throw (IOException) e;
} else if (e instanceof ServletException) {
throw (ServletException) e;
}
}
// Obtain the form from the session
ActionForm f = (ActionForm) session.getAttribute(FORM_KEY);
// Set it in the appropriate context
if ("request".equals(mapping.getScope())) {
request.setAttribute(mapping.getAttribute(), f);
} else {
session.setAttribute(mapping.getAttribute(), f);
}
// Obtain and save the errors from the session
saveErrors(request, (ActionErrors)
session.getAttribute(ERRORS_KEY));
// Obtain the forward from the session
forward = (ActionForward) session.getAttribute(FORWARD_KEY);
} else {
// Perform the appropriate action in case of token error
forward = performInvalidToken(mapping, form, request,
response);
}
}
return forward;
}

As you see above, the protected action is performed only once, that is, if the token is
valid. If other requests are received while the action is running, they are directed to the
performInvalidToken() method's result until the action completes. By default, this
method simply returns an ActionForward named "synchroError". This forward should
lead to a page signaling that the action is in progress and providing a button to continue.
This button simply resubmits to the same action without any form or parameter in the
request (they will not be considered anyway). 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.

I include an example in the source code to demonstrate the behavior of a simple


synchronized action. The provided implementation is based on Struts 1.0.2, but can easily
be adapted for release 1.1.

Preserve consistent flow control

Multiple form submissions may cause inconsistency in transactions and must be


avoided. For that purpose, the Synchronizer Token pattern is a great help. This article's
proposed strategy nicely complements this pattern in recovering the response to the
original request and, as such, helps preserve a consistent flow of control in Web
applications.

Personal

Vous aimerez peut-être aussi