Upload page content

You can upload content for the page named below. If you change the page name, you can also upload content for another page. If the page name is empty, we derive the page name from the file name.

File to load page content from
Page name
Comment

Locked History Actions

GWT/ReverseProxy

リバースプロキシ問題

通常、gwtアプリをwarファイルとしてtomcatに配備し、apacheと連携させ、ユーザ側にはapache側にアクセスさせることと思われる。 その際、例えばgwtアプリのコンテキストパスがfoobarであるのに、apache側ではルートとしてアクセスさせるような以下のような設定を行うと、

<Location />
 ProxyPass ajp://localhost:8009/foobar/
</Location>

以下のようなエラーが発生する。

Apr 5, 2012 2:37:43 PM org.apache.catalina.core.ApplicationContext log
INFO: Key[type=com.gwtplatform.dispatch.server.guice.DispatchServiceImpl, annotation=[none]]: ERROR: The module path requested, /foobar/, is not in the same web application as this servlet, /foobar.  Your module may not be properly configured or your client and server code maybe out of date.
Apr 5, 2012 2:37:43 PM org.apache.catalina.core.ApplicationContext log
INFO: Key[type=com.gwtplatform.dispatch.server.guice.DispatchServiceImpl, annotation=[none]]: WARNING: Failed to get the SerializationPolicy 'BFA4FE22E9D21F283BBB8900278EB97B' for module 'http://****/foobar/'; a legacy, 1.3.3 compatible, serialization policy will be used.  You may experience SerializationExceptions as a result.
Apr 5, 2012 2:37:43 PM org.apache.catalina.core.ApplicationContext log
SEVERE: Key[type=com.gwtplatform.dispatch.server.guice.DispatchServiceImpl, annotation=[none]]: An IncompatibleRemoteServiceException was thrown while processing this call.
com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException: Type '***' was not assignable to 'com.google.gwt.user.client.rpc.IsSerializable' and did not have a custom field serializer. For security purposes, this type will not be deserialized.
        at com.google.gwt.user.server.rpc.RPC.decodeRequest(RPC.java:315)
        at com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:206)
        at com.google.gwt.user.server.rpc.RemoteServiceServlet.processPost(RemoteServiceServlet.java:248)

apache側でルートコンテキストとしてアクセスさせたい場合は、tomcat側でもルートコンテキストとして配備しなければならないらしい。

答えはこのあたりにありそう

解決策

上記のリンクに含まれない、もっと単純な解決策があった。これで一応は動作するが、あらゆる場面で正常かどうかは不明。 対象はGWT 2.5.1 + GWTP 0.7で、GWTPのDispatchServiceImplを置き換えるものだが、これはRemoteServiceServletを継承しているので、生のRemoteServiceServletの場合にも適用可能と思われる。

import java.net.*;
import java.util.logging.*;

import javax.servlet.http.*;

import com.google.gwt.user.server.rpc.*;
import com.google.inject.*;
import com.gwtplatform.dispatch.server.*;
import com.gwtplatform.dispatch.server.guice.*;

@Singleton
public class MyDispatchServiceImpl extends DispatchServiceImpl {
  private static final long serialVersionUID = 1L;
  
  @Inject
  public MyDispatchServiceImpl(Logger logger, Dispatch dispatch,
      RequestProvider requestProvider) {
    super(logger, dispatch, requestProvider);
  }

  @Override
  protected SerializationPolicy doGetSerializationPolicy(
      HttpServletRequest request, String moduleBaseURL, String strongName) {
    
    URL url;
    try {
      url = new URL(moduleBaseURL);
    } catch (Exception ex) {
      throw new RuntimeException(ex);
    }
    String path = url.getPath();
    String host = moduleBaseURL.substring(0, moduleBaseURL.length() - path.length());
    
    return super.doGetSerializationPolicy(
      request, 
      host + request.getContextPath()  + path, 
      strongName
    );    
  }
}

別の問題

上記の措置を施しても、なおエラーが発生する場合がある。

com.google.gwt.user.client.rpc.SerializationException: Type '[Ljp.xxx.yyy.zzz.AAAA;' was not assignable to 'com.google.gwt.user.client.rpc.IsSerializable' and did not have a custom field serializer.For security purposes, this type will not be serialized.: instance = [Ljp.xxx.yyy.zzz.AAAA;@1bca390a
        at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serialize(ServerSerializationStreamWriter.java:667)
        at com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamWriter.writeObject(AbstractSerializationStreamWriter.java:126)
        at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter$ValueWriter$8.write(ServerSerializationStreamWriter.java:153)
        at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serializeValue(ServerSerializationStreamWriter.java:587)
        at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serializeClass(ServerSerializationStreamWriter.java:757)
        at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serializeImpl(ServerSerializationStreamWriter.java:796)
        at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serialize(ServerSerializationStreamWriter.java:669)
        at com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamWriter.writeObject(AbstractSerializationStreamWriter.java:126)
        at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter$ValueWriter$8.write(ServerSerializationStreamWriter.java:153)
        at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serializeValue(ServerSerializationStreamWriter.java:587)

上記の発生する状況としては、サーブレットコンテナ上ではルートに配備されているが、リバースプロキシ側ではルートに無いような場合が考えられる。

<VirtualHost *:80>
 ServerName test.com
  
 ProxyPass /sample/ http://localhost:8080/

</VirtualHost>

答えはこのあたりか

つまり、RemoteServiceServletのコードを見ればわかるが、 doGetSerializationPolicy(HttpServletRequest request, String moduleBaseURL, String strongName) 呼び出しのパラメータstrongNameは、直列化ポリシーファイルのベース名になっており、strongName + ".gwt.rpc"というファイルが取得できないと、Serializableによる直列化・復帰ができず、IsSerializableでマークされた直列化・復帰しかサポートされない模様。

これを解消する方策としては二つ。

  • 直列化指示として、IsSerializableを用いる(同時にSerializableをつけていてもよい)。

  • ポリシーファイルを探し出せるようにmoduleBaseURLを調整する。

Serializableを変更するのは面倒なので、後者の方法で行う。とりあえず、以下のようなコードに変更する。 あらゆるケースに対応可能かどうかは不明。

  @Override
  protected SerializationPolicy doGetSerializationPolicy(
      HttpServletRequest request, String moduleBaseURL, String strongName) {

    // サーブレットコンテナ上のコンテキストパスを取得する。
    // 空文字列の場合はルートに配備されているということ。
    String contextPath = request.getContextPath();
    
    // moduleBaseURLをホスト部とパス部に分ける。
    // パス部はスラッシュから開始、ホスト部の最後にスラッシュはない
    String moduleBasePath = createURL(moduleBaseURL).getPath();
    String moduleBaseHost = 
        moduleBaseURL.substring(0, moduleBaseURL.length() - moduleBasePath.length());
    
    // 変更後のmoduleBaseURL
    String modifiedModuleBaseURL;
    
    if (contextPath.length() == 0) {
      // contextPathが空。ルートに配備されている。
      // 最後のパス要素のみを切り出す。      
      if (!moduleBasePath.endsWith("/")) throw new RuntimeException("Invalid moduleBaseURL");
      moduleBasePath = moduleBasePath.substring(0, moduleBasePath.length() - 1);
      int lastSlash = moduleBasePath.lastIndexOf('/');
      if (lastSlash < 0) throw new RuntimeException("Invalid moduleBaseURL");
      moduleBasePath = moduleBasePath.substring(lastSlash) + "/";      

      modifiedModuleBaseURL = moduleBaseHost + moduleBasePath;
      
    } else {
      // contextPathが指定されている。ルートではない。
      modifiedModuleBaseURL = moduleBaseHost + contextPath  + moduleBasePath;
    }
    
    return super.doGetSerializationPolicy(request, modifiedModuleBaseURL, strongName);
  }