GAE/J + BlazeDS + Flash Builder 環境の構築
今回は GAE/J(Google App Engine Java) と BlazeDS と flex (Adobe Flash Builder) の環境の構築方法についてまとめたいと思います。
以下は古い情報です。最新版はこちら
基本情報
まずは関係する基本的な情報をおさえておきましょう。
Adobe Flex から、購入もしくは体験版をし取得してインストールします。(体験版は現時点では60日のライセンスになっています)
以下の動作検証は Adobe Flash Builder 4 の Standalone 版で行っています(が、どの版でも問題ないと思います)。
Adobe Flash Builder 4 の Standalone 版のベースは Eclipse 3.4(Ganymede) ではなく Eclipse 3.5(Galileo) のようです。
GAE/J(Google App Engine Java)環境の構築
Eclipse 3.5(Galileo) をベースとして、GAE/J(Google App Engine Java)環境を構築します。
- Javaがインストールされていなければ http://java.sun.com/ から Java SE をダウンロードしてインストールします。
- Eclipse Downloads から、Eclipse IDE for Java EE Developers をダウンロードしてインストール。(2010.04.03 時点では eclipse-jee-galileo-SR2-win32.zip)
- Eclipse のGoogle 関連プラグインをインストール。Update Site は http://dl.google.com/eclipse/plugin/3.5。(eclipse が起動しない場合は eclipse.ini の -showsplash org.eclipse.platform を削除すると動くかもしれません。)
RPC疎通確認用アプリケーションの開発(サーバ側)
GAE/J環境にて疎通確認用アプリケーションを以下のように作成します。
- Web Application Project を作成
- Project name : RpcServer
- Package : rpcserver
- Google SDKs : Use Google App Engine のみチェック
- RPC疎通確認用サービスの作成
- rpcserver.EchoService クラスを作成
package rpcserver; public class EchoService { public String echo(String text) { return "Server says: I received '" + text + "' from you"; } }
- BlazeDS 関連ライブラリの導入
- Downloads ページ内の Download the latest BlazeDS Release builds から Download the BlazeDS binary distribution を選択し、最新のリリース版のバイナリディストリビューションを取得します。
- zip ファイルを展開し、blazeds.war 内の WEB-INF/lib フォルダ内の以下の jar ファイルを RpcServer プロジェクトの war/WEB-INF/lib フォルダ以下にコピーします。
<?xml version="1.0" encoding="UTF-8"?> <service id="remoting-service" class="flex.messaging.services.RemotingService"> <adapters> <adapter-definition id="java-object" class="flex.messaging.services.remoting.adapters.JavaAdapter" default="true" /> </adapters> <default-channels> <channel ref="my-amf" /> </default-channels> <destination id="echoServiceDestination" channels="my-amf"> <properties> <source>rpcserver.EchoService</source> </properties> </destination> </service>
<?xml version="1.0" encoding="UTF-8"?> <services-config> <services> <service-include file-path="remoting-config.xml" /> </services> <channels> <channel-definition id="my-amf" class="mx.messaging.channels.AMFChannel"> <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/my-amf" class="flex.messaging.endpoints.AMFEndpoint" /> </channel-definition> </channels> <system> <!-- Workaround for GAE : java.lang.management.ManagementFactory is a restricted class. --> <manageable>false</manageable> </system> </services-config>
- WEB-INF/web.xml
<?xml version="1.0" encoding="utf-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <!-- Http Flex Session attribute and binding listener support --> <listener> <listener-class>flex.messaging.HttpFlexSession</listener-class> </listener> <!-- MessageBroker Servlet --> <servlet> <display-name>MessageBrokerServlet</display-name> <servlet-name>MessageBrokerServlet</servlet-name> <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class> <init-param> <param-name>services.configuration.file</param-name> <param-value>/WEB-INF/flex/services-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <!-- GAE's sample servlet --> <servlet> <servlet-name>RpcServer</servlet-name> <servlet-class>rpcserver.RpcServerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>MessageBrokerServlet</servlet-name> <url-pattern>/messagebroker/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>RpcServer</servlet-name> <url-pattern>/rpcserver</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app>
- WEB-INF/appengine-web.xml
<?xml version="1.0" encoding="utf-8"?> <appengine-web-app xmlns="http://appengine.google.com/ns/1.0"> <application></application> <version>1</version> <!-- Configure java.util.logging --> <system-properties> <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/> </system-properties> <!-- flex.messaging.HttpFlexSession uses session. --> <sessions-enabled>true</sessions-enabled> </appengine-web-app>
RPC疎通確認用アプリケーションの開発(クライアント側)
Adobe Flash Builder 4 にて疎通確認用アプリケーション(クライアント側)を以下のように作成します。
- Flex Project を作成
- Project name : RpcClient
- Application server type : J2EE
- User remote object access service にチェックし BlazeDS を選択
- Root folder : サーバ側プロジェクトの war フォルダのパス(例:C:\eclipse\workspace\RpcServer\war)
- Root URL : http://localhost:8888/
- Context root : /
- RpcClient.mxml ファイルの編集
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%"> <s:layout> <s:VerticalLayout horizontalAlign="center" paddingLeft="24" paddingBottom="24" paddingRight="24" paddingTop="24" /> </s:layout> <fx:Declarations> <!-- Place non-visual elements (e.g., services, value objects) here --> <mx:RemoteObject id="remoteObject" destination="echoServiceDestination" result="resultHandler(event);" fault="faultHandler(event);" /> </fx:Declarations> <fx:Script> <![CDATA[ import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; // Send the message in response to a Button click. private function echo():void { var text:String = ti.text; remoteObject.echo(text); } // Handle the recevied message. private function resultHandler(event:ResultEvent):void { ta.text += "Server responded: "+ event.result + "\n"; } // Handle a message fault. private function faultHandler(event:FaultEvent):void { ta.text += "Received fault: " + event.fault + "\n"; } ]]> </fx:Script> <mx:Label text="Enter a text for the server to echo" /> <mx:TextInput id="ti" text="Hello World!" /> <mx:Button label="Send" click="echo();" /> <mx:TextArea id="ta" width="100%" height="100%" /> </s:Application>
ローカル環境での動作確認
ローカル環境で Google App Engine のサーバを起動し、http://localhost:8888/RpcClient-debug/RpcClient.html にアクセスします。
GAE環境での動作確認
appengine-web.xml ファイルの application 要素に GAE の application-id を入れてデプロイし、http://
おそらく、ある確率で正常に動作すると思います。
GAE対策のパッチ当て
以下は古い情報です。最新版はこちら
現状ではおそらく、ある確率で以下のようなエラーが出ることと思います。
Received fault: [RPC Fault faultString="Detected duplicate HTTP-based FlexSessions, generally due to the remote host disabling session cookies. Session cookies must be enabled to manage the client connection correctly." faultCode="Server.Processing.DuplicateSessionDetected" faultDetail="null"]この問題は AppEngine & Adobe BlazeDS (fix) に書かれています。
この問題については、BLZ-444 を見る限り BlazeDS 3 系は放置され、BlazeDS 4 から対応されると考えるのが妥当なようです。そのため、ここではソースレベルでパッチをあてることにより対応します。
まずは、問題箇所のソースを取得します。
これを、ローカルのクラス(flex.messaging.endpoints.BaseHTTPEndpoint)として配置します。
このままではコンパイルが通らないので、以下の jar ファイルをビルドパスに通します。(対象のjarファイルを右クリックして Build Path -> Add to Build Path)
404行周辺の if (duplicateSessionDetected) ブロックをコメントアウト。
すべてビルドしなおし、GAEにデプロイすれば、Session 系のエラーが出なくなります。
この点については、後日考察してみることとします。
付録:jar不足によるエラーの例
- java.lang.ClassNotFoundException: flex.messaging.HttpFlexSession
flex-messaging-core.jar が不足 - java.lang.NoClassDefFoundError: flex/messaging/LocalizedException
flex-messaging-common.jar が不足 - java.lang.ClassNotFoundException: edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap
backport-util-concurrent.jar が不足 - javax.servlet.UnavailableException: Cannot create class of type 'flex.messaging.services.RemotingService'.
flex-messaging-remoting.jar が不足
付録:WEB-INF/appengine-web.xml に <sessions-enabled>true</sessions-enabled> を付け忘れた場合の例外
java.lang.RuntimeException: Session support is not enabled in appengine-web.xml. To enable sessions, puttrue in that file. Without it, getSession() is allowed, but manipulation of sessionattributes is not.
at com.google.apphosting.utils.jetty.StubSessionManager$StubSession.throwException(StubSessionManager.java:67)
at com.google.apphosting.utils.jetty.StubSessionManager$StubSession.setAttribute(StubSessionManager.java:63)
at flex.messaging.HttpFlexSession.getFlexSession(HttpFlexSession.java:236)
at flex.messaging.MessageBrokerServlet.service(MessageBrokerServlet.java:257)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:806)
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)
at com.google.appengine.api.blobstore.dev.ServeBlobFilter.doFilter(ServeBlobFilter.java:51)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
at com.google.apphosting.utils.servlet.TransactionCleanupFilter.doFilter(TransactionCleanupFilter.java:43)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
at com.google.appengine.tools.development.StaticFileFilter.doFilter(StaticFileFilter.java:122)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
at com.google.apphosting.utils.jetty.DevAppEngineWebAppContext.handle(DevAppEngineWebAppContext.java:70)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
at com.google.appengine.tools.development.JettyContainerService$ApiProxyHandler.handle(JettyContainerService.java:349)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
at org.mortbay.jetty.Server.handle(Server.java:326)
at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:923)
at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:547)
at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:212)
at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:409)
at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
付録:services-config.xml ファイルの endpoint の指定が不正な場合の例外の例
Received fault: [RPC Fault faultString="Send failed" faultCode="Client.Error.MessageSend" faultDetail="Channel.Security.Error error Error #2048: セキュリティサンドボックス侵害 : http://beyondseeker-mixi-01.appspot.com/BlazeDSTest.swf は http://localhost:8888/messagebroker/my-amf からデータを読み込めません。 url: 'http://localhost:8888/messagebroker/my-amf'"]