GAE/J + BlazeDS + Flash Builder 環境の構築

今回は GAE/J(Google App Engine Java) と BlazeDSflex (Adobe Flash Builder) の環境の構築方法についてまとめたいと思います。


以下は古い情報です。最新版はこちら


基本情報

まずは関係する基本的な情報をおさえておきましょう。

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)環境を構築します。

RPC疎通確認用アプリケーションの開発(サーバ側)

GAE/J環境にて疎通確認用アプリケーションを以下のように作成します。

  • Web Application Project を作成
  • RPC疎通確認用サービスの作成
    • rpcserver.EchoService クラスを作成

package rpcserver;

public class EchoService {
  public String echo(String text) {
    return "Server says: I received '" + text + "' from you";
  }
}

<?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:///RpcClient-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"]
この問題は AppEngine & Adobe BlazeDS (fix) に書かれています。

この問題については、BLZ-444 を見る限り BlazeDS 3 系は放置され、BlazeDS 4 から対応されると考えるのが妥当なようです。そのため、ここではソースレベルでパッチをあてることにより対応します。

まずは、問題箇所のソースを取得します。

これを、ローカルのクラス(flex.messaging.endpoints.BaseHTTPEndpoint)として配置します。

このままではコンパイルが通らないので、以下の jar ファイルをビルドパスに通します。(対象のjarファイルを右クリックして Build Path -> Add to Build Path)

  • WEB-INF\lib\flex-messaging-core.jar
  • WEB-INF\lib\flex-messaging-common.jar

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, put true 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.swfhttp://localhost:8888/messagebroker/my-amf からデータを読み込めません。 url: 'http://localhost:8888/messagebroker/my-amf'"]