Saturday, August 22, 2009

GWT RPC - How Server-side JSP Service Works

Continued from Passing Parameters in GWT RPC (Server-side)


Remember that the name of any JSP that is registered as a GWT RPC service responder must end with suffix ".gwtrpc.jsp".

Here's the reason why.

A JSP is first translated into a Java source file with a _jspService method, which always begins with this block of code
public void _jspService(
HttpServletRequest request,
HttpServletResponse response)
throws java.io.IOException, ServletException {

JspFactory _jspxFactory = null;
  PageContext pageContext = null;
  HttpSession session = null;
  ServletContext application = null;
  ServletConfig config = null;
  JspWriter out = null;
  Object page = this;
  JspWriter _jspx_out = null;
  PageContext _jspx_page_context = null;

  try {
    _jspxFactory = JspFactory.getDefaultFactory();
    response.setContentType("text/html");
    pageContext =
    _jspxFactory.getPageContext(
    this, request, response,
    null, true, 8192, true);
    _jspx_page_context = pageContext;
    application = pageContext.getServletContext();
    config = pageContext.getServletConfig();
    session = pageContext.getSession();
    out = pageContext.getOut();
    _jspx_out = out;

    ....

However, the RemoteServiceServlet class, which a GWT RPC service responder servlet should extend (in order to exploit the convenience of data serialization afforded by implementng GWT RPC interfaces), uses its own output stream and ignores/discards the response output stream. That is, the JspWriter out stream is ignored/discarded. Which causes the output of a JSP extending RemoteServiceServlet to be blank, because all the content rendering of a JSP is translated into lines of ignored output stream

out.write( .... );

Therefore, we need a way to exploit the out.write lines generated by the JSP translator, by diverting out back to the response body. So we create a class JspResponseWriter, which extends JspWriter, to over-ride the write method, so that it writes to the response body.
package org.synthful.gwt.http.servlet.server;
....
public class JspResponseWriter
 extends JspWriter
{
  ....



@Override public void write(char[] cbuf, int off, int len) throws IOException{ for (int i=off; i<off+len; i++) this.Body.append(cbuf[i]); }
.... }

Alright, now we the mice have the bell, we only need find a ribbon to tie it to the neck of the pussycat. Even with a ribbon, we still need to sneak up to the cat tie the ribbon with the bell around the cat.

Okay, notice in the translated JSP Java source is the line

out = pageContext.getOut();

So we create an extension of PageContext whose getOut method would sneak our version of JspWriter to the out variable.

And, the plot thickens. Now, we have to find a way to sneak our version of PageContext into the conspiracy to defraud Google's blindfolding of the JSP out prisoner.

Notice the translated Java source has a line
pageContext = _jspxFactory.getPageContext(....);

And your premonition is correct - we have to send in a double-agent to sneak our impersonator of the JspFactory, who would help us sneak our version of PageContext into the belly of RemoteServiceServlet.

So, here's the double-agent, JspServiceBeanable, who would undertake this mission. Notice the line JspFactory.setDefaultFactory( .... ), which is how our agent sneaks our version of JspFactory, which would sneak our version of PageContext, which would sneak our version of JspWriter, which would sneak GWT server-side prisoner, the JSP rendering output, back to the response body where it rightly belongs:
package org.synthful.gwt.http.servlet.server;
...
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

abstract public class JspServiceBeanable
  extends RemoteServiceServlet
  implements HttpJspPage
{
....

protected String doJspService( HashMap<String, String> parameters){ this.parameters = new Hashtable<String, String>(parameters); JspFactory.setDefaultFactory(new JspFactoryShunt()); this.jspFactory = JspFactory.getDefaultFactory(); try{ this._jspService( this.getThreadLocalRequest(), this.getThreadLocalResponse()); } catch (Exception e){ e.printStackTrace(); } this.jspContext.popBody(); return this.jspOut.Body.toString(); }
public class JspFactoryShunt extends JspFactory { .... @Override public PageContext getPageContext( Servlet arg0, ServletRequest arg1, ServletResponse arg2, String arg3, boolean arg4, int arg5, boolean arg6) { Class cls = arg0.getClass(); this.shuntedPageContext = this.shuntedJspFactory.getPageContext( arg0, arg1, arg2, arg3, arg4, arg5, arg6); if (!cls.getName().contains("_gwtrpc_jsp")) return this.shuntedPageContext; jspContext = new PageContextShunt(this.shuntedPageContext); return jspContext; } .... }
public class PageContextShunt extends PageContext { public PageContextShunt(PageContext ctx) { this.shuntedPageContext = ctx; } .... }
}



But, but, but ...,
JspFactory.setDefaultFactory and
JspFactory.getDefaultFactory
are static methods, and affects the whole application, not just a request, so that our version of JspFactory is thrusted into the throats of even JSPs who are not involved in this conspiracy.

We need to a way for our JspFactory impersonator to differentiate between visitors and prisoners, between JSPs not part of the crime from JSPs intent on committing this fraud. Otherwise, the output of the non-complicit JSPs would not be processed as intended thro the normal response out stream.

That is where the suffix, .gwtrpc.jsp comes in. Notice the code in the JspFactoryShunt, which returns the original shunted PageContext for JSPs whose names are without .gwtrpc.jsp.

if (!cls.getName().contains("_gwtrpc_jsp"))
  return this.shuntedPageContext;




Continue next Why Use JSP for GWT RPC Server-side Service

6 comments: