Filter to block duplicate POST submission on server side I am working on a JSF 1.1 webapp. Lots of POST request take some time and users have the bad habit of clicking again when it takes too long. I am planning to target all the long running processes and add an overlay to prevent this, but it will take some time. Before it is finished, I am asked to setup a quickwin on server side do block double submission of the same POST request.

My comprehension of the mechanisms is that when the users clicks on the submit button a second time, the browser aborts the first post submission (as I can see it in Firebug), but on server side both are processed in parallel. So in order to avoid to process two times the same request while sending the expected response to the client, I am planning to set up a Filter on incoming POST request with the following process:

Suppose someone sends for example 3 exact same POST requests Rq1, Rq2, Rq3 with the corresponding responses being Rs1, Rs2, Rs3. We want to block Rq2 and Rq3 while Rq1 is processing. When Rs1 is ready we will block everything while we are copying Rs1 into Rs3. Then we send Rs3 back to the client and once this is done, we terminate Rs1 and Rs2 by sending back a 200 (they should have already been aborted on client side anyway)

Here is the implementation I thought about. The synchronization stuff has purposely been skipped as the filter is quite heavy already. I need reviews about this because I really feel like having imagined a really dirty thing.

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
        ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;
    HttpSession session = req.getSession(false);
    if (session == null || notAPost(request)) {
        chain.doFilter(request, response);
        return;
    }

    Map&lt;String, String&gt; hashTokenMap = (Map&lt;String, String&gt;) session.getAttribute("HASH_TOKEN_MAP");
    Map&lt;String, HttpServletResponse&gt; hashResponseMap = (Map&lt;String, HttpServletResponse&gt;) session
            .getAttribute("HASHS_RESPONSE_MAP");
    String requestHash = makeCheckSumString(request);
    String token = generateUniqueToken();

    // double POST case
    if (hashTokenMap.containsKey(requestHash)) {
        hashTokenMap.put(requestHash, token); // this will allow the previous POST to understand that it has been overriden
        // wait until we detect that either the response is available or has already been processed
        while (hashResponseMap.get(requestHash) == null &amp;&amp; hashTokenMap.get(requestHash) != null) {
            sleep(1000);
        }

        // this is the last submitted post =&gt; we copy the response and return it
        if (token.equals(hashTokenMap.get(requestHash))) {
            HttpServletResponse realResponse = hashResponseMap.remove(requestHash);
            copyResponses(realResponse, res);
            hashTokenMap.remove(requestHash);
            return;
        }
        // there was another post submitted inbetween
        else {
            // since we have been overriden, lets wait till other business is finished
            while (hashTokenMap.get(requestHash) != null) {
                sleep(1000);
            }
            res.sendError(HttpServletResponse.SC_OK, "Already replied");
            return;
        }
    }
    // first POST case
    else {
        hashTokenMap.put(requestHash, token);
        chain.doFilter(request, response);

        // there was no double POST
        if (token.equals(hashTokenMap.get(requestHash))) {
            return;
        }
        // some other POST has been submitted during processing
        else {
            hashResponseMap.put(requestHash, res);
            while (hashResponseMap.get(requestHash) != null) {
                sleep(1000);
            }
            res.sendError(HttpServletResponse.SC_OK, "Already replied");
            return;
        }
    }
}
Just some suggestions:

1.Change you UI concept. 


Hide Submit button until the server responds.
Show Please Wait notification / dialog


2.Use JavaScript to send request and prevent multiple submits:

if(ignoreSubmitEvent){
    return;
}

hideSubmitButton();
ignoreSubmitEvent = true;

var jqxhr = $.post( "/url...", { submittoken: "unique-one-time-token", name: "dit" });

jqxhr.done(function( data ) {
     [...] // your code 
});

jqxhr.fail(function() {
     [...] // your code 
});

// DO IT ALLWAYS
jqxhr.always(function() {
    showSubmitButton();
    ignoreSubmitEvent = false;
});


3.It could be a good idea to use HandlerInterceptor instead of Filter

public class OneTimeTokenPostInterceptor extends HandlerInterceptorAdapter {

    private static final String UNIQUE_ONE_TIME_TOKEN = "unique-one-time-token";
    private static final Logger logger                = LoggerFactory.getLogger(OneTimeTokenPostInterceptor.class);

    private Set&lt;String&gt;         tokens                = Collections.synchronizedSet(new HashSet&lt;String&gt;());

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String token = request.getParameter(UNIQUE_ONE_TIME_TOKEN);

        logger.debug("preHandle: token = {}", token);

        if (token == null || tokens.contains(token)) {
            logger.error("token {} is null or allready used", token);
            response.sendError(202); // ignore request
            return false;
        }
        tokens.add(token);
        return true;
    }

    //
    // GET CALLED AFTER CONTROLLER DID HIS JOB
    //

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        String token = request.getParameter(UNIQUE_ONE_TIME_TOKEN);

        logger.debug("postHandle: token = {}", token);

        if (token != null) {
            tokens.remove(token);
        }
    }
}