= リバースプロキシ問題 =
通常、gwtアプリをwarファイルとしてtomcatに配備し、apacheと連携させ、ユーザ側にはapache側にアクセスさせることと思われる。
その際、例えばgwtアプリのコンテキストパスがfoobarであるのに、apache側ではルートとしてアクセスさせるような以下のような設定を行うと、
{{{
ProxyPass ajp://localhost:8009/foobar/
}}}
以下のようなエラーが発生する。
{{{
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側でもルートコンテキストとして配備しなければならないらしい。
答えはこのあたりにありそう
* http://stackoverflow.com/questions/1517290/problem-with-gwt-behind-a-reverse-proxy-either-nginx-or-apache
* http://java.dzone.com/articles/how-do-cross-domain-gwt-rpc
== 解決策 ==
上記のリンクに含まれない、もっと単純な解決策があった。これで一応は動作するが、あらゆる場面で正常かどうかは不明。
対象は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)
}}}
上記の発生する状況としては、サーブレットコンテナ上ではルートに配備されているが、リバースプロキシ側ではルートに無いような場合が考えられる。
{{{
ServerName test.com
ProxyPass /sample/ http://localhost:8080/
}}}
答えはこのあたりか
* https://groups.google.com/forum/#!msg/Google-Web-Toolkit/63Ui7lp-Bm4/ccMjqPksMbEJ
つまり、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);
}
}}}