Lost In Redirection With Apache Wicket

Quite a few teams have already got stuck into the following problem when working with wicket forms in a clustered environment while having 2 (or more) tomcat server with enabled session replication running.

In case of invalid data being submitted with a form instance for example, it seemed like according error messages wouldn’t be presented when the same form page gets displayed again. Sometimes! And sometimes they would! One of those nightmares of rather deterministic programmer’s life. This so called Lost In Redirection problem, even if it looks like a wicket bug at first, is rather a result of a default setting in wicket regarding the processing of form submissions in general. In order to prevent another wide known problem of double form submissions, Wicket uses a so called REDIRECT_TO_BUFFER strategy for dealing with rendering a page after web form’s processing (@see IRequestCycleSettings#RenderStrategy).

What does the default RenderStrategy actually do?

Both logical parts of a single HTTP request, an action and a render part get processed within the same request, but instead of streaming the render result to the browser directly, the result is cached on the server first.

Default Form Processing in Wicket

Wicket will create an according BufferedHttpServletResponse instance that will be used to cache the resulting HttpServletResponse within the WebApplication.

WebApplication Caching Buffered Responses

After the buffered response is cached the HTTP status code of 302 get’s provided back to the browser resulting in an additional GET request to the redirect URL (which Wicket sets to the URL of the Form itself). There is a special handling code for this case in the WicketFilter instance that then looks up a Map of buffered responses within the WebApplication accordingly. If an appropriate already cached response for the current request is found, it get’s streamed back to the browser immediately. No additional form processing happens now. The following is a code snippet taken from WicketFilter:

			// Are we using REDIRECT_TO_BUFFER?
			if (webApplication.getRequestCycleSettings().getRenderStrategy() == IRequestCycleSettings.REDIRECT_TO_BUFFER)
			{
				// Try to see if there is a redirect stored
				// try get an existing session
				ISessionStore sessionStore = webApplication.getSessionStore();
				String sessionId = sessionStore.getSessionId(request, false);
				if (sessionId != null)
				{
					BufferedHttpServletResponse bufferedResponse = null;
					String queryString = servletRequest.getQueryString();
					// look for buffered response
					if (!Strings.isEmpty(queryString))
					{
						bufferedResponse = webApplication.popBufferedResponse(sessionId,
							queryString);
					}
					else
					{
						bufferedResponse = webApplication.popBufferedResponse(sessionId,
							relativePath);
					}
					// if a buffered response was found
					if (bufferedResponse != null)
					{
						bufferedResponse.writeTo(servletResponse);
						// redirect responses are ignored for the request
						// logger...
						return true;
					}
				}
			}

So what happens in case you have 2 server running your application with session replication and load balancing turned on while using the default RenderStrategy described above?

Since a Map of buffered responses is cached within a WebApplication instance that does not get replicated between the nodes obviously, a redirect request that is suppose to pick up the previously cached response (having possibly form violation messages inside) potentially get’s directed to the second node in your cluster by the load balancer. The second node does not have any responses already prepared and cached for your user. The node therefore handles the request as a completely new request for the same form page and displays a fresh new form page instance to the user accordingly.

Clustering Without Sticky Sessions

Unfortunately, there is currently no ideal solution to the problem described above. The default RenderStrategy used by Apache Wicket simply does not work well in a fully clustered environment with load balancing and session replication turned on. One possibility is to change the default render strategy for your application to a so called ONE_PASS_RENDER RenderStrategy which is the more suitable option to use when you want to do sophisticated (non-sticky session) clustering. This is easily done in the init method of your own subclass of Wicket’s WebApplication :

    @Override
    protected void init() {
        getRequestCycleSettings().setRenderStrategy(
            IRequestCycleSettings.ONE_PASS_RENDER);
    }

ONE_PASS_RENDER RenderStrategy does not solve the double submit problem though! So this way you’d only be trading one problem for another one actually.

You could of course turn on the session stickiness between your load balancer (apache server) and your tomcat server additionally to the session replication which would be the preferred solution in my opinion.

Clustering With Sticky Sessions Turned On

Session replication would still provide you with failover in case one of the tomcat server dies for whatever reason and sticky sessions would ensure that the Lost In Redirection problem does not occur any more.

Advertisements

4 thoughts on “Lost In Redirection With Apache Wicket

  1. Ihor P.

    Hi Daniel

    Nice article, thx!
    I’m currently setting up cluster with Wicket on board.Sticky session works fine there but when I change balancing strategy from sticky session to round-robin (to test a scenario that every request will be settled down on diff node) I faced a problem with redirection in Wicket, which you nicely described here. After checking of Wicket sources I found that REDIRECT_TO_BUFFER didn’t fit for my test scenarios. Replacing to ONE_PASS_RENDER helped me in most cases but not in all…
    But I have another question – do you have some experience with configuration of tomcat6+Wicket+cluster? Now I’m suffering with problem that session ID is prefixed by worker name (like “.serverA”) and seems to be Wicket can’t work properly with combination of org.apache.catalina.ha.session.JvmRouteBinderValve (looks like page can’t be found in replicated pageMap). Maybe you tried that and could give some advise…

    Thank you in advance.

    Reply
  2. Hardy Henneberg

    Hi Daniel,

    I have a related problem.
    I’m running my wicket app on a Jboss server on port 8080.
    In front I have an Apache webserver running on port 80 which forwards to port 8080 – using mod-rewrite.
    The problem is that Wicket makes a redirect to port 8080, which should not be available from outside.
    To get rid of the redirection – I tried :
    @Override
    protected void init() {
    super.init();
    getRequestCycleSettings().setRenderStrategy(
    IRequestCycleSettings.RenderStrategy.ONE_PASS_RENDER);

    }
    , but gets an ‘Internal error’

    I tried to access it from telnet-client, and got this:
    hh@hh-NC212AA-UUW-a6758sc:~/Ubuntu One/eclipse/wicket_homepage/target$ telnet 192.34.56.108 8080Trying 192.34.56.108…
    Connected to 192.34.56.108.
    Escape character is ‘^]’.
    GET /wicket_homepage-1.0-SNAPSHOT/ HTTP/1.0

    HTTP/1.1 500 Internal Server Error
    Server: Apache-Coyote/1.1
    Date: Thu, 11 Apr 2013 08:07:19 GMT
    Expires: Thu, 01 Jan 1970 00:00:00 GMT
    Pragma: no-cache
    Cache-Control: no-cache, no-store
    Content-Type: text/html;charset=UTF-8
    Connection: close

    Without changing the render strategy, there is no problem – apart from the redirect.

    Reply
  3. Hardy Henneberg

    Found out that ‘internal server error’ was related to another thing,

    My problem was actually solved by changing the render strategy in my WicketApplication:
    @Override
    protected void init() {
    super.init();
    getRequestCycleSettings().setRenderStrategy(
    IRequestCycleSettings.RenderStrategy.ONE_PASS_RENDER);

    Thanks a lot

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s