You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 2 Next »

Today I struggled with more redirect related issues as I continued rewriting our Facebook app with Wicket - I'll note my observations here (keep in mind that I am a first-timer with Wicket).

Caveats!

  • The newWebResponse() implementation below ignores the getRequestCycleSettings().getBufferResponse() value, which the original implementation takes into consideration.
  • If you embed iframes to your fbml pages with the Fb:iframe tag that point to the app's callback url, you will want to "enable" the normal redirects for those requests.

Problem explained

When I submit one of my forms, my access log reveals that Wicket follows the "redirect after post" pattern.

"POST /appcontext/somepage?wicket:interface=:0:somepanel:someform::IFormSubmitListener:: HTTP/1.1" 200
"POST /appcontext/somepage?wicket:interface=:1:::: HTTP/1.1" 200

In the context of a Facebook FBML application, the second access log entry never appears, and the url in the browser remains as follows:

http://apps.facebook.com/myapp/somepage?wicket:interface=:0:somepanel:someform::IFormSubmitListener::

which seems to represent some expired state of the page. If one does a refresh on the page, the browser's "repost popup" appears, and if one chooses to resubmit the form, Wicket responds with a 404 - Page expired.

This is by Facebook's design and the redirect issued by Wicket after the form post is simply discarded and the browser is never made aware of it. Access log looks like this:

"POST /appcontext/somepage?wicket:interface=:0:somepanel:someform::IFormSubmitListener:: HTTP/1.1" 200

Solution explained

Facebook provides a custom tag to accomplish a redirect within the Facebook canvas - Fb:redirect (http://wiki.developers.facebook.com/index.php/Fb:redirect).

The trick is to make Wicket use the Fb:redirect tag instead of doing the "normal" redirect.

org.apache.wicket.protocol.http.WebResponse

If you check out the source for org.apache.wicket.protocol.http.WebResponse, you'll find a redirect() method that contains the following line:

httpServletResponse.sendRedirect(url);

This is what we wish to avoid, and instead output the Fb:redirect tag into the response body:.

FBWebResponse

Let's override the redirect() method:

import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import org.apache.wicket.protocol.http.WebResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FBWebResponse extends WebResponse
{
    private static final Logger log = LoggerFactory.getLogger(FBWebResponse.class);

    private final HttpServletResponse httpServletResponse;

    public FBWebResponse(HttpServletResponse response)
    {
        super(response);
        this.httpServletResponse = response;
    }

    @Override
    public void redirect(String url)
    {
        if (!redirect)
        {
            if (httpServletResponse != null)
            {
                // encode to make sure no caller forgot this
                url = httpServletResponse.encodeRedirectURL(url);
                try
                {
                    if (httpServletResponse.isCommitted())
                    {
                        log.error("Unable to redirect to: " + url +
                                ", HTTP Response has already been committed.");
                    }

                    if (log.isDebugEnabled())
                    {
                        log.debug("Redirecting to " + url);
                    }

                    if (isAjax())
                    {
                        /*
                         * By reaching this point, make sure the HTTP response status code is set to
                         * 200, otherwise wicket-ajax.js will not process the Ajax-Location header
                         */
                        httpServletResponse.addHeader("Ajax-Location", url);

                        // safari chokes on empty response. but perhaps this is
                        // not the best place?
                        httpServletResponse.getWriter().write(" ");
                    }
                    else
                    {
                        // httpServletResponse.sendRedirect(url);
                        httpServletResponse.getWriter().write("<fb:redirect url=\"" + url + "\" />");
                    }
                    redirect = true;
                }
                catch (IOException e)
                {
                    log.warn("redirect to " + url + " failed: " + e.getMessage());
                }
            }
        }
        else
        {
            log.info("Already redirecting to an url current one ignored: " + url);
        }
    }
}

The redirect() method was a copied from WebResponse, and the following change was applied:

// httpServletResponse.sendRedirect(url);
httpServletResponse.getWriter().write("<fb:redirect url=\"" + url + "\" />");

Make use of FBWebResponse in your application

In your application class, override the newWebResponse() method:

@Override
protected WebResponse newWebResponse(HttpServletResponse servletResponse)
{
    return new FBWebResponse(servletResponse);
}

You should be set!

  • No labels