ラベル BlazeDS の投稿を表示しています。 すべての投稿を表示
ラベル BlazeDS の投稿を表示しています。 すべての投稿を表示

2009年10月26日月曜日

BlazeではKey型はマッピングされないのでString型をメインに使おう

エンティティグループの永続化をunowned-relationshipで構成しようと、gae.parent-pkを使い、親キーのタイプをKey型で指定していた。

しかし、システムの環境としてBlazeDSを使っていたので、ActionScriptが展開できる型にKey型がなく問題に。ふむー。

ちなみにActionScriptとJavaで互換性のあるデータタイプの一覧はこちら。
Explicitly mapping ActionScript and Java objects

よってKey型をやめ、String型をメインに使うように仕様を変更し、encoded-pk属性を追加。

また、データ登録時に主キーにユニークな値を設定してほしいので、IdGeneratorStrategy.IDENTITYの設定も有りにして、以下のような構成になった。
@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
public class Parent{

 @PrimaryKey
 @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
 @Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
 private String parentId;

//以下アクセッサー

}

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
public class Child {

 @PrimaryKey
 @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
 @Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
 private String childId;

 // 親キーIDを格納するプロパティ
 @Persistent
 @Extension(vendorName = "datanucleus", key = "gae.parent-pk", value = "true")
 private String parentId;

//以下アクセッサー

}
これからはBlazeDSとの互換性の意味でも、このような主キー、親キーの設定方法でいこう。


それにしても、Kindの形をドンドン変えられるというのはスゴイ。これまでのRelational DataBaseでは考えられない拡張性だ。フィールドの追加も型の変更も、オンザフライでドンドン変えられる。この体験はまさに別世界だなー。この環境を与えてくれたGoogleに感謝!

2009年9月30日水曜日

InvocationTargetExceptionのその後

以前書いたInvocationTargetExceptionがよく起こる点について、GAE/JのGoogle Groupsを見ていたら、同じようなことで悩んでるスレッド発見。

Startup takes forever - Google App Engine for Java | Google Groups
new stack traces after upgrading to SDK 1.2.5 - Google App Engine for Java | Google Groups

後者の方で言われているが、スレッドがサポートされてないので、やっぱりこの例外はどうしようもないのかもしれないし、クリティカルなものではないからINFOレベルなわけだし。

ただそのとき、CPU時間がグッとあがる点がね・・・。ユーザー数やリクエスト数が増えたときにすぐに無料の範囲を超えないかって気になる。

2009年9月14日月曜日

GAE/J-Blaze接続でInvocationTargetExceptionがよく起こる

クライアントのAirアプリからGAE/JのサーバーにアクセスするとよくInvocationTargetExceptionが起こる。

発生するタイミングとしては、アイドルタイムが10分以上経ったりなど、しばらくしてからサーバーへリクエストを発生した時によく起こる。というか必ず起こっている。

ログのレベルとしてはInfoレベルなのだが、Air上でも一瞬「うっ」とリクエストが止まったように見えて、なんだか気になる。
xxx.xxx.xxx.xxx - - [13/Sep/2009:18:47:23 -0700] "POST /messagebroker/amf;jsessionid=Z6NYw5g89TFfOj6cDi50aA HTTP/1.1" 200 960 "app:/xxx.swf" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/526.9+ (KHTML, like Gecko) AdobeAIR/1.5,gzip(gfe)" "xxx.appspot.com"

I 09-13 06:47PM 22.601

com.google.appengine.repackaged.com.google.common.base.FinalizableReferenceQueue$SystemLoader loadFinalizer: Not allowed to access system class loader.

I 09-13 06:47PM 22.614

com.google.appengine.repackaged.com.google.common.base.internal.Finalizer getInheritableThreadLocalsField: Couldn't access Thread.inheritableThreadLocals. Reference finalizer threads will inherit thread local values.

I 09-13 06:47PM 22.616

com.google.appengine.repackaged.com.google.common.base.FinalizableReferenceQueue <init>: Failed to start reference finalizer thread. Reference cleanup will only occur when new references are created.
java.lang.reflect.InvocationTargetException
 at com.google.appengine.runtime.Request.process-1ed2a922d6cd3b28(Request.java)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 at java.lang.reflect.Method.invoke(Method.java:40)
 at com.google.appengine.repackaged.com.google.common.base.FinalizableReferenceQueue.<init>(FinalizableReferenceQueue.java:124)
 at com.google.appengine.repackaged.com.google.common.labs.misc.InterningPools$WeakInterningPool.<clinit>(InterningPools.java:104)
 at com.google.appengine.repackaged.com.google.common.labs.misc.InterningPools.newWeakInterningPool(InterningPools.java:48)
 at com.google.appengine.repackaged.com.google.io.protocol.ProtocolSupport.<clinit>(ProtocolSupport.java:55)
 at com.google.apphosting.api.DatastorePb$Query.<init>(DatastorePb.java:1072)
 at com.google.apphosting.api.DatastorePb$Query$1.<init>(DatastorePb.java:2355)
 at com.google.apphosting.api.DatastorePb$Query.<clinit>(DatastorePb.java:2355)
 at com.google.appengine.api.datastore.QueryTranslator.convertToPb(QueryTranslator.java:27)
 at com.google.appengine.api.datastore.DatastoreServiceImpl$PreparedQueryImpl.convertToPb(DatastoreServiceImpl.java:357)
 at com.google.appengine.api.datastore.DatastoreServiceImpl$PreparedQueryImpl.runQuery(DatastoreServiceImpl.java:339)
 at com.google.appengine.api.datastore.DatastoreServiceImpl$PreparedQueryImpl.access$100(DatastoreServiceImpl.java:269)
 at com.google.appengine.api.datastore.DatastoreServiceImpl$PreparedQueryImpl$1.iterator(DatastoreServiceImpl.java:303)
 at org.datanucleus.store.appengine.query.RuntimeExceptionWrappingIterable.iterator(RuntimeExceptionWrappingIterable.java:42)
 at org.datanucleus.store.appengine.query.StreamingQueryResult.<init>(StreamingQueryResult.java:77)
 at org.datanucleus.store.appengine.query.DatastoreQuery.newStreamingQueryResultForEntities(DatastoreQuery.java:324)
 at org.datanucleus.store.appengine.query.DatastoreQuery.fulfillEntityQuery(DatastoreQuery.java:310)
 at org.datanucleus.store.appengine.query.DatastoreQuery.performExecute(DatastoreQuery.java:242)
 at org.datanucleus.store.appengine.query.JDOQLQuery.performExecute(JDOQLQuery.java:84)
 at org.datanucleus.store.query.Query.executeQuery(Query.java:1489)
 at org.datanucleus.store.query.Query.executeWithArray(Query.java:1371)
 at org.datanucleus.jdo.JDOQuery.execute(JDOQuery.java:266)
 at jp.co.tricell.sonicboom.rpc.MutterUtils.getMutterAround(MutterUtils.java:84)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 at java.lang.reflect.Method.invoke(Method.java:40)
 at flex.messaging.services.remoting.adapters.JavaAdapter.invoke(JavaAdapter.java:421)
 at flex.messaging.services.RemotingService.serviceMessage(RemotingService.java:183)
 at flex.messaging.MessageBroker.routeMessageToService(MessageBroker.java:1503)
 at flex.messaging.endpoints.AbstractEndpoint.serviceMessage(AbstractEndpoint.java:884)
 at flex.messaging.endpoints.amf.MessageBrokerFilter.invoke(MessageBrokerFilter.java:121)
 at flex.messaging.endpoints.amf.LegacyFilter.invoke(LegacyFilter.java:158)
 at flex.messaging.endpoints.amf.SessionFilter.invoke(SessionFilter.java:44)
 at flex.messaging.endpoints.amf.BatchProcessFilter.invoke(BatchProcessFilter.java:67)
 at flex.messaging.endpoints.amf.SerializationFilter.invoke(SerializationFilter.java:146)
 at flex.messaging.endpoints.BaseHTTPEndpoint.service(BaseHTTPEndpoint.java:278)
 at flex.messaging.MessageBrokerServlet.service(MessageBrokerServlet.java:322)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:806)
 at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:487)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1093)
 at com.google.apphosting.runtime.jetty.SaveSessionFilter.doFilter(SaveSessionFilter.java:35)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
 at com.google.apphosting.utils.servlet.TransactionCleanupFilter.doFilter(TransactionCleanupFilter.java:43)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
 at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:360)
 at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
 at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
 at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:712)
 at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
 at com.google.apphosting.runtime.jetty.AppVersionHandlerMap.handle(AppVersionHandlerMap.java:237)
 at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:139)
 at org.mortbay.jetty.Server.handle(Server.java:313)
 at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:506)
 at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:830)
 at com.google.apphosting.runtime.jetty.RpcRequestParser.parseAvailable(RpcRequestParser.java:76)
 at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:381)
 at com.google.apphosting.runtime.jetty.JettyServletEngineAdapter.serviceRequest(JettyServletEngineAdapter.java:139)
 at com.google.apphosting.runtime.JavaRuntime.handleRequest(JavaRuntime.java:235)
 at com.google.apphosting.base.RuntimePb$EvaluationRuntime$6.handleBlockingRequest(RuntimePb.java:4950)
 at com.google.apphosting.base.RuntimePb$EvaluationRuntime$6.handleBlockingRequest(RuntimePb.java:4948)
 at com.google.net.rpc.impl.BlockingApplicationHandler.handleRequest(BlockingApplicationHandler.java:24)
 at com.google.net.rpc.impl.RpcUtil.runRpcInApplication(RpcUtil.java:359)
 at com.google.net.rpc.impl.Server$2.run(Server.java:823)
 at com.google.tracing.LocalTraceSpanRunnable.run(LocalTraceSpanRunnable.java:56)
 at com.google.tracing.LocalTraceSpanBuilder.internalContinueSpan(LocalTraceSpanBuilder.java:516)
 at com.google.net.rpc.impl.Server.startRpc(Server.java:778)
 at com.google.net.rpc.impl.Server.processRequest(Server.java:351)
 at com.google.net.rpc.impl.ServerConnection.messageReceived(ServerConnection.java:437)
 at com.google.net.rpc.impl.RpcConnection.parseMessages(RpcConnection.java:319)
 at com.google.net.rpc.impl.RpcConnection.dataReceived(RpcConnection.java:290)
 at com.google.net.async.Connection.handleReadEvent(Connection.java:428)
 at com.google.net.async.EventDispatcher.processNetworkEvents(EventDispatcher.java:762)
 at com.google.net.async.EventDispatcher.internalLoop(EventDispatcher.java:207)
 at com.google.net.async.EventDispatcher.loop(EventDispatcher.java:101)
 at com.google.net.rpc.RpcService.runUntilServerShutdown(RpcService.java:251)
 at com.google.apphosting.runtime.JavaRuntime$RpcRunnable.run(JavaRuntime.java:392)
 at java.lang.Thread.run(Unknown Source)
Caused by: java.security.AccessControlException: access denied (java.lang.RuntimePermission modifyThreadGroup)
 at java.security.AccessControlContext.checkPermission(Unknown Source)
 at java.security.AccessController.checkPermission(Unknown Source)
 at java.lang.SecurityManager.checkPermission(Unknown Source)
 at java.lang.ThreadGroup.checkAccess(Unknown Source)
 at java.lang.Thread.init(Unknown Source)
 at java.lang.Thread.<init>(Unknown Source)
 at com.google.appengine.repackaged.com.google.common.base.internal.Finalizer.<init>(Finalizer.java:96)
 at com.google.appengine.repackaged.com.google.common.base.internal.Finalizer.startFinalizer(Finalizer.java:82)
 ... 81 more
上記のログには3種類のInfoレベルのログが含まれており、最後の3つ目のInfoレベルのものがInvocationTargetExceptionなのだが、この3種類のものが必ずアイドルタイム発生後に発生する。

InvocationTargetExceptionは、JavaDocによると「呼び出されるメソッドまたはコンストラクタがスローする例外をラップする、チェック済み例外」ということ。問題なのはログの下の方にある、原因となった「Caused by~」のほうだが、securityのAccessControlExceptionということで、これはGAE/J上でスレッドなどを走らせようとしたときによく出る例外だったなぁと思いだす。

Airから接続して前回のセッションを呼び出そうとした時にスレッドを使おうとしてExceptionが発生しているのかもしれない。もともとBlazeDSのRemoting Objectもコードを改変してムリクリ動かしているわけで(^^;)。

動作上は、ちょっとリクエストがつっかえたように見えるというくらいで、その他は期待したとおりの動作が出来ているので問題はないのだが・・・。

毎回このログが出ないようにできれば改善したい。どうにかできんもんかのー。

2009年9月9日水曜日

Google App EngineでStreamingAMFChannelが使えたかとぬかった

昨日の記事に書いた以下の例外メッセージを見ていて、要はBaseStreamingHTTPEndpoint.javaの833行目にあるコード「currentThread.setName(threadName);」、つまりスレッドにsetNameしている部分をコメントしてみたら解決するのではないかということに。
2009/09/08 4:57:25 com.google.apphosting.utils.jetty.JettyLogger warn
警告: /messagebroker/streamingamf
java.security.AccessControlException: access denied (java.lang.RuntimePermission modifyThread)
 at java.security.AccessControlContext.checkPermission(AccessControlContext.java:323)
 at java.security.AccessController.checkPermission(AccessController.java:546)
 at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
 at com.google.appengine.tools.development.DevAppServerFactory$CustomSecurityManager.checkPermission(DevAppServerFactory.java:139)
 at com.google.appengine.tools.development.DevAppServerFactory$CustomSecurityManager.checkAccess(DevAppServerFactory.java:178)
 at java.lang.Thread.checkAccess(Thread.java:1263)
 at java.lang.Thread.setName(Thread.java:1050)
 at flex.messaging.endpoints.BaseStreamingHTTPEndpoint.handleFlexClientStreamingOpenRequest(BaseStreamingHTTPEndpoint.java:833)
 at flex.messaging.endpoints.BaseStreamingHTTPEndpoint.serviceStreamingRequest(BaseStreamingHTTPEndpoint.java:1022)
 at flex.messaging.endpoints.BaseStreamingHTTPEndpoint.service(BaseStreamingHTTPEndpoint.java:430)
 at flex.messaging.MessageBrokerServlet.service(MessageBrokerServlet.java:322)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:806)
 at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:487)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1093)
 at com.google.apphosting.utils.servlet.TransactionCleanupFilter.doFilter(TransactionCleanupFilter.java:43)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
 at com.google.appengine.tools.development.StaticFileFilter.doFilter(StaticFileFilter.java:121)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
 at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:360)
 at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
 at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
 at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:712)
 at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
 at com.google.apphosting.utils.jetty.DevAppEngineWebAppContext.handle(DevAppEngineWebAppContext.java:54)
 at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:139)
 at com.google.appengine.tools.development.JettyContainerService$ApiProxyHandler.handle(JettyContainerService.java:313)
 at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:139)
 at org.mortbay.jetty.Server.handle(Server.java:313)
 at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:506)
 at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:844)
 at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:644)
 at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:211)
 at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:381)
 at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:396)
 at org.mortbay.thread.BoundedThreadPool$PoolThread.run(BoundedThreadPool.java:442)
コメントしてビルドしなおし、再びStreaimingAMFChannelを使って接続を試してみると・・・

java.security.AccessControlExceptionが出ない(>w<)!
やった!GAE/Jのセキュリティサンドボックスにひっかからずにいけたみたい!

ローカルのGAE/Jのプロジェクトで、StreamingAMFChannelを使って各種ブラウザとチャット通信を試してみる。IE8、Firefox、Chrome、Safari、Opera、Sleipnir、Lunascape5など。

IE系エンジンを使ったブラウザでは、consumer.subscribe();しても反応が無く、再読み込みして再度subscribeすると
[BlazeDS]Endpoint with id 'my-streaming-amf' cannot grant streaming connection to FlexClient with id 'F1B77645-EE1C-F1B8-66AA-7714CC07D614' because max-streaming-connections-per-session limit of '1' has been reached.
とエラーが出て、Consumerの接続に失敗する。IEならではのセッション周りの問題かと思い、BlazeDSのドキュメントを参考に、services-config.xmlのStreamingAMFChannelの設定にpropertiesを追加する。
        
            
            
              
                
                
              
            
        
IE系エンジンでも無事にsubscribe出来るようになった。

う~ん、さすがStreaming。文字列表示がサックサク。pollingの場合は文字列が表示されるまで一呼吸ある感じがモッサリして見えるがStreamingはそんなことはない。やっぱりStreamingの方がNearリアルタイム通信だなぁとルンルンでGAE/J上にデプロイしてみると・・・

動かない・・・orz

ぬかった。ぬかった。完全にぬか喜びだった。ノー。ConsumerもProducerもchannelFaultが発生。GAE/Jの管理パネルからログを見てみると、
[<GAE/Jアプリケーション名>/1.336207119101686921].: [BlazeDS]Endpoint with id 'my-streaming-amf' cannot service the streaming request as either the supplied FlexClient id 'F2421357-2D0B-B9BA-A2B2-6F5794E9859B is not valid, or the FlexClient with that id is not valid.
というログが残っていた。このエラーメッセージを表示していたflex.messaging.endpoints.BaseStreamingHTTPEndpoint.javaの1005行目から1019行目までをコメントしてみたらどうかということに(^^;ソースいじり放題だな)
/*if (!command.equals(CLOSE_COMMAND) && !validFlexClientId)
        {
            if (Log.isError())
                log.error("Endpoint with id '" + getId() + "' cannot service the streaming request as either the supplied"
                        + " FlexClient id '" + flexClientId + " is not valid, or the FlexClient with that id is not valid.");

            try
            {
                // Return an HTTP status code 400 to indicate that the client's request was syntactically invalid (invalid id).
                res.sendError(HttpServletResponse.SC_BAD_REQUEST);
            }
            catch (IOException ignore)
            {}
            return; // Abort further server processing.
        }*/
またビルドしなおしてローカルで動作確認→OK!→GAE/Jにデプロイ→GAE/J上で動作確認→失敗orz

ログにエラーは出なくなったが、Consumerにsubscribeしてもウンともスンとも言わない。もちろんチャットのメッセージを書いて送っても何のリアクションもない。動かない。。。


つまりはこれがクラウドや分散環境ということかもしれない。

Streamingのように静的に特定のサーバーとコネクションをはり続けるようなアプリケーションは構造的にムリというか向かないということだろう。1つのクライアントからのセッション情報が重複してしまうという最初のトラブルからそもそもそういうことだったのだ。

セッションがGAE/Jでは使えないということではないが、特定のサーバーとずっと通信するのではなく、分散環境で、どのサーバーと通信されているのか分からないし知らなくてもいいようなクラウドの世界では、セッションメインのWebアプリケーションの作成手法は一旦頭から追い出さないといけない。自分でサーバーもメンテし運用していると、そういう従来のクセがついつい出てしまう。

やってみてわかることもあるものだ。

2009年9月8日火曜日

Google App EngineでBlazeDSのLong Polling機能を試す

結論から言います。
できませんでした。orz

アドレスへアクセスして動作を確認しようとすると、一瞬、接続が確立されたように見えるのですが、その後すぐにConsumerがchannelFaultイベントをはき、ProducerもchannelFaultイベントをはく。そしてまた一瞬ConsumerがchannelConnectするものの、すぐにchannelFaultイベントをはくという動作になりました。結局接続が安定的に確立できず・・・

前回のMessagingのテスト状況からの変更点は以下。

1.WEB-INF/flex/services-config.xmlのchannelsにLongPollのチャンネルを追加
        
          
          
            true
            0
            60000
            3000
            100
          
        
BlazeDSのドキュメントによると、polling-interval-secondsタグではなくpolling-interval-millisと書いてある部分もあるので、どちらも試してみました。動作は変わりませんでした。

2.WEB-INF/flex/messaging-config.xmlのdefault-channelsにLongPollのチャンネルを追加
    
        
        
    
3. mxmlのChannelSetを変更
      private function onClickButton():void{
       
       consumer.unsubscribe();
       
       if(local.selected){
         pollAmfChannel.url="http://127.0.0.1:8080/messagebroker/amfpolling";
         longPollAmfChannel.url="http://127.0.0.1:8080/messagebroker/myamflongpoll";
       }else{
         pollAmfChannel.url = "http://xxx.appspot.com/messagebroker/amfpolling";
         longPollAmfChannel.url = "http://xxx.appspot.com/messagebroker/myamflongpoll";
       }
       
       consumer.subscribe();
      }
    ]]>
  
  
  
    
  

  
    
  

 
 
ConsumerもProducerもchannelSetの値を{pollChannelSet}にすると、前回の状況と同じで、単純にpollingを使ったMessagingになります。それを、どちらも{longPollChannelSet}を指定したり、Consumerのみ{longPollChannelSet}にしたりしましたが、結局、安定的に接続が確立されませんでした。

Comet的な処理はGAE/Jではできないという記事もあり、Long Pollingは今のところGAE/Jでは動作しないのかもしれません。Pullではなく、Push的な動作をGAE/J上で実現するには、何か別の手段を考えなければならない。

Google App EngineでBlazeDSのMessagingを試す

GAE上でBlazeのROは使えることは確認したが、Messagingを使ってチャットのようなNearリアルタイム通信は可能なのか試してみる。Messagingといっても、StreamingAMFChannelを使ったものや、AMFChannelのpollingを使ったものがあるが、とりあえずはStreamingAMFChannelが使えるかチェックしてみる。

[環境]

Remotingを試した時と変わっていません。
  • OS : Windows Vista SP2
  • IDE : Eclipse v3.4.2(Ganymade)
  • SDK : appengine-sdk-1.2.5/JDK1.6.0_07/Flex Builder3.2.0
  • ライブラリ : BlazeDS3.2.0.3978

[サーバーサイド]

1.WEB-INF/flex/messaging-config.xmlにdestinationとしてid=chatを追加

GAEのプロジェクト配下にあるWEB-INF/flex/messaging-config.xmlにdestinationを追加。これがmxml側で指定するdestination先になる。


    
        
        
    

    
        
    
    
    



2.WEB-INF/flex/services-config.xmlのchannelsにStreamingAMFChannelが設定されていることを確認
    

        
            
        

    

[クライアントサイド]

1.mxmlを作成

ローカルとGAEへデプロイした時と接続するチャンネルのパスが変わるので、ラジオボタンで簡単に接続先を設定できるようにしています。BlazeDSのサンプルにあるtestdrive-chatをベースにしています。

  
  
    
  
  
    


 
 

  
    
    
    
  

  
    
    
       
       
    
  
  


2.swfなどのコンテンツをGAEのプロジェクトへ配置

swf、html、AC_OETags.js、playerProductInstall.swfをGAEプロジェクト下のwarに配置。history機能を使う場合はhistoryフォルダもwarに配置する。


[動作確認]

ローカル上で動作確認。Streaming通信に
失敗・・・ノーorz
2009/09/08 4:57:25 com.google.apphosting.utils.jetty.JettyLogger warn
警告: /messagebroker/streamingamf
java.security.AccessControlException: access denied (java.lang.RuntimePermission modifyThread)
 at java.security.AccessControlContext.checkPermission(AccessControlContext.java:323)
 at java.security.AccessController.checkPermission(AccessController.java:546)
 at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
 at com.google.appengine.tools.development.DevAppServerFactory$CustomSecurityManager.checkPermission(DevAppServerFactory.java:139)
 at com.google.appengine.tools.development.DevAppServerFactory$CustomSecurityManager.checkAccess(DevAppServerFactory.java:178)
 at java.lang.Thread.checkAccess(Thread.java:1263)
 at java.lang.Thread.setName(Thread.java:1050)
 at flex.messaging.endpoints.BaseStreamingHTTPEndpoint.handleFlexClientStreamingOpenRequest(BaseStreamingHTTPEndpoint.java:833)
 at flex.messaging.endpoints.BaseStreamingHTTPEndpoint.serviceStreamingRequest(BaseStreamingHTTPEndpoint.java:1022)
 at flex.messaging.endpoints.BaseStreamingHTTPEndpoint.service(BaseStreamingHTTPEndpoint.java:430)
 at flex.messaging.MessageBrokerServlet.service(MessageBrokerServlet.java:322)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:806)
 at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:487)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1093)
 at com.google.apphosting.utils.servlet.TransactionCleanupFilter.doFilter(TransactionCleanupFilter.java:43)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
 at com.google.appengine.tools.development.StaticFileFilter.doFilter(StaticFileFilter.java:121)
 at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
 at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:360)
 at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
 at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
 at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:712)
 at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
 at com.google.apphosting.utils.jetty.DevAppEngineWebAppContext.handle(DevAppEngineWebAppContext.java:54)
 at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:139)
 at com.google.appengine.tools.development.JettyContainerService$ApiProxyHandler.handle(JettyContainerService.java:313)
 at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:139)
 at org.mortbay.jetty.Server.handle(Server.java:313)
 at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:506)
 at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:844)
 at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:644)
 at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:211)
 at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:381)
 at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:396)
 at org.mortbay.thread.BoundedThreadPool$PoolThread.run(BoundedThreadPool.java:442)
GAEのセキュリティサンドボックスにひっかかってしまったようだ。よって、Martinの記事を参考に、StreamingAMFChannelではなく、AMFChannelのpollingを使って動作チェックしてみる。
参考:Martin's Blog: AppEngine & BlazeDS (Messaging)


[エラー解決までの道のり]

1.mxmlでStreamingAMFChannelを使っていた箇所をAMFChannelのpollingを使った記述へ変更。以下、変更部部分。
      private function onClickButton():void{
       
       consumer.unsubscribe();
       
       if(local.selected){
        amfchannel.url="http://127.0.0.1:8080/messagebroker/amfpolling";
       }else{
        amfchannel.url = "http://xxx.appspot.com/messagebroker/amfpolling";
       }
       
       consumer.subscribe();
      }
    ]]>
  
  
  
    
  

2.WEB-INF/flex/services-config.xmlのchannelsにAMFChannelが設定されていることを確認
2秒おきにポーリングするようにしてみました。
    

        
            
            
                true
                2
            
        

    


3.通信テスト
ローカル上で通信が確立することを確認!イェイ。
GAE上にデプロイして動作確認。
通信成功!やたっ!

結局、polling使わないとGAEではダメだった。今度はLong Pollingを試してみよう。

2009年9月7日月曜日

Google App EngineでBlazeDSのRemoting通信を試す

Google App Engine(以下GAE/J)でBlazeDSのRemotingObjectを拡張したリモーティング通信を試してみる。

[環境]
  • OS : Windows Vista SP2
  • IDE : Eclipse v3.4.2(Ganymade)
  • SDK : appengine-sdk-1.2.5/JDK1.6.0_07/Flex Builder3.2.0
  • ライブラリ : BlazeDS3.2.0.3978

[サーバーサイド]

1.Eclipse上でWeb Application Projectとして新規プロジェクトを作成

2.1で作ったプロジェクト下にBlazeDS用のライブラリや設定ファイルをコピー

デプロイ図
BlazeDSをダウンロードして展開するとblazeds.warがある。それが基本的にBlazeDSの動作に最低限必要なコンポーネントになっている。

なのでblazeds.warを展開し、WEB_INFの下にある、flexフォルダ(4つのxmlファイルがある)、及びlibフォルダ(12個のjarファイルがる)をコピーし、GAE/Jのプロジェクト/war/WEB-INF下に配置する。

プロジェクト内のリソースの配置は以下。赤枠は新規追加したファイル。青枠はプロジェクト作成時に自動生成されるファイルで変更が必要なファイル。









3.クラス作成

動作確認のため、HelloWorld的な簡単な文字列を返すクラスを作ってみる。コードはこんな感じ。
package jp.co.tricell.sonicboom.rpc;
import java.util.logging.Logger;
public class HelloDS {
 // ロガーを指定
 private static final Logger log =  Logger.getLogger(HelloDS.class.getName());
 public String sayHello(String name) {
  log.info("input str = " + name);
  return "こんにちは、" + name + "さん!";
 }
}

4.WEB-INF/flex/remoting-config.xmlにdestinationを追加

HelloDSクラスをリモーティング接続先として設定ファイルに追加する。SyntaxHighlighterが一行だけで閉じるxmlのタグの書き方に対応していないのでワザワザ</adapter-definition>と書いてますが、実際は<adapter-definition id=~ default="true"/>でOKです。


    
        
    

    
        
    
    
    
          
              jp.co.tricell.sonicboom.rpc.HelloDS
          
    



5.WEB-INF/flex/services-config.xmlのsystemタグにmanageableの値falseを追加

これは今回の環境で必要な設定なのか不明だが、GAE/J上でBlazeDSを動作させるために参考にしたいくつかのページで記述されていたので設定として追加。
参考:
・Martin's Blog: AppEngine & Adobe BlazeDS (fix)
・Flex/AIRハマり帳 ~第2回 Google App Engine for Java+BlazeDSでハマらない方法・後編~ | デベロッパーセンター
    
 
        
        false
 
        
            false
            
        
    

6.WEB-INF/appengine-web.xmlのsessions-enabledタグに値trueを追加

セッションを有効にするため設定を追加する。
 true

 
 
  
 

7.WEB-INF/web.xmlにMessageBroker Servletを追加

  
        flex.messaging.HttpFlexSession
  
  
  
    
        MessageBrokerServlet
        MessageBrokerServlet
        flex.messaging.MessageBrokerServlet
        
            services.configuration.file
            /WEB-INF/flex/services-config.xml
       
       1
    
    
        MessageBrokerServlet
        /messagebroker/*
    

[クライアントサイド]

Airアプリを作ってみました。encpoint先はお持ちのGAE/Jのドメインを指定してください。
タグを含んだコードの場合、SyntaxHighlighterがコードを全て、Firefoxなどは小文字(<mx:WindowedApplication~→<mx:windowedapplication~など)、Internet Explorerの場合は大文字に展開してしまいます。コードをコピーする場合は大文字小文字に注意してください。

  
  
    
  
   
      
      
      
   


[動作確認]

Airアプリをクライアントとしてつくり、HelloDSと通信させてみた。但し!!!ここで問題が・・・
GAE/J上にデプロイしたクラスと通信させてみたら、以下のようなメッセージが表示され実行失敗orz。
[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"]
at mx.rpc::AbstractInvoker/http://www.adobe.com/2006/flex/mx/internal::faultHandler()[C:\autobuild\3.2.0\frameworks\projects\rpc\src\mx\rpc\AbstractInvoker.as:220]
at mx.rpc::Responder/fault()[C:\autobuild\3.2.0\frameworks\projects\rpc\src\mx\rpc\Responder.as:53]
at mx.rpc::AsyncRequest/fault()[C:\autobuild\3.2.0\frameworks\projects\rpc\src\mx\rpc\AsyncRequest.as:103]
at NetConnectionMessageResponder/statusHandler()[C:\autobuild\3.2.0\frameworks\projects\rpc\src\mx\messaging\channels\NetConnectionChannel.as:569]
at mx.messaging::MessageResponder/status()[C:\autobuild\3.2.0\frameworks\projects\rpc\src\mx\messaging\MessageResponder.as:222]
GAE/Jが自動的に複数のサーバノード上でサーブレットを展開するために、1つのクライアントからのセッション情報が重複したように見えてしまうのが原因らしい。参考サイトを元にソースコードを編集してビルドする。
参考:http://prepro.wordpress.com/2009/05/17/googleappengineでblazeds環境を構築してみた


[エラー解決までの道のり]

1.BlazeDSのソースコードをダウンロード
BlazeDSのページからソースコードをダウンロードする。

2.BaseHTTPEndpoint.javaのコードの一部をコメント
ソースコードのzipを展開。編集が必要なソースは、flex.messaging.endpoints.BaseHTTPEndpoint.java。 ここの405行目からのduplicateSessionDetectedのチェックをコメントする。
/*if (duplicateSessionDetected)
        {
            List sessions = flexClient.getFlexSessions();
            for (Iterator iter = sessions.iterator(); iter.hasNext();)
            {
                FlexSession session = (FlexSession)iter.next();
                if (session instanceof HttpFlexSession)
                    session.invalidate();
            }
            
            // Return an error to the client.
            DuplicateSessionException e = new DuplicateSessionException();
            e.setMessage(ERR_MSG_DUPLICATE_SESSIONS_DETECTED);
            throw e;
        }*/
return flexClient;

3.ソースのビルドのため、Antをダウンロード
Apache Ant 1.7.1をダウンロード。

4.ソースのビルドのため、Ant-CONTRIBをダウンロード
Ant-CONTRIB 1.0b3をダウンロード。
展開してできるlibフォルダ内に有るant-contrib-1.0b3.jarをantフォルダのlib内にコピペ。

5.環境変数としてANT_HOMEを設定

6.環境変数PATHに%ANT_HOME%\binを追加

7.ビルド実行
BlazeDSのソースコードを展開したフォルダから
#ant clean
#ant make
を実行。
ビルド失敗orz
BUILD FAILED
C:\blazeds-src-3.2.0.3978\build.xml:73: The following error occurred while executing this line:
build.xml:156: Unable to rename old file
(C:\blazeds-src-3.2.0.3978\lib\flex-messaging-common.jar) to temporary file
編集したコードが含まれるflex-messaging-common.jarが権限が足りず変更できないらしい。はぅー。
参考:Adobe Forums: build blazeDS source code

結局Windows環境では権限をフルコントロールにしてもビルドエラーがどないもこないも解決できなかったので、Macでビルド。
ビルド成功!ほー(^-^;)

新しく出来た、flexから始まる5つのjarをGAE/JのプロジェクトのWEB-INF/libにコピペ。

8.通信テスト
GAE/J上にデプロイし、Airクライアントと通信。
通信成功ぉ~~!やたっ!

Windows環境でビルドに困る人のために、修正したコードでビルド済みのjarファイル5つを公開しま~す。この5つのjarをGAE/JのプロジェクトのWEB-INF/libにコピペしてGAE/Jへデプロイすればビルドせずにリモーティング通信できると思います。
元ソース:
blazeds-src-3.2.0.3978.zip

修正後jarファイル:
flex-messaging-common.jar
flex-messaging-core.jar
flex-messaging-opt.jar
flex-messaging-proxy.jar
flex-messaging-remoting.jar