How to do cross-domain GWT RPC with a ProxyServlet
Last week, I started working on a new project using GWT. On my last project, we used GWT HTTP Calls and my new project is using RPC. We'll likely migrate to a JSON backend eventually, but in the meantime, I wanted to be able to develop in hosted mode (localhost:8888) and call services on another host (localhost:8080), where the services are running in a JSF/Spring webapp.
At first, I thought it'd be easy thanks to the handy-dandy ProxyServlet I mentioned in Implementing OAuth with GWT. However, when I tried to hook it in and use it, I saw the following error in my server-side logs.
java.lang.NullPointerException
at javax.servlet.GenericServlet.getServletName(GenericServlet.java:322)
at javax.servlet.GenericServlet.log(GenericServlet.java:277)
at com.google.gwt.user.server.rpc.RemoteServiceServlet.doGetSerializationPolicy(RemoteServiceServlet.java:219)
at com.google.gwt.user.server.rpc.RemoteServiceServlet.getSerializationPolicy(RemoteServiceServlet.java:117)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader.prepareToRead(ServerSerializationStreamReader.java:429)
at com.google.gwt.user.server.rpc.RPC.decodeRequest(RPC.java:234)
Looking at RemoteServiceServlet.java:219, there's a logging call that fails for some reason (at least in my application).
/*
* Check that the module path must be in the same web app as the servlet
* itself. If you need to implement a scheme different than this, override
* this method.
*/
if (modulePath == null || !modulePath.startsWith(contextPath)) {
String message = "ERROR: The module path requested, "
+ modulePath
+ ", is not in the same web application as this servlet, "
+ contextPath
+ ". Your module may not be properly configured or your client and server code maybe out of date.";
log(message, null);
}
In the above code, you might notice that GWT is checking to make sure the client is hosted in the same application as the server. After I figured this out, it was pretty easy to modify my ProxyServlet to trick GWT RPC into thinking the client was in the same web application. In the ProxyServlet's handleContentPost method, I added the following code to replace "localhost:8888/" with "localhost:8080/services/" (in the content of the post to the server).
if (contentType.startsWith("text/x-gwt-rpc")) {
String clientHost = httpServletRequest.getLocalName();
if (clientHost.equals("127.0.0.1")) {
clientHost = "localhost";
}
int clientPort = httpServletRequest.getLocalPort();
String clientUrl = clientHost + ((clientPort != 80) ? ":" +
clientPort : "");
String serverUrl = stringProxyHost + ((intProxyPort != 80) ? ":" +
intProxyPort : "") + httpServletRequest.getServletPath();
postContent = postContent.replace(clientUrl , serverUrl);
}
After manipulating the posted content, I was successfully able to use GWT RPC cross-domain.
Woo hoo!
For your convenience, the full handleContentPost() method is listed below.
private void handleContentPost(PostMethod postMethodProxyRequest,
HttpServletRequest httpServletRequest)
throws IOException, ServletException {
StringBuilder content = new StringBuilder();
BufferedReader reader = httpServletRequest.getReader();
for (;;) {
String line = reader.readLine();
if (line == null) break;
content.append(line);
}
String contentType = httpServletRequest.getContentType();
String postContent = content.toString();
if (contentType.startsWith("text/x-gwt-rpc")) {
String clientHost = httpServletRequest.getLocalName();
if (clientHost.equals("127.0.0.1")) {
clientHost = "localhost";
}
int clientPort = httpServletRequest.getLocalPort();
String clientUrl = clientHost + ((clientPort != 80) ? ":" +
clientPort : "");
String serverUrl = stringProxyHost + ((intProxyPort != 80) ? ":" +
intProxyPort : "") + httpServletRequest.getServletPath();
postContent = postContent.replace(clientUrl , serverUrl);
}
String encoding = httpServletRequest.getCharacterEncoding();
debug("POST Content Type: " + contentType + " Encoding: " + encoding,
"Content: " + postContent);
StringRequestEntity entity;
try {
entity = new StringRequestEntity(postContent, contentType, encoding);
} catch (UnsupportedEncodingException e) {
throw new ServletException(e);
}
// Set the proxy request POST data
postMethodProxyRequest.setRequestEntity(entity);
}
Update: In the comments, Ganesh asked for more details, so I figured it'd be a good idea to post the full source code. First of all, click here to see the code for the ProxyServlet:
I generally subclass ProxyServlet to provide my own configuration:
public class MyProxyServlet extends ProxyServlet {
@Override
public void init(ServletConfig servletConfig) {
setFollowRedirects(true);
setRemovePrefix(false);
setProxyPort(8080);
}
}
Here's another example that reads configuration settings from web.xml and proxies to a different domain name:
public class AlternateHostProxyServlet extends ProxyServlet {
@Override
public void init(ServletConfig servletConfig) {
setProxyHost(servletConfig.getInitParameter("proxyHost"));
String secure = servletConfig.getInitParameter("secure");
if (secure != null) {
setSecure(Boolean.valueOf(secure));
}
setFollowRedirects(false);
setRemovePrefix(true);
setProxyPort(80);
}
}
After you've added these to your project, simply map the servlet (and its path) in your *.gwt.xml file (if you're using GWT) and your web.xml.



















































