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.

Posted in Java at Aug 05 2009, 04:06:12 PM MDT 17 Comments
Comments:

Hello Matt

I also need to make cross domain AJAX calls in my GWT application. I read your article but could not understand what changes, I need to do to make it possible. Could you please post some more details in this. Kindly excuse me if I am asking very basic question.

Thanks in advance

Ganesh

Posted by Ganesh on August 28, 2009 at 07:50 AM MDT #

Ganesh - I updated the post with ProxyServlet code and configuration instructions. Hope this helps.

Posted by Matt Raible on August 28, 2009 at 09:26 AM MDT #

In your code, is there a reason you check append a '/' to the stringProxyURL, and then check if pathInfo starts with a / ?

541.        stringProxyURL += "/";
542.         
543.        // Handle the path given to the servlet
544.        String pathInfo = httpServletRequest.getPathInfo();
545.        if (pathInfo != null && pathInfo.startsWith("/")) {
546.            if (stringProxyURL != null && stringProxyURL.endsWith("/")) {
547.                // avoid double '/'
548.                stringProxyURL += pathInfo.substring(1);
549.            }
550.        } else {
551.            stringProxyURL += httpServletRequest.getPathInfo();
552.        }

Wouldn't it be simpler to just do something like:

String pathInfo = httpServletRequest.getPathInfo();
if (pathInfo != null)
{
    stringProxyURL += pathInfo;
}

Maybe I'm missing something.

Posted by brian on November 12, 2009 at 11:49 PM MST #

The setProxyRequestCookies method contains a bug: it sets only single last cookie to proxy request. Possible fix:

 
    private void setProxyRequestCookies(HttpServletRequest httpServletRequest, HttpMethod httpMethodProxyRequest) {
        // Get an array of all of all the cookies sent by the client
        Cookie[] cookies = httpServletRequest.getCookies();
        if (cookies == null) {
            return;
        }

        String cookiesString = "";
        for (Cookie cookie : cookies) {
            cookie.setDomain(stringProxyHost);
            cookie.setPath(httpServletRequest.getServletPath());
            cookiesString += cookie.getName() + "=" + cookie.getValue() + "; Path=" + cookie.getPath() + ";";
        }
        httpMethodProxyRequest.setRequestHeader("Cookie", cookiesString);
    }

Posted by alexei on November 24, 2009 at 08:01 AM MST #

@alexie - you are correct. In fact, on my current project, I've removed this method as it doesn't seem necessary. See my update to my GWT + Spring Security entry for more information.

Posted by Matt Raible on November 24, 2009 at 08:44 AM MST #

Hey Matt,

the proxy servlet works great except a small gotcha.

We noticed that in executeProxyRequest() you modified the original code (http://edwardstx.net/wiki/Wiki.jsp?page=HttpProxyServlet) and now use:

httpServletResponse.getWriter().write(response);

to write the response.

This works ok if you content is ISO-8859-1 but breaks on UTF-8 since getWriter() returns a PrinterWriter which

which will convert characters into bytes using the default character encoding.

The original method of using an OutputStream is preferred and doesn't mangle the content:

// Send the content to the client
InputStream inputStreamProxyResponse = httpMethodProxyRequest.getResponseBodyAsStream();
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStreamProxyResponse);
OutputStream outputStreamClientResponse = httpServletResponse.getOutputStream();
int intNextByte;
 while ( ( intNextByte = bufferedInputStream.read() ) != -1 ) {
        outputStreamClientResponse.write(intNextByte);
}

Just a small note for future aspiring users of this servlet.

Posted by Rento on February 11, 2010 at 03:35 PM MST #

I've been working to get your servlet working. I'm using JSON-RPC so I'm trying to proxy the JSON-RPC requests to another domain using your servlet. The problem I'm having is that I keep loosing my session (on the destination server) between requests from this proxy servlet.

My content type is text/plain on the calls from JSON-RPC so I changed the text/x-gwt-rpc content type in handleContentPost method.

I've used tcpmon to check the JSESSIONID between requests and I can see that it is changing each time.

Any ideas why this would happen?

Posted by rcastle on February 22, 2010 at 10:48 AM MST #

Hi! Im trying to add the ProxyServlet to my appengine project, without success. I created de proxy class on the server package. Can you post a gwt.xml and web.xml example? Thanks dude!

Posted by Cleber Dantas on March 14, 2010 at 07:57 PM MDT #

@Cleber - if you can't get it to work, I'd suggesting trying to use the proxy functionality of the UrlRewriteFilter. If you read the manual and search for "proxy", you should see how to do it. I plan on moving to it as soon as it add the ability to follow redirects.

Posted by Matt Raible on March 14, 2010 at 08:50 PM MDT #

Sorry Matt, i did not explain it right. Im trying to use the proxy to integrate the GWT with SPRING on APPENGINE project. I setup with all of your instructions, but i get an exception on the HTTPPost off HTTPClient Class. So, i will try with FormAuthentication

Posted by Cleber Dantas on March 15, 2010 at 10:35 AM MDT #

Hi Matt,

I'm a bit confused. All along, I thought the Same Origin Policy (SOP) is implemented in the browser. So browser will prevent any cross domain request.

How come in your case, the request can be sent to the server (different domain)?

Who should implement the SOP, is it the server or the client (browser)?

Thanks,
Ferry

Posted by Ferry on May 14, 2010 at 12:34 AM MDT #

@Ferry - you send the request to the same server your webapp is hosted on. Then your ProxyServlet forwards the request to another server, receives the response, and sends that back to the client.

Posted by Matt Raible on May 14, 2010 at 03:22 PM MDT #

Please help me to do cross-domain rpc call without Implementing OAuth with GWT. My Client code is a GWT Web Application and the server side part is like a normal spring web application. I have splitted the client and server code into two projects like I mentioned above (i.e., Client is a GWT Web Application and Server is like normal spring dynamic project). RpcServiceImpl class will be there in server side code which is deployed in tomcat and I was trying to call that remote service from my GWTClient uisng rpc mechanism. like

MyRpcServiceAsync rpcService = (MyRpcServiceAsync) GWT.create(MyRpcService.class);
				ServiceDefTarget endpoint = (ServiceDefTarget) rpcService;
						endpoint.setServiceEntryPoint("/GWTServer/MyRpcService.do");

the above rpcservice has been deployed in tomcat like localhost:8080 and I was trying to call that rpcservice from the client i.e., localhost:8888.

How to do the above one? Please help me in this issue.

Posted by chanti on July 28, 2011 at 06:01 AM MDT #

@chanti - See the "update" section of this blog entry and click on the "click here" link to see the ProxyServlet which allows you to do this.

Posted by Matt Raible on July 28, 2011 at 06:03 AM MDT #

Hi Matt,

I have tried with the ProxyServlet but am getting the following error at server side

SEVERE: Exception while dispatching
incoming RPC call
java.lang.NullPointerException
      at
javax.servlet.GenericServlet.getServletName(GenericServlet.java:322)
  
at javax.servlet.GenericServlet.log(GenericServlet.java:254)
      at
com.google.gwt.user.server.rpc.RemoteServiceServlet.loadSerializationPolicy(RemoteServiceServlet.java:75)

at
com.google.gwt.user.server.rpc.RemoteServiceServlet.doGetSerializationPolicy(RemoteServiceServlet.java:293)

at
com.google.gwt.user.server.rpc.RemoteServiceServlet.getSerializationPolicy(RemoteServiceServlet.java:157)

at
com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader.prepareToRead(ServerSerializationStreamReader.java:455)

at com.google.gwt.user.server.rpc.RPC.decodeRequest(RPC.java:237)
     
at
com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:206)

at
com.google.gwt.user.server.rpc.RemoteServiceServlet.processPost(RemoteServiceServlet.java:248)

at
com.google.gwt.user.server.rpc.AbstractRemoteServiceServlet.doPost(AbstractRemoteServiceServlet.java:62)

at
com.cybage.server.AbstractShrisRpcSerivce.handleRequest(AbstractShrisRpcSerivce.java:56)

at
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:48)

at
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:771)

at
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:716)

at
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:647)

at
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:563)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:710)
     
at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
     
at
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)

at
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)

at
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)

at
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)

at
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)

at
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)

at
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)

at
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:286)

at
org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)

at
org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)

at
org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)

at java.lang.Thread.run(Unknown Source)

even though I did the same thing like you mentioned and I have changed the jar files also. And at client side I am getting http 500 error.

One doubt I have here is, I have placed this proxyservlet in my client directory and I am confused to configure the project(web.xml and application.xml) please help me to solve the above error and also please send me the configuration settings.

Posted by chanti on August 02, 2011 at 09:07 AM MDT #

Hi Matt,

if i have 2 servers which is having following domain name, can i exchange information between the 2 servers using RequestBuilder.

server 1. http://factory-dev03.example.com:8111/

server 2. http://factory-dev109.example.com:8111/

Thanks
karun

Posted by karun on November 19, 2011 at 03:44 AM MST #

Hi,
I found a solution how you can use cross-side-ajax call using rpc call library without changing the google-code. In my tutorial I am using a proxy which will change the adress and so you can do cross-side-scripting. Because the xss-prevention is only for the webbrowser and on the server-side you can re-direct how often you want to.

The link to my tutorial:

http://gwtdebugtomcateclipse.blogspot.com/2011/12/cross-side-rpc-calls-with-gwt.html

Posted by Michael on December 11, 2011 at 11:51 AM MST #

Post a Comment:
  • HTML Syntax: Allowed