基本情報
まずは関係する基本的な情報をおさえておきましょう。
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環境にてRPC疎通確認用アプリケーションを以下のように作成します。
- Web Application Project を作成
- Project name : Server
- Package : server
- Google SDKs : Use Google App Engine のみチェック
- 疎通確認用サービスの作成
- server.EchoService クラスを作成
package server; 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 ファイルを Server プロジェクトの war/WEB-INF/lib フォルダ以下にコピーします。
- backport-util-concurrent.jar
- flex-messaging-core.jar
- flex-messaging-common.jar
- flex-messaging-remoting.jar
- BlazeDS 関連設定
- WEB-INF/flex/remoting-config.xml
<?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> <destination id="echoServiceDestination"> <properties> <source>server.EchoService</source> <scope>application</scope> </properties> </destination> </service>
- WEB-INF/flex/services-config.xml
<?xml version="1.0" encoding="UTF-8"?> <services-config> <services> <default-channels><channel ref="amf" /></default-channels> <service-include file-path="remoting-config.xml" /> </services> <channels> <channel-definition id="amf" class="mx.messaging.channels.AMFChannel"> <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/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> <load-on-startup>1</load-on-startup> </servlet> <!-- GAE's sample servlet --> <servlet> <servlet-name>Server</servlet-name> <servlet-class>server.ServerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>MessageBrokerServlet</servlet-name> <url-pattern>/messagebroker/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>Server</servlet-name> <url-pattern>/server</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>
疎通確認用アプリケーションの開発(クライアント側)
Adobe Flash Builder 4 にて疎通確認用アプリケーション(クライアント側)を以下のように作成します。
- Flex Project を作成
- Project name : Client
- Application server type : J2EE
- User remote object access service にチェックし BlazeDS を選択
- Root folder : サーバ側プロジェクトの war フォルダのパス(例:C:\eclipse\workspace\Server\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/Client-debug/RpcClient.html にアクセスします。
GAE環境での動作確認
appengine-web.xml ファイルの application 要素に GAE の application-id を入れてデプロイし、http://<host:port>/Client-debug/RpcClient.html にアクセスします。
おそらく、ある確率で正常に動作すると思います。
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"]
現在の筆者の環境では、2回失敗し1回成功するパターンの繰り返しになるようです。
この件の詳細はGAE/J + BlazeDS 環境の DuplicateSessionDetected 問題参照のこと。
Google App Engine のサポートに関しては BLZ-444 にて Feature Request が提出されていますが、BlazeDS 3 系は放置され、BlazeDS 4 から対応ようです。そのため、ここではソースレベルでパッチをあてることにより対応します。
まずは、問題箇所であるFlexSession クラスのソースを取得します。
これを、ローカルのクラス(flex.messaging.FlexSession)として配置します。
このままではコンパイルが通らないので、以下の jar ファイルをビルドパスに通します。(対象のjarファイルを右クリックして Build Path -> Add to Build Path)
- WEB-INF\lib\flex-messaging-core.jar
- WEB-INF\lib\flex-messaging-common.jar
- WEB-INF\lib\backport-util-concurrent.jar
HttpFlexSession クラスに以下のメソッドを追加します。
@Override public boolean equals(Object obj) { if (!FlexSession.class.isInstance(obj)) return false; return FlexSession.class.cast(obj).getId().equals(getId()); } @Override public int hashCode() { if (HttpFlexSession.class.isInstance(this) && HttpFlexSession.class.cast(this).httpSession != null) { // to avoid NPE. return getId().hashCode(); } else { return super.hashCode(); } }
もしくは、FlexSession クラス以下に以下のメソッドを追加します。
@Override public boolean equals(Object obj) { if (!FlexSession.class.isInstance(obj)) return false; return FlexSession.class.cast(obj).getId().equals(getId()); } @Override public int hashCode() { if (HttpFlexSession.class.isInstance(this) && HttpFlexSession.class.cast(this).httpSession != null) { // to avoid NPE. return getId().hashCode(); } else { return super.hashCode(); } }
また、flex.messaging.client.FlexClient クラスの registerFlexSession(FlexSession) メソッドについて、以下のように修正します。(メモ)
public void registerFlexSession(FlexSession session) { if (sessions.addIfAbsent(session)) { session.addSessionDestroyedListener(this); session.registerFlexClient(this); } // for session serialization else if (session.getFlexClients().isEmpty()) { session.addSessionDestroyedListener(this); session.registerFlexClient(this); } }
すべてビルドしなおし、GAEにデプロイすれば完了です。
メッセージング疎通確認用アプリケーションの開発
先ほど RPC の疎通確認用アプリケーションを作成したので、今度はメッセージングの疎通確認アプリケーションを作成します。
先ほどの Flex プロジェクトに、MessagingClient.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%" creationComplete="init()"> <s:layout> <s:VerticalLayout horizontalAlign="center" paddingLeft="24" paddingBottom="24" paddingRight="24" paddingTop="24" /> </s:layout> <fx:Declarations> <mx:Producer id="producer" destination="messageServiceDestination" acknowledge="acknowledgeHandler(event);" fault="messagingFaultHandler(event);"/> <mx:Consumer id="consumer" destination="messageServiceDestination" message="messageHandler(event);" fault="messagingFaultHandler(event);"/> </fx:Declarations> <fx:Script> <![CDATA[ import mx.messaging.events.MessageAckEvent; import mx.messaging.events.MessageEvent; import mx.messaging.events.MessageFaultEvent; import mx.messaging.messages.AsyncMessage; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; // Initialize this component. private function init():void { consumer.subscribe(); } // Send the message in response to a Button click. private function send():void { var text:String = ti.text; var message:AsyncMessage = new AsyncMessage(); message.body = text; producer.send(message); } // Handle the received acknoledge. private function acknowledgeHandler(event:MessageAckEvent):void { if (!ackCheckBox.selected) return; ta.text += "[ Ack ] "+ event.acknowledgeMessage + "\n"; } // Handle the recevied message. private function messageHandler(event:MessageEvent):void { ta.text += "[ Message ] " + event.message.body + "\n"; } // Handle a message fault. private function messagingFaultHandler(event:MessageFaultEvent):void { ta.text += "[ fault ] "+ event.message.faultString + "\n"; } ]]> </fx:Script> <mx:Label text="Enter a message" /> <mx:TextInput id="ti" text="Hello World!" /> <mx:Button label="Send" click="send();" /> <mx:TextArea id="ta" width="100%" height="100%" fontFamily="Courier New"/> <s:CheckBox id="ackCheckBox" label="show ack"/> </s:Application>
WEB-INF/flex/messaging-config.xml ファイルの作成
<service id="message-service" class="flex.messaging.services.MessageService"> <adapters> <adapter-definition id="actionscript" class="flex.messaging.services.messaging.adapters.ActionScriptAdapter" default="true" /> </adapters> <destination id="messageServiceDestination"> </destination> </service>
services-config.xml ファイルに messaging-config.xml のインクルード設定をします。
<?xml version="1.0" encoding="UTF-8"?> <services-config> <services> <default-channels><channel ref="amf" /></default-channels> <service-include file-path="remoting-config.xml" /> <service-include file-path="messaging-config.xml" /> </services> <channels> <channel-definition id="amf" class="mx.messaging.channels.AMFChannel"> <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf" class="flex.messaging.endpoints.AMFEndpoint" /> <properties> <polling-enabled>true</polling-enabled><!-- default is true (though the document doesn't say so) --> </properties> </channel-definition> </channels> <system> <!-- Workaround for GAE : java.lang.management.ManagementFactory is a restricted class. --> <manageable>false</manageable> </system> </services-config>polling-enabled は、現状の実装だとデフォルト値が true のようなので記述しなくても問題ないと思いますが、ドキュメントでは false と書かれているので、明示的に設定しておいたほうが無難かと思われます。
ローカル環境での動作確認
ローカル環境で Google App Engine のサーバを起動し、http://localhost:8888/Client-debug/MessagingClient.html にアクセスします。
付録: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/amf からデータを読み込めません。 url: 'http://localhost:8888/messagebroker/amf'"]
0 件のコメント:
コメントを投稿