views:

1281

answers:

4

I'm having this problem with GWT when it's behind a reverse proxy. The backend app is deployed within a context - let's call it /context.

The GWT app works fine when I hit it directly:

http://host:8080/context/

I can configure a reverse proxy in front it it. Here's my nginx example:

upstream backend {
    server 127.0.0.1:8080;
}

...

location / {
   proxy_pass        http://backend/context/;
}

But, when I run through the reverse proxy, GWT gets confused, saying:

2009-10-04 14:05:41.140:/:WARN:  Login: ERROR: The serialization policy file '/C7F5ECA5E3C10B453290DE47D3BE0F0E.gwt.rpc' was not found; did you forget to include it in this deployment?
2009-10-04 14:05:41.140:/:WARN:  Login: WARNING: Failed to get the SerializationPolicy 'C7F5ECA5E3C10B453290DE47D3BE0F0E' for module 'https://hostname:444/'; a legacy, 1.3.3 compatible, serialization policy will be used.  You may experience SerializationExceptions as a result.
2009-10-04 14:05:41.292:/:WARN:  StoryService: ERROR: The serialization policy file '/0445C2D48AEF2FB8CB70C4D4A7849D88.gwt.rpc' was not found; did you forget to include it in this deployment?
2009-10-04 14:05:41.292:/:WARN:  StoryService: WARNING: Failed to get the SerializationPolicy '0445C2D48AEF2FB8CB70C4D4A7849D88' for module 'https://hostname:444/'; a legacy, 1.3.3 compatible, serialization policy will be used.  You may experience SerializationExceptions as a result.

In other words, GWT isn't getting the word that it needs to prepend /context/ hen look for C7F5ECA5E3C10B453290DE47D3BE0F0E.gwt.rpc, but only when the request comes throught proxy. A workaround is to add the context to the url for the web site:

location /context/ {
    proxy_pass        http://backend/context/;
}

but that means the context is now part of the url that the user sees, and that's ugly.

Anybody know how to make GWT happy in this case?

Software versions:
GWT - 1.7.0 (same problem with 1.7.1)
Jetty - 6.1.21 (but the same problem existed under tomcat)
nginx - 0.7.62 (same problem under apache 2.x)

I've looked at the traffic between the proxy and the backend using DonsProxy, but there's nothing noteworthy there.

A: 

I've run into a similar problem, a successful workaround was to make all serialized objects implement GWT's IsSerializable interface (in addition to the standard Serializable interface). If you read the message, it states that 'a legacy, 1.3.3 compatible, serialization policy will be used' - the 1.3.3 compatible policy requires all of your serialized objects implement the IsSerializable interface, so by adding it, everything worked.

I do have concerns that the legacy policy will be desupported in future versions of GWT, so i am also in search for a better workaround myself.

Chi
Interesting. What's the connection between the IsSerializable interface and the reverse proxy?
Don Branson
With your example, the reverse proxy is causing the serialization policy file (C7F5ECA5E3C10B453290DE47D3BE0F0E.gwt.rpc) to not be found. Because of this, it is reverting to the 1.3.3 compatible serialization policy, which does not require such a policy file, but instead requires serialized objects to implement the IsSerializable interface
Chi
I agree that there's something that the proxy is doing. I think it has nothing to do with what java interfaces are in use. The question is, what is the proxy doing that's confusing GWT? The headers look the same, the URLs are being translated correctly, and so forth. The request for the C7F%... file never actually goes through the proxy, since it's all handled at the server side, base on what I see on the wire both with and without the proxy.
Don Branson
+1  A: 

I'm fairly sure the correct answer here is to patch the source and submit a bug report. Another option would be to run the GWT app at / on your backend.

I'd prefer the former, but the latter should work too. If you really needed things separated out into multiple contexts, use a different port number?

Bob Aman
I don't necessarily need things separated out in the short term - but the app builder sets modules up with a context by default, and I might want to separate out certain pieces into other modules.Patching the source (to GWT) sounds like the right answer, since it seems that everything is configured correctly.
Don Branson
The way I see it, you've got a messy problem, and other people are likely to benefit from your fix, so a patch would be extremely valuable. If you do go this route, be sure to put your patch file up on Gist (http://gist.github.com/) or similar, and link this question to it, in case the patch isn't accepted immediately.
Bob Aman
+2  A: 

I have the same problem, and I opened a bug report:

http://code.google.com/p/google-web-toolkit/issues/detail?id=4817

The problem is that it was marked "As Design", so I don't think it will be fixed.

I found this solution for me. I extended the class RemoteServiceServlet and I forced GWT to load serialization policy file starting from ContextName instead of URL. Then I extended my service my class instead of RemoteServiceServlet class. In this way the application will be unlinked from the url from where it will be called.

Here there is my custom class:

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.google.gwt.user.server.rpc.SerializationPolicy;
import com.google.gwt.user.server.rpc.SerializationPolicyLoader;

public class MyRemoteServiceServlet extends RemoteServiceServlet
{
    @Override
    protected SerializationPolicy doGetSerializationPolicy(HttpServletRequest request, String moduleBaseURL, String strongName)
    {
        return MyRemoteServiceServlet.loadSerializationPolicy(this, request, moduleBaseURL, strongName);
    }


    /**
      * Used by HybridServiceServlet.
      */
      static SerializationPolicy loadSerializationPolicy(HttpServlet servlet,
      HttpServletRequest request, String moduleBaseURL, String strongName) {
    // The serialization policy path depends only by contraxt path
    String contextPath = request.getContextPath();

    SerializationPolicy serializationPolicy = null;


    String contextRelativePath = contextPath + "/";



      String serializationPolicyFilePath = SerializationPolicyLoader.getSerializationPolicyFileName(contextRelativePath
          + strongName);

      // Open the RPC resource file and read its contents.
      InputStream is = servlet.getServletContext().getResourceAsStream(
          serializationPolicyFilePath);
      try {
        if (is != null) {
          try {
        serializationPolicy = SerializationPolicyLoader.loadFromStream(is,
            null);
          } catch (ParseException e) {
        servlet.log("ERROR: Failed to parse the policy file '"
            + serializationPolicyFilePath + "'", e);
          } catch (IOException e) {
        servlet.log("ERROR: Could not read the policy file '"
            + serializationPolicyFilePath + "'", e);
          }
        } else {
          String message = "ERROR: The serialization policy file '"
          + serializationPolicyFilePath
          + "' was not found; did you forget to include it in this deployment?";
          servlet.log(message);
        }
      } finally {
        if (is != null) {
          try {
        is.close();
          } catch (IOException e) {
        // Ignore this error
          }
        }
      }

    return serializationPolicy;
      }
}
Michele Renda
I like this solution, but like KC Berg, it only works in the production environment. My final solution, located at http://gist.github.com/476175, uses this code but tries to load the file normally before calling `loadSerializationPolicy()`.
BinaryMuse
A: 

Michele,

Thank you for the example servlet to handle this problem. However when I tried to use your approach it worked in the reverse proxy environment but not in my dev mode eclipse environment.

I took an approach that would let me seamlessly move between my dev and prod environments.

As you did I overwrote RemoteServiceServlet but I only replaced following...

@Override
protected SerializationPolicy doGetSerializationPolicy(
        HttpServletRequest request, String moduleBaseURL, String strongName) {
    //get the base url from the header instead of the body this way 
    //apache reverse proxy with rewrite on the header can work
    String moduleBaseURLHdr = request.getHeader("X-GWT-Module-Base");

    if(moduleBaseURLHdr != null){
        moduleBaseURL = moduleBaseURLHdr;
    }

    return super.doGetSerializationPolicy(request, moduleBaseURL, strongName);
}

In my apache config I added...

ProxyPass /app/ ajp://localhost:8009/App-0.0.1-SNAPSHOT/

RequestHeader edit X-GWT-Module-Base ^(.*)/app/(.*)$ $1/App-0.0.1-SNAPSHOT/$2

This approach works in all scenarios and delegates the url "mucking" to apache's proxy settings which is the approach I've always taken.

Comments on this approach are appreciated

KC Berg