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.
No Format |
---|
"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:
No Format |
---|
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 - the redirect issued by Wicket after the form post is simply discarded and the browser is never made aware of it.
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:
Code Block |
---|
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:
Code Block |
---|
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:
Code Block |
---|
// 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:
Code Block |
---|
@Override protected WebResponse newWebResponse(HttpServletResponse servletResponse) { return new FBWebResponse(servletResponse); } |
You should be set!
A big thank you goes to jwcarman on ##wicket for helpful pointers.