Rails3からmemcachedを利用する

Rails3では、構成要素のモジュール化が進み、標準のライブラリ以外は個別にインストールする方針になりました。memcachedクライアントの実装も、インストール直後の状態では含まれていないので、自分でインストールする必要があります。

一方で、ライブラリの選択がとても容易にできることは、モジュール化の恩恵と言えるでしょう。
今回は、Rails3で利用できるmemcachedクライアントとして、memcache-client と Dalli を紹介します。両者の違いは設定手順だけで、利用方法は同じです。

memcache-clientを使う

http://github.com/mperham/memcache-client

Rails2時代と同様、memcache-client を利用することができます。Gemfileに以下の行を追加して、bundle install すればインストール完了です。

gem 'memcache-client'

■キャッシュストアに設定

production環境で使用する場合は、config/environments/production.rb に次の行を追加します。接続先を指定しなければ、localhost:11211のmemcachedに接続します。

config.cache_store = :mem_cache_store

次のように、複数の接続先やオプションを指定することもできます。

config.cache_store = :mem_cache_store, 'cache-1.example.com', 'cache-2.example.com', {:namespace => NAME_OF_RAILS_APP}

■セッションストアに設定

セッションストアをデフォルトのCookieStoreから変更することもできます(あまり機会はないかもしれませんが..)。変更するには、config/initializers/session_store.rb に以下の行を追加します.

Railsアプリ名::Application.config.session_store :mem_cache_store
Railsアプリ名::Application.config.session_options = {:cookie_only => false}

こちらもキャッシュストアと同様、細かく設定することができます。

Dalliを使う

http://github.com/mperham/dalli

Dalli は、高速なpure Rubyのmemcachedクライアントです。memcached 1.4+に対応していて、バイナリプロトコルを使用しています。
開発者は、memcache-clientのメンテナンスもしている Mike Perham さんです。「記憶の固執(The Persistence of Memory)」を描いたサルバトール・ダリにちなんだ名前だとのことです。

Dalli – memcached for Ruby

Dalli には以下の特徴があります。

  • memcache-client と互換性があるので、そのまま入れ替えできる
  • Ruby 1.9.2 では約20%高速
  • モニタリングツール用にフックを提供
  • Railsに対応
  • memcache-clientの1250行から、700行にまでダイエット
  • SASLサポート

こちらもインストールは簡単です。Gemfile に下記の行を追加して、bundle install してください。

gem 'dalli'

■キャッシュストアに設定

production環境で使用する場合は、config/environments/production.rb に次の行を追加します。何も指定しなければ、localhost:11211 のmemcachedに接続します。

config.cache_store = :dalli_store

以下のように、こってりした設定をすることも可能です。

config.cache_store = :dalli_store, 'cache-1.example.com', 'cache-2.example.com', {:namespace => NAME_OF_RAILS_APP, :expires_in => 3600, :compress => true, :compress_threshold => 64*1024}

Passenger で使う場合は、config/environment.rb に設定を追加する必要があるようです。詳しくは、http://github.com/mperham/dalli を参照してください。

■セッションストアに設定

config/initializers/session_store.rb に以下の行を追加します.

require 'action_dispatch/middleware/session/dalli_store'
Railsアプリ名::Application.config.session_store :dalli_store
Railsアプリ名::Application.config.session_options = {:cookie_only => false}

試してみる

コンソールから実験してみましょう。
ここからは、memcache-client でも Dalli でも大きな違いはないので、Dalli を使った場合の結果で説明します。

$ RAILS_ENV=production rails server
> Rails.cache
 => #<ActiveSupport::Cache::DalliStore:0x000001020ce600 @options={}, @data=#<Dalli::Client:0x000001020ce0b0 @servers=["localhost:11211"], @options={}>, @thread_local_key=:active_support_cache_dalli_store_local_cache_2164683520, @middleware=ActiveSupport::Cache::Strategy::LocalCache>

キャッシュストアが DalliStore になっていることがわかりますね。

> Rails.cache.write("test", {:message => 'Hello', :time => Time.now})
 => true
> Rails.cache.read("test")
 => {:message=>"Hello", :time=>2010-09-15 13:26:09 +0900}

無事データをmemcacheにキャッシュし、その後取り出すことができました!

終わりに

memcached のアスキープロトコルには、memcached injection による攻撃を受けやすいという弱点があります。memcached injection への対策の一つは、memcached のバイナリプロトコルを使用することです。
Dalli はバイナリプロトコルを使用しているので、セキュリティの面でも優れています。Rails で memcached を使用する場合、クライアントライブラリとして Dalli を選択するのがよさそうですね。

ニフティクラウドlarge16 VMとEC2 Cluster Compute VMとでLINPACK Benchmark

Amazon EC2でCluster Computeサービスが提供されて久しいですが、ニフティクラウドで提供されているVMもなかなかの性能であるという事を耳にしたので、いっちょ試してみる事にしました。比較したのは、両サービスで利用できる最上位モデルである、以下の2タイプのVM。

  • Amazon EC2 cc1.4xlarge .. 33.5ECU, 23GB Memory, CentOS 5.4 HVM AMI(64bit), 1.60USD/h
  • ニフティクラウド large16(VM0416) .. 4vCPU, 16GB Memory, CentOS 5.3 64bit Server, 159.6円/h

vCPU(仮想CPU)や、ECU(EC2 Compute Unit)の辺りは定義が異なるため単純な比較が難しいのですが、これだけ見ると、なんとなく、EC2の方が凄い印象を受けます。が、結論から書くと、LINPACKによるFLOPS値ベースのベンチマーク測定では、ニフティクラウドの方に軍配が上がりました。

続きを読む

mod_auth_opensocial をリリースしました

mod_auth_opensocial 0.1.0 をリリースしました。

mod_auth_opensocial は、OpenSocial アプリからサーバに届いた署名付きリクエストの検証(Verification)を行うための Apache 2.0, 2.2 用のモジュールです。

署名付きリクエストとは、OpenSocial の makeRequest 関数 (AUTHORIZATIONパラメータにSIGNEDを指定) により生成され、コンテナにより署名されたリクエストです。

mod_auth_opensocial が署名の検証に成功すると、リクエストは通常通り(HTMLの取得や、PHP、Servlet Containerへのアクセスなど)に処理されます。 検証に失敗すると、HTTPエラーをリクエストの送信者へ返します。
バックエンドが受け取るリクエストは全て署名の検証に成功したものなので、バックエンドはリクエストを再度検証する必要が無くなります。
リクエストに署名が付加されているかどうかすらバックエンドは気にする必要がありません。

尚、本プロジェクトは Google Code で管理しています。
http://code.google.com/p/mod-auth-opensocial/

■ インストール

mod_auth_opensocial は openssl の機能を使用していますので、Apache に mod_ssl が組み込まれている必要があります。

Apache のインストール

※ configure コマンドへのオプションの –prefix と –enable-mods-shared の値は適宜変更してください。
またシステムによっては、 –with-ssl オプションで openssl へのパスを指定する必要があるかもしれません。 (例: –with-ssl=/usr/local/openssl )

#./configure 
--prefix=/usr/local/apache \ --enable-mods-shared=most \ --enable-so \ --with-included-apr \ --enable-ssl # make # make install

mod_auth_opensocial のインストール

# tar xvzf mod_auth_opensocial-0.1.0tar.gz
# cd mod-auth-opensocial-0.1.0
# vi Makefile APACHE_BASE=PATH_TO_APACHE の行の PATH_TO_APACHE を Apache を↑でインストールしたディレクトリに変更してください。 APACHE_BASE=/usr/local/apache # make # make install

httpd.conf の変更

httpd.conf 内の LoadModule ディレクティブ が記述されている箇所に↓の行を追加してください。

LoadModule auth_opensocial_module modules/mod_auth_opensocial.so

Apache をリスタートすると mod_auth_opensocial がロードされます。
ただし、この段階では設定を行っていないので、まだ何も起こりません。

■ mod_auth_opensocial の設定

Apache が http://www.example.com という URL で稼働しているとします。
http://www.example.com/secure/ の以下全てのディレクトリ(パス) へのアクセスを検証するには、以下のように設定します。

<Location "/secure/">
   AuthType OpenSocial
   Require valid-user

   AuthOpenSocialEnabled On

   AuthOpenSocialAcceptSignatureMethods hmac-sha1 rsa-sha1

   AuthOpenSocialConsumerKey 4345983491233
   AuthOpenSocialConsumerSecret wa/nx35kdmerLwQZp8AfmoX

   AuthOpenSocialRSAPublicKeyFile ./conf/pub.1199819524.-1556113204990931254.cer
</Location>

※ consumer secret の値を記述しているので、httpd.conf のパーミッションには注意を払ってください。

AuthType Require ディレクティブは、必ず必要なので忘れずに記述してください。

それでは mod_auth_opensocial 個別のディレクティブを見ていきましょう。

AuthOpenSocialEnabled :

引数の数 : 1
説明 : このモジュールの有効、無効を指定します。 指定の方法は、有効の場合には On を、無効にする場合には Off を指定します。

AuthOpenSocialAcceptSignatureMethods :
引数の数 : 1
説明 : 受け付ける署名方式です。 hmac-sha1, rsa-sha1 のどちらか、もしくは両方(スペース区切り)を指定します。

AuthOpenSocialConsumerKey :

引数の数 : 1
説明 : OpenSocial サービスから提供される Consumer Key です。 AuthOpenSocialAcceptSignatureMethods ディレクティブに hmac-sha1 を指定した場合に必須となります。

AuthOpenSocialConsumerSecret :

引数の数 : 1
説明 : OpenSocial サービスから提供される Consumer Secret です。AuthOpenSocialAcceptSignatureMethods ディレクティブに hmac-sha1 を指定した場合に必須となります。

AuthOpenSocialRSAPublicKeyFile :

引数の数 : 1
説明 : OpenSocial サービスから提供される RSA PublicKey を保存したファイルへのパスを指定します。 AuthOpenSocialAcceptSignatureMethods ディレクティブに rsa-sha1 を指定した場合に必須となります。

上の設定例では用いていませんが、以下のディレクティブもオプションで指定可能です。

AuthOpenSocialRequiredKey :

引数の数 : 1
説明 : 必須HTTPパラメータ (query string 又は post data) の key を指定します。リクエストのパラメータにこの値が存在しなければ、HTTPエラーをリクエスト送信者へ返します。

AuthOpenSocialRequiredKeyAndValue :

引数の数 :2
説明 : 必須HTTPパラメータ (query string 又は post data) の key と値のペアをスペース区切りで指定します。リクエストのパラメータに、このディレクティブで指定したkeyと値のペアが含まれていなければ、HTTPエラーをリクエスト送信者へ返します。

今回リリースした mod_auth_opensocial の バージョン 0.1 では、ある一つの URL に対して1つの consumer key や rsa publickey しか設定できないため、複数のOpenSocial サービスからの署名リクエストを1つのLocationディレクティブでさばくことができません。

例えば、http://www.example/myapp/ に対して mixi アプリとOrkut の両方からの署名リクエストの検証は行えないということです。 Locationタグには、1つだけOpenSocial サービスの設定を行ってください。
この制限や、他にも課題が多数ありますが、今後のバージョンで対応していきます。

このモジュールの使いどころやOpenSocialについて、このブログに順次アップしていきます。
この機会に是非 RSSの購読をお願いいたします。

速攻で作る OpenSocialアプリ ( RSA-SHA1 )

前回のブログにて OpenSocial Client Library Ruby版に付属している Gifts アプリを動作させるまで説明しました。

makeRequestの署名方式に RSA-SHA1 を用いる

OpenSocialコンテナ(Orkut) -> 外部サーバへのリクエストの署名方式には HMAC-SHA1 を用いていましたが、今回はより一般的な RSA-SHA1 を用いた方法を説明します。

HMAC-SHA1では consumer key と consumer secret の2つが必要でしたが、RSA-SHA1ではそれらは必要なく、公開鍵だけが必要となります。

■ OpenSocialコンテナ(Orkut) -> 外部サーバ のリクエストの署名方式を HMAC-SHA1 から RSA-SHA1 へ変更

GIFT_SAMPLE/public/gifts.xml の 14行目辺りのparams["OAUTH_SERVICE_NAME"] = “HMAC”;
をコメントアウトしてください。
この変更により OpenSocialコンテナ(Orkut) -> 外部サーバへのリクエストの署名方式がデフォルトの RSA-SHA1 になります。

■ Rails側で RSA-SHA1 にて署名されたリクエストを受け取れるようにする

○ 公開鍵の取得

OpenSocialコンテナ(Orkut) -> 外部サーバへのリクエストのパラメータに
xoauth_signature_publickey=pub.1199819524.-1556113204990931254.cer
というkeyとvalueが付与されてきますが、この pub.1199819524.-1556113204990931254.cer ファイルが公開鍵となります。

で、そのファイルはどこにあるのかというと http://sandbox.orkut.com/46/o/pub.1199819524.-1556113204990931254.cer にあります。

xoauth_signature_publickeyの値が変更されたらどうするんだ?という疑問が浮かぶかもしれませんが、Orkutの場合は、xoauth_signature_publickeyの値が変わる時にはGoogleが前もって告知してくれますので、現状はこの公開鍵のファイルの内容をソースコードにハードコーディングしておいても大丈夫だと思います。
※逆に、自動で公開鍵ファイルを取得するような実装は危険です。そのファイルが正規のものかどうか判断のしようがありませんので。

○ gifts コントローラの修正

opensocial client library ( gem opensocial-0.0.2 )ですが、何故か署名方式に HMAC-SHA1 しか使用できないようになっています・・・。
RSA-SHA1の署名方式が使用できるようにします。

gifts コントローラ内で require ‘oauth/signature/rsa/sha1′ を行います。

また check_signature メソッド内にてコールされている validate メソッド(OpenSocial::Auth#validate(…)) では opensocial client が内部で用いている oauth ライブラリの Consumer クラスのコンストラクタに options を渡せない(署名方式を指定できない)ので、その validate メソッドを用いず、新たに validate メソッドを作成します。

上記全てを反映した gifts コントローラは下のようになります。

class GiftsController < ApplicationController
  include OpenSocial::Auth

  require 'oauth/signature/rsa/sha1'


# 公開鍵のハードコーディング
CERT = <<"EOS"
-----BEGIN CERTIFICATE-----
MIIDHDCCAoWgAwIBAgIJAMbTCksqLiWeMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIG
A1UEChMLR29vZ2xlIEluYy4xDjAMBgNVBAsTBU9ya3V0MQ4wDAYDVQQDEwVscnlh
bjAeFw0wODAxMDgxOTE1MjdaFw0wOTAxMDcxOTE1MjdaMGgxCzAJBgNVBAYTAlVT
MQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChML
R29vZ2xlIEluYy4xDjAMBgNVBAsTBU9ya3V0MQ4wDAYDVQQDEwVscnlhbjCBnzAN
BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAseBXZ4NDhm24nX3sJRiZJhvy9eDZX12G
j4HWAMmhAcnm2iBgYpAigwhVHtOs+ZIUIdzQHvHeNd0ydc1Jg8e+C+Mlzo38OvaG
D3qwvzJ0LNn7L80c0XVrvEALdD9zrO+0XSZpTK9PJrl2W59lZlJFUk3pV+jFR8NY
eB/fto7AVtECAwEAAaOBzTCByjAdBgNVHQ4EFgQUv7TZGZaI+FifzjpTVjtPHSvb
XqUwgZoGA1UdIwSBkjCBj4AUv7TZGZaI+FifzjpTVjtPHSvbXqWhbKRqMGgxCzAJ
BgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEU
MBIGA1UEChMLR29vZ2xlIEluYy4xDjAMBgNVBAsTBU9ya3V0MQ4wDAYDVQQDEwVs
cnlhboIJAMbTCksqLiWeMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEA
CETnhlEnCJVDXoEtSSwUBLP/147sqiu9a4TNqchTHJObwTwDPUMaU6XIs2OTMmFu
GeIYpkHXzTa9Q6IKlc7Bt2xkSeY3siRWCxvZekMxPvv7YTcnaVlZzHrVfAzqNsTG
P3J//C0j+8JWg6G+zuo5k7pNRKDY76GxxHPYamdLfwk=
-----END CERTIFICATE-----
EOS


  # Declares the keys for your app on different containers. The index is the
  # incoming consumer key sent in the signed makeRequest from the gadget.
  # These are sample gadget credentials and should be replaced with the HMAC
  # keys granted to your own gadget.
  KEYS = {
    'XXXXXXXXXX' => {
      :secret => 'XXXXXXXXXXXXXXXXXXXXXXXXXX',
      :outgoing_key => 'orkut.com:XXXXXXXXXX',
      :container => OpenSocial::Connection::ORKUT
    }
  }

  # Declares where your application lives. Points to the page where users are
  # redirected after loading the iframe.
  SERVER = 'http://www.example.com/'

  # Controls the auto-validation of the signed makeRequest.
  before_filter :check_signature, :only => [:iframe]

  # Renders pages with boilerplate HTML/CSS/JS.
  layout 'default'

  # Implicitly checks the signature of the incoming request and saves the
  # owner id, viewer id, and consumer key in a temporary session for later use.
  # The session id is returned as an iframe snippet that the gadget can use to
  # render the app.
  def iframe
    session[:id] = params[:opensocial_owner_id]
    session[:viewer] = params[:opensocial_viewer_id]
    session[:consumer_key] = params[:oauth_consumer_key]

    render :text => "<iframe width='98%' height='600px' frameborder='0' src='#{SERVER}?sessid=#{session.model.session_id}' />"
  end

  # Loads the temporary session referenced by sessid, copies the values into
  # a persistent session that will be used to actually drive the app, and
  # deletes the temporary session (to prevent replay). Then, initiates a
  # connection to the given container and renders the appropriate content.
  def index

    proxied_session = session.model.class.find(:first,
                        :conditions => ['session_id = ?', params[:sessid]])

    if params[:sessid] && proxied_session
      session[:id] = proxied_session.data[:id]
      session[:viewer] = proxied_session.data[:viewer]
      session[:consumer_key] = proxied_session.data[:consumer_key]
      proxied_session.destroy
    end

    c = OpenSocial::Connection.new(:container => KEYS[session[:consumer_key]][:container],
                                   :consumer_key => KEYS[session[:consumer_key]][:outgoing_key],
                                   :consumer_secret => KEYS[session[:consumer_key]][:secret],
                                   :xoauth_requestor_id => session[:id])

    if session[:id] == session[:viewer]
      render_owner(c)
    elsif session[:viewer] && session[:id] != session[:viewer]
      render_viewer_with_app(c)
    else
      render_viewer_without_app(c)
    end
  end

  # Sends a gift on behalf of the viewer. If the viewer is the owner, the gift
  # is sent to the selected friend (but doesn't check to confirm that the owner
  # is friends with the specified ID). If the viewer is not the owner, the gift
  # is sent to the owner. Then the app redirects to the index.
  def give
    gift = Gift.new(params[:gift])
    if session[:id] != session[:viewer]
      gift.sent_by = session[:viewer]
      gift.received_by = session[:id]
    else
      gift.sent_by = session[:id]
    end

    if gift.save
      redirect_to :action => :index
    else
      flash[:error] = 'Error giving gift.'
      redirect_to :action => :index
    end
  end

  private

  # If the viewer is also the owner, this will render the list of gifts the
  # owner has sent or received. The owner can elect to send a gift to a friend.
  def render_owner(c)
    @id = session[:id]
    @gifts = Gift.find(:all,
                       :conditions => ['sent_by = ? OR received_by = ?', @id, @id],
                       :order => 'created_at DESC', :limit => 10)
    @gift_names = GiftName.find(:all)

    @people = fetch_gift_givers_and_friends(@id, @gifts, c)
    @owner = @people.delete(@id)

    render :action => "owner"
  end

  # If the viewer has the app installed, this will render the list of gifts
  # exchanged between the owner and viewer. The viewer can elect to send a gift
  # to the owner.
  def render_viewer_with_app(c)
    @id = session[:viewer]
    @owner_id = session[:id]

    @gifts = Gift.find(:all,
                       :conditions => ['(sent_by = ? AND received_by = ?) OR ' +
                                       '(sent_by = ? AND received_by = ?)',
                                       @owner_id, @id, @id, @owner_id],
                       :order => 'created_at DESC', :limit => 10)
    @gift_names = GiftName.find(:all)

    @viewer = OpenSocial::FetchPersonRequest.new(c, @id).send
    @owner = OpenSocial::FetchPersonRequest.new(c).send

    render :action => "viewer_with_app"
  end

  # If the viewer doesn't have the app installed, this will render the list of
  # gifts the owner has sent or received (without notes). No gifts may be sent.
  def render_viewer_without_app(c)
    @id = session[:id]
    @gifts = Gift.find(:all,
                       :conditions => ['sent_by = ? OR received_by = ?', @id, @id],
                       :order => 'created_at DESC', :limit => 10)
    @gift_names = GiftName.find(:all)

    @people = fetch_gift_givers_and_friends(@id, @gifts, c)

    render :action => "viewer_without_app"
  end

  # Requests social data for each of the people that have sent or received a
  # gift from the owner (oid). The method first generates a unique list of
  # user ids from the list of gifts, and sends a REST request for their data.
  # Users without the app installed will trigger an exception which is caught.
  # Finally, the owner's friends are fetched (which fills in the missing spaces
  # where insufficient permissions are granted to collect the user data
  # directly) and merged with the existing data.
  # This function could be sped up considerably by using a single RPC request
  # if the container supports it.
  def fetch_gift_givers_and_friends(oid, gifts, c)
    people = {}
    ids = gifts.collect {|g| [g.sent_by, g.received_by]}.flatten.uniq
    ids.each do |id|
      begin
        r = OpenSocial::FetchPersonRequest.new(c, id)
        people[id] = r.send
      rescue OpenSocial::AuthException
      end
    end
    friends = OpenSocial::FetchPeopleRequest.new(c, oid).send
    return people.merge(friends)
  end


  # Looks up the consumer secret paired with the given consumer key and
  # hands them off to the client library authentication.
  def check_signature

    is_valid_request = false

    if params[:oauth_signature_method] == 'HMAC-SHA1'
      # 署名方式が HMAC-SHA1 
      key = params[:oauth_consumer_key]
      is_valid_request = validate(key, KEYS[key][:secret], {:signature_method => 'HMAC-SHA1'})
    elsif params[:oauth_signature_method] == 'RSA-SHA1'
      # 署名方式が RSA-SHA1
      # RSA-SHA1 では 公開鍵のみ必要なので validate メソッドの 第1引数は nil. 公開鍵は第2引数に指定する )
      is_valid_request = validate(nil, CERT, {:signature_method => 'RSA-SHA1'})
    end

    unless is_valid_request
      render :text => '401 Unauthorized', :status => :unauthorized
    end

  end


  # opensocial client の validate メソッドを用いずに新たに作成.
  def validate(key = CONSUMER_KEY, secret = CONSUMER_SECRET, options={})

    consumer = OAuth::Consumer.new(key, secret, options)
    begin
      signature = OAuth::Signature.build(request) do
        [nil, consumer.secret]
      end
      pass = signature.verify
    rescue OAuth::Signature::UnknownSignatureMethod => e
      logger.error 'An unknown signature method was supplied: ' + e.to_s
    end
    return pass
  end


end

以上で、署名方式 RSA-SHA1 を用いたリクエストが受け取れるようになります。

が!

これだけではアプリケーションとしては動作しません。

何故かというと、index メソッド内にてユーザ情報を取得するために OpenSocialコンテナ(Orkut)へリクエストを送信するための Connection クラスのオブジェクトを作成しているのですが、そこで HMAC-SHA1 のデータが必要となるからです。

外部サーバ -> OpenSocialコンテナ(Orkut) へのリクエストには HMAC-SHA1 が必須となっていますので、結局前回のブログと同様に consumer key, consumer secret が必要になります。

それぞれを取得したら、下記のようにKEY定数を変更してます。

KEYS = {
    'orkut.com' => {
      :secret => '<CONSUMER SECRET>',
      :outgoing_key => 'orkut.com:<CONSUMER KEY>',
      :container => OpenSocial::Connection::ORKUT
    }

これにてアプリケーションとして動作します。

外部サーバ -> OpenSocialコンテナ(Orkut)へのリクエストが必要ないのであれば RSA-SHA1 を用いるのが良いと思いますが、外部サーバ -> OpenSocialコンテナ(Orkut)へのリクエストも必要だとすると 両方向のリクエストの署名方式に HMAC-SHA1 を用いるのが楽でいいかもしれません。

よういちろうさんが執筆された 「OpenSocial入門 ソーシャルアプリケーションの実践開発」が 2009年1月24日に発売されるとのことですので楽しみにしている今日この頃ですOpenSocialを用いたアプリ開発をされる方には必須となるのではないでしょうか。

速攻で作る OpenSocialアプリ

新年明けましておめでとうございます。


mixiアプリも採用の Open Social



日本最大のSNSである mixiが mixiアプリ にOpen Socialを採用したことにより、今年2009年は他社のサービスでもOpenSocialを採用するところが一気に増えてくるではないでしょうか。

そこで弊社Banana Systemsが2009年最初にお届けする記事は「速攻で作るOpenSocialアプリ」です。

OpenSocialの詳細につきましては、オフィシャル・ページ にて説明されていますのでここでは割愛します。

まず前提として、

Orkut用のソーシャル・アプリ

  今現在 mixiアプリはまだβ版であり一般には公開されていませんので、

  GoogleのSNSであるOrkutにてソーシャル・アプリを開発します。

自社サーバからデータの取得、保存

  HTML, Javascriptのみを用いたシンプルなソーシャル・アプリではなく、

  実際のサービスを考えた時には外部(自社)サーバからコンテンツ、

  データの取得、また逆に保存を行いますので、↓のような形式とします。

  外部サーバ <-> Orkut(OpenSocialコンテナ) <-> ソーシャル・アプリ



OpenSocial Client Libraryの使用

  2008年12月17日に OpenSocial Blog にてPHP, Java, Ruby, Python用のOpenSocial Client Library が発表されました。

  このライブラリはサーバ間(外部サーバ <-> OpenSocialコンテナ)通信の複雑なやり取りを隠ぺいしてくれます。

  Ruby用のものには Railsアプリのサンプルが入っていますので、今回はそのRailsアプリを動作させるまでを説明します。


OpenSocialアプリの構築



それでは始めていきましょう!



Getting started with the Ruby Client Library に大まかな説明がされているので、それに沿って進めていきますが、Orkut(OpenSocialコンテナ)から外部サーバへ通信する際に必要となるSigned 通信の設定方法が記述されていなかったり、migration
ファイルに誤りがありそのままでは動作しませんので、補足しながら説明していきます。



1. 必要なライブラリのインストール



 # gem install oauth json mocha rails



2. opensocial ライブラリのダウンロードとインストール



 ダウンロード ページ からopensocial-X.X.X.gem をダウンロードします。



 ダウンロードしたgemをインストールします。



 # gem install opensocial-X.X.X.gem



3. サンプルの Railsアプリのダウンロード



 ダウンロード ページ から gifts_sample_X.X.zip をダウンロードします。



 ダウンロードしたzipファイルを展開すると gifts_sampleディレクトリ(Railsアプリのトップ)が作成されますので、Railsアプリを設置する適切なディレクトリに移動してください。



4. DB設定



 GIFT_SAMPLE/config/database.ymlの設定を適切なものに変更し、DBにデータベースを作成してください。

 ※GIFT_SAMPLE は 当Railsアプリのトップディレクトリとします。



今回はMySQLを使用しました。

※database, username, password はサンプルですので適宜変更してください。


development:
  adapter: mysql
  encoding: utf8
  database: gifts_development
  pool: 5
  username: banana
  password: banana
  host: localhost

test:
  adapter: mysql
  encoding: utf8
  database: gifts_test
  pool: 5
  username: banana
  password: banana
  host: localhost

production:
  adapter: mysql
  encoding: utf8
  database: gifts_production
  pool: 5
  username: banana
  password: banana
  host: localhost

MySQL コンソールにて ( GRANT文は適宜変更してください )

> GRANT ALL PRIVILEGES ON *.* TO banana@localhost IDENTIFIED BY 'banana' WITH GRANT OPTION;
> flush privileges;
> create gifts_development DEFAULT CHARSET=utf8;
> create gifts_test DEFAULT CHARSET=utf8;
> create gifts_production DEFAULT CHARSET=utf8;

5. migrationファイルの修正



 GIFT_SAMPLE/config/20081201192139_create_gifts.rb に note カラムを追加します。

class CreateGifts < ActiveRecord::Migration
  def self.up
    create_table :gifts do |t|

      t.integer :gift_name_id
      t.string :sent_by
      t.string :received_by
      t.boolean :viewed
      
      # note カラムの追加
      t.string :note
      
      t.timestamps
    end
  end

  def self.down
    drop_table :gifts
  end
end

※通常はmigrationファイルを直接編集せずに ruby script/generate migration AddNoteToGift note:string コマンドにて新しい migrationファイルを作成しますが、DBにまだテーブルを作成していないので今回は migration ファイルを直接編集しました。↓の migration ファイルも同様に直接編集しています。


 GIFT_SAMPLE/config/20081201203248_create_gift_names.rb に ギフトを追加します。

 ※ Gift アプリはギフトを友達に贈ったりするものなのですが、ギフトはDBに保存しておかないといけません。

class CreateGiftNames < ActiveRecord::Migration
  def self.up
    create_table :gift_names do |t|
      t.string :name
      t.timestamps
    end
    
    # ギフトの追加
    
    GiftName.create(:id => 1, :name => 'book')
    GiftName.create(:id => 2, :name => 'coffee')
    GiftName.create(:id => 3, :name => 'candy')
    GiftName.create(:id => 4, :name => 'icecream')
    
    
  end

  def self.down
    drop_table :gift_names
  end
end

6. DBにテーブル作成



 Gift sample アプリのトップディレクトリにて

 # rake migrate



7. サーバの起動



 Gift sample アプリのトップディレクトリにて

 # ruby script/server



 上記コマンドにより、本アプリは http://www.example.com/にて動作しているとし以降の説明を進めます。


OrkutにてGiftsアプリのインストール



1. Orkutにアカウントのない方は、ここから アカウントを作成してください。



2. デベロッパー サンドボックスへの登録



 Orkutにてサンプルのアプリをインストールするには、サンドボックスに登録する必要があります。



 Orkutにログイン後、ここから 登録します。



 ※以前は登録が承認されるまでに数日要しましたが、現在はすぐに完了します。



3. Giftsアプリのインストール



 サイドメニューのアプリの編集をクリックします。



orkut_sidemenu_1













 URLに本Giftsアプリの下記URLを入力し、「アプリケーションを追加」ボタンをクリックします。



 http://www.example.com/gifts.xml



orkut_add_sample_app_1












 アプリケーション追加をクリックします。


orkut_add_sample_app_2





















 これでGiftsアプリのサンドボックスへのインストールは完了です。


外部サーバにてOrkut(OpenSocialコンテナ)からの署名付きのリクエストを受け付ける



 Gifts アプリでは、Orkutと外部サーバ間にてデータの改ざんが行われないように、Orkutから外部サーバへの通信には署名付けられるように実装されています。

 (GIFT_SAMPLE/public/gifts.xml の 13行目辺り params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.SIGNED; )

 実サービスにおいても、署名付きリクエストは必須となるでしょう。


追記: 2009-01-10 05:30 (コメント参照) ———
また、署名方式には HMAC-SHA1 を用いるように指定されています。
(GIFT_SAMPLE/public/gifts.xml の 14行目辺り params["OAUTH_SERVICE_NAME"] = “HMAC”; )

追記ここまで ————

この署名方式では Consumer Key と Consumer Secret Key が必要となります。

1. Consumer Key と Consumer Secret Key の取得



 Orkutでは、Consumer Key と Consumer Secret Keyを発行する際に、本アプリの所有者があなたか確認するためにトークンを発行します。



 下記ページにGiftsアプリのURL( http://www.example.com/gifts.xml )を入力し、「submit」ボタンをクリックします。

 https://www.google.com/gadgets/directory/verify



 すると、 <!– ALm6fM2iKDPf9qY5 … gMjzbD1cwDBifBX5w== –> といったトークンが発行されます。

 ※ブラウザは閉じずにそのままにしておいてください。



 GIFT_SAMPLE/public/gifts.xml の Content タグ内(CDATA開始タグとscriptタグの間)に発行されたトークンを挿入し、保存します。

 

<?xml version="1.0" encoding="UTF-8"?>
<Module>
  <ModulePrefs title="Gifts">
	  <Require feature="opensocial-0.8" />
	  <Require feature="dynamic-height"/>
  </ModulePrefs>
  <Content type="html">
    <![CDATA[
    <!-- ALm6fM2iKDPf9qY5 ... gMjzbD1cwDBifBX5w== -->
	    <script>
  	    // Sends a signed makeRequest to a specified remote server to load the iframe
  	    function sendSignedRequest(server) {

 さきほどのページにて「Verify」ボタンをクリックします。

 Giftsアプリの所有者があなたであることが確認されると、下記のように Consumer Key と Consumer Secret Key が発行されます。



OAuth Consumer Key: 294828716828

OAuth Consumer Secret: xw/zDlw3y4p4yRFZo2mCQCID



2. Controller の修正



 Gifts アプリの gifts controller にて、署名付きリクエストを受け付けられるようにします。



 GIFT_SAMPLE/app/controllers/gifts_controller.rb を開き、KEYS定数に先ほどのConsumer KeyとConsumer Secretを追加します。

  # Declares the keys for your app on different containers. The index is the
  # incoming consumer key sent in the signed makeRequest from the gadget.
  # These are sample gadget credentials and should be replaced with the HMAC
  # keys granted to your own gadget.
  KEYS = {
    # ↓追加
    
    '294828716828' => {
      :secret => 'xw/zDlw3y4p4yRFZo2mCQCID',
      :outgoing_key => 'orkut.com:294828716828',
      :container => OpenSocial::Connection::ORKUT
    },
    
    # ↓削除してしまってかまいません
    '649048920060' => {
      :secret => '2rKmqUigMbFqK783szqzzOky',
      :outgoing_key => 'orkut.com:649048920060',
      :container => OpenSocial::Connection::ORKUT
    },
    'http://opensocial-resources.googlecode.com/svn/samples/rest_rpc/sample.xml' => {
      :secret => '6a838d107daf4d09b7d446422f5e7a81',
      :outgoing_key => 'http://opensocial-resources.googlecode.com/svn/samples/rest_rpc/sample.xml',
      :container => OpenSocial::Connection::MYSPACE
    }
  }

 KESY定数はHashになっています。HashのKeyに先ほどの Consumer Key を指定します。そのkeyのvalueもHashになっていますので、secretには先ほどのConsumer Secretを指定します。また、outgoing_keyには orkut.com: に続けて再度 Consumer Key を指定します。



 SERVER 定数も変更します。

 

# Declares where your application lives. Points to the page where users are
  # redirected after loading the iframe.
  SERVER = 'http://www.example.com/'

 これにて署名付きリクエストが受け付けられるようになります。



 Giftsアプリが署名付きリクエストを外部サーバに送信する(実際には、リクエストを送信するのはOpenSocialコンテナ(今回はOrkutのサーバ))のはユーザが本アプリにアクセスした初めの一度だけです。

 これは Giftsアプリがコンテンツをiframeにて表示するからです。



 よって、iframe内にて表示するコンテンツのURLを指定する必要があります。



 GIFT_SAMPLE/public/gifts.xml を開いて、30行目辺りの sendSignedRequest メソッドへの引数を下記のように gifts controller の iframe アクションへのURLに変更します。

 

    sendSignedRequest('http://www.example.com/gifts/iframe');

 最後にサーバを再起動して終了です。



 今回は OpenSocialコンテナ(Orkut) -> 外部サーバへの一方的な通信でしたが、OAuthを用いて外部サーバ -> OpenSocialコンテナへの通信も可能となります。

 mixi では mixi connect がそれに当ります。



 今回の記事のタイトルが「速攻で作る OpenSocialアプリ」でありながら全くもって速攻では終わりませんでしたが、実サービスにて必須となるであろう署名付きリクエストの受付方法を示すことができましたのでご参考になれば幸いです。


 
 本年もBananan Systemsをどうぞ宜しくお願い申し上げます。

Flex/AIR チャートシリーズの LineSeries に常に値を表示する

Flex の Chart Series には ColumnSeries, PieSeries, LineSeries 等さまざまな種類のものがありますが、ColumnSeries, PieSeries などはlabelPositionプロパティに値を指定することにより、下の画像のように常に値を表示させることが可能です。

Labels for ColumnSeries, PieSeries

しかしながら、LineSeriesに関しては常に値を表示させることができません。
マウスオーバーの際に値を表示させる DataTip(ToolTip ※1) は使用可能です。

以前、自分も同じようなことを行いたく自作したLineSeriesDataLabelクラスがあったので少し変更して紹介します。

LineSeriesDataLabel_Original

           
LineSeries に常に値を表示する LineSeriesDataLabel
(右クリックからソースが見れます)

今後他のシリーズにも適応できるようにLineSeriesを直接継承せずに、ChartElementを継承しています。
LineSeriesDataLabelのseriesプロパティにLineSeriesのインスタンスを指定します。

各値を表示するラベル(UITextField)がチャートの表示領域からはみ出した場合には内側にずらす簡単な処理を入れていますが、複雑なポジショニングが必要な場合は、LineSeriesDataLabelクラスのlabelPositioningFunctionに独自のFunctionを設定してください。

その Function の引数、実装等は LineSeriesDataLabel#defaultLabelPositioningFunction を参考にしてください。

※1
あまり気にする必要はありませんが、DataTip と ToolTip には下記の明確な違いがあります。
「DataTip コントロールは ToolTip コントロールに似ていますが、マウスポインタから最も近いグラフのデータポイントを表す適切な値が表示されるという点で異なります。」

http://livedocs.adobe.com/flex/3_jp/langref/mx/charts/chartClasses/ChartBase.html

itemRenderer パート4 : ステート & トランジション

原文 : http://weblogs.macromedia.com/pent/archives/2008/03/itemrenderers_p_3.html

itemRenderers: Part 4: ステート と トランジション

itemRenderer は、視覚的に情報を伝えることにとても優れています。

伝えると一口で言っても、名前を表示するだけのシンプルなものから、たくさんの色を使い表現するもの、時にはインタラクションを含むものまであります。

itemEditor がまさにインタラクティブなコントロールですが、今回の記事の目的ではありませんので割愛します。
今回の記事では、itemRenderer の data プロパティやユーザのアクションにより外観を変化させる itemRenderer について見ていきます。

■ ステート

<mx:State> は、itemRendererの外観を変更するのにとても適した方法です。
ステートはとても簡単に使うことができ、トランジションと組み合わせることにより、ユーザにフィードバックを返して、使い勝手をよくすることができます。

今回のサンプルでは、List コンポーネント用に、MXML を用いた itemRenderer を作成していきます。( ActionScript のみでも作成可能です )

itemRenderer で表示するのは、image, title, author, price と 本を購入するための Button コントロールです。

<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" >

<mx:Image id="bookImage" source="{data.image}" />
<mx:VBox height="115" width="100%" verticalAlign="top" verticalGap="0" paddingRight="10">
<mx:Text text="{data.title}" fontWeight="bold" width="100%"/>
<mx:Label text="{data.author}" />
<mx:HBox id="priceBox" width="100%">
<mx:Label text="{data.price}" width="100%"/>
<mx:Button label="Buy" />
</mx:HBox>
</mx:VBox>
</mx:HBox>

もし本の在庫が無ければ、その本が非表示になるようにしましょう( data の <instock> タグの値 ( yes / no ) で在庫の有無を判定します )。
実装を簡単にするために、HBox の id に “priceBox” を指定しています。
この HBox の可視状態を変更することにより、内部の Label と Button 両方の可視状態も変更できるからです。

set data メソッドをオーバーライドしてもいいのですが、ここでは priceBox の可視状態を直接変更せずに、ステートを使ってみます。

<mx:states>
<mx:State name="NoStockState">
<mx:SetProperty target="{priceBox}" name="visible" value="false"/>
</mx:State>
</mx:states>

このタグをルートタグ直下に配置してください。

やりたいことが簡単なわりに少々複雑になってしまっていますが、ステートの使い方がよくわかると思います。
以下の2つのステートが存在します。

・ ベースステート : これはコンポーネントの標準(デフォルト)の状態です。コンポーネントがステートを使っていないときは、この状態になっています。今回の例では、ステートがベースステートの時には priceBox の visible プロパティが true (デフォルト値) になります。 これは instock タグの値が yes の時です。

・ NoStockState : これは instock タグの値が no の時のステートです。 このステートになると <mx:State> タグ内の SetProperty が処理されます。 target はそのSetProperty 処理の対象となるインスタンスを指定します。name プロパティはアップデートするプロパティ名、value プロパティは新しくセットする値です。

set data メソッドは、instock の値に基づいてステートを切り替えます。

override public function set data( value:Object ) : void
{
super.data = value;

if( data )
{
if( data.instock == "yes" )
currentState = "";
else
currentState = "NoStockState";
}
}

currentState は全ての UIComponent コントロールが備えているプロパティであり、現在のステート名を保持しています。 ステートを切り替えると、Flex フレームワークはベースステートから始め、新たなステートへの切り替えに必要な処理を行います。

—————-
itemRenderer は再利用されますので、必ず元の値(状態)に戻す処理を入れてください。itemRenderer に if を書いたら、必ず else も書く必要があります。
—————-

もし興味があればですが、下のコードのように set data メソッドをオーバーライドしない方法もあります。 データバインディングを使って、 root タグの中で currentState の値を直接変更しています。

<mx:HBox xmlns:mx=”http://www.adobe.com/2006/mxml” width=”400″
currentState=”{data.instock == ‘yes’ ? ” : ‘NoStockState’}” >

インラインで data.instock の値を評価し、その結果を currentState プロパティに代入しています。 トリッキーなので保守性には優れていないかもしれません。

■ エレメントの追加

次の itemRenderer では、instack の値が yes の時にだけ price と 購入ボタンが表示されます。
もちろんステートを使わずに同じことは出来ますが、もし itemRenderer に追加、削除するコントロールが多数ある場合にはステートを使うのが得策です。 
なぜなら、itemRenderer の currentState プロパティに値をセットするだけでそれら多数のコントロールを管理できるからです。

単に price と 購入ボタン を削除するだけでなく、在庫切れであることを表示する Label も追加します。

変更を加えたコードは次のようになります。

<mx:states>
<mx:State name="NoStockState">
<mx:SetProperty target="{priceBox}" name="visible" value="false"/>
<mx:AddChild relativeTo="{priceBox}" position="before">
<mx:Label text="-- currently not in stock --" color="#73DAF0"/>
</mx:AddChild>
</mx:State>
</mx:states>

<mx:AddChild> タグで priceBox に Label を追加しています。
priceBox の visible プロパティに false をセットし、代わりにわかりやすい文字列を表示します。

繰り返しますが、set data メソッドをオーバーライドすることで、同じように Label を追加することもできます。また、必要なコンポーネントを前もって追加しておいて、それらの可視状態を後で変更するといった方法も可能です。

しかし、ステートにはわかりやすいメリットがあります。
ステートを使えば、在庫切れ状態の表示に必要な処理がどんなに複雑になっても、NoStockState を編集するだけで十分です。ステートを切り替えるための ActionScript コードを変更する必要はありません。

—————
Flex Builder の Design View ではステートを編集することができます。
—————

■ 伸び縮みする List

今回の例は、List コントロールではうまく動作しませんが、VBox と Repeater を用いて実現できます。
もし List コントロールを使用して、そのリストがスクロールされてしまうと伸長させたアイテムの itemRenderer が機能しなくなるかもしれません。
例えば、高さが同じアイテムを複数保持するリストがあるとします。
ここで2番目のアイテムの高さを伸長させると、そのアイテムの高さは他のアイテムよりも高くなります。 ここまでは大丈夫です。
( And there’s the catch: the visible items. )
次にそのリストをスクロールさせます。 itemRenderer は再利用されるのを覚えていますよね。 2番目のアイテムがリストの外に出ると、そのアイテムの itemRenderer はリストの最下部のアイテムを表示するために移動します。
その時、その itemRenderer の高さを元に戻す必要があります。
そして、2番目のアイテムが再度見えるようにListをスクロールします。
2番目のアイテムの高さが伸長されたままになっていて欲しいところですが、itemRenderer は前の高さのことを知りません。
以前の記事に書いたように、それらの情報は itemRenderer の data プロパティ、もしくは何かしらの外部ソースから得なければなりません。

itemRenderer のリサイズはとても複雑になりがちなので、頑張って実装するわりには大したものにならないと思います。
もしリサイズが必要なのであれば VBox と Repeater を用いる方法が良いと思います。しかし Repeater を用いる方法の欠点として、Repeater で表示する子コンポーネントのインスタンス全てが生成されてしまいます。
もし Repeater を用いて 1000 個のレコードを表示すると、itemRenderer のインスタンスが1000 個生成されてしまいます。

今回の例では、VBox の 子コンポーネントとなる itemRenderer を作成します。
本のタイトルと著者のみを表示するとてもシンプルな itemRenderer ですが、クリックするとその場で itemRenderer が伸長するようにします。
これは下記の2つを使って実現できます。

* itemRenderer に追加情報を持つステートを持たせる
* itemRenderer をスムーズに伸縮させるため Resize トランジションを使用する

itemRenerer のベース ステートはとてもシンプルです:

<mx:HBox width="100%">
<mx:Label text="{data.author}" fontWeight="bold"/>
<mx:Text text="{data.title}" width="100%" fontSize="12" selectable="false"/>
</mx:HBox>

ExpandedState ステートでは新たな情報を付加して itemRenderer の高さの調節をします。

<mx:states>
<mx:State name="ExpandedState">
<mx:AddChild position="lastChild">
<mx:HBox width="100%">
<mx:Image source="{data.image}"/>
<mx:Spacer width="100%"/>
<mx:Label text="{data.price}"/>
<mx:Button label="Buy"/>
</mx:HBox>
</mx:AddChild>
</mx:State>
</mx:states>

itemRenderer のサイズ変更にかかる手間は、トランジションをただ追加するのと大差ありません。

<mx:transitions>
<mx:Transition fromState="*" toState="*">
<mx:Resize target="{this}" />
</mx:Transition>
</mx:transitions>

この トランジションは fromState と toState プロパティの両方にワイルドカードが指定されているので、ステートが変更される毎に再生されます。

後は、itemRenderer のクリックイベントのハンドラを用意し、そのハンドラ内でステートを変更するだけです。

<mx:Script>
<![CDATA[

private function expandItem() : void
{
if( currentState == "ExpandedState" )
currentState = "";
else
currentState = "ExpandedState";
}
]]>
</mx:Script>

■ Summary

ステートは、itemRenderer の外観を変更するのにとても良い方法です。
複数の変更をステートとしてまとめることにより、itemRendererへの変更を一斉に反映させることができます。

次回の記事では、UIComponent クラスを継承して効率的な itemRenderer を作成します。

itemRenderer パート3 : Communication ( データのやりとり )

○ 原文
itemRenderers: Part 3: Communication

前回の記事では external itemRenderer の作成方法を、MXML と ActionScript の両方で説明しました。
サンプルコードでは、itemRenderer 内の Button がクリックされるとカスタム・イベント(BuyBookEvent)をディスパッチして、itemRenderer外でそのイベントに応じた処理を行えるようにしました。
今回の記事では、itemRenderer とのデータのやり取りを掘り下げていきたいと思います。

絶対に破ってほしくないルールが一つあります。それは「(外部から) itemRenderer のインスタンスを保持し、(publicなプロパティをセットすることにより)itemRendererを変更したり、itemRenderer の public メソッドを呼び出してはいけない」ということです。
Part 1 の記事で述べたように、 itemRenderer は再利用されるものなので保持するのはとても難しく、もしそれを行ってしまうと Flex frameworkの挙動を狂わせかねません。

このルールをに則って考えると、itemRenderer でできるのは次のようなことです。

* itemRenderer は自身を適用したコンポーネントを通じてイベントをディスパッチできます。(既にバブリングを紹介しました。この方法が優れていることを後で説明します)
* itemRenderer では Application.application などのクラス変数が使用可能です。Application オブジェクトに「グローバルに」変数を定義したのなら、この方法でその変数にアクセス可能です。
* itemRenderer は自身を適用したコンポーネントのpublic 変数にアクセス可能です。
* itemRenderer は data のレコードの全てのフィールドにアクセスできます。例えば、直接画面に表示するデータでなくとも、itemRendererの動作に影響を与えるフィールドにアクセスしたりできます。

■ itemRenderer の処理を動的に変更する

次のコードは、前回の記事で TileList 用に作成した MXML の itemRenderer です。
これを外部のデータによってitemRendererの処理を動的に変えるようにします。 ( ファイル名を BookItemRenderer.mxml とします )

<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="250" height="115" >

<mx:Script>
<![CDATA[
]]>
</mx:Script>

<mx:Image id="bookImage" source="{data.image}" />
<mx:VBox height="115" verticalAlign="top" verticalGap="0">
<mx:Text text="{data.title}" fontWeight="bold" width="100%"/>
<mx:Spacer height="20" />
<mx:Label text="{data.author}" />
<mx:Label text="Available {data.date}" />
<mx:Spacer height="100%" />
<mx:HBox width="100%" horizontalAlign="right">
<mx:Button label="Buy" fillColors="[0x99ff99,0x99ff99]">
<mx:click>
<![CDATA[
var e:BuyBookEvent = new BuyBookEvent();
e.bookData = data;
dispatchEvent(e);
]]>
</mx:click>
</mx:Button>
</mx:HBox>
</mx:VBox>

</mx:HBox>

TileList を用いてアイテムのカタログの表示をしようとしているとします。
金額の範囲を設定できる Slider コントロールも存在するとしましょう(Slider コントロールは itemRenderer の外側にあります)。
金額の範囲外のアイテムは、フェードアウトさせます(itemRendererのアルファ値を変化させます)。
itemRenderer の alpha 値を変更するためには、金額の範囲が変わったことを全ての itemRenderer に伝える必要があります。

set data メソッドを次のようにオーバーライドします。

override public function set data( value:Object ) : void
{
super.data = value;
if( data.price < criteria ) alpha = 0.4;
else alpha = 1;
}

問題は、どのように criteria (金額の範囲)の値を変えればいいかです。
itemRenderer を使う場合のベスト・プラクティスは、「itemRenderer は常に与えられたデータのみに基づいて動作させる」ことです。
しかし、今回のようなケースでは criteria を data に含めるのは良い方法とは言えませんので、data の外に存在するようにしましょう。
これを実現する方法はいくつかあります。

* list に含める。 list ( List, DataGrid, TileList 等 ) コンポーネントを継承し、その継承したクラスに public な変数として criteria (金額の範囲) を保持させる。
* グローバル変数として application オブジェクトに含める。

私ならば、一番目のクラスを継承して criteria をクラスに含める方法を選びます。
つまるところ、クラスはデータを表示するために使われていて、criteria は表示される項目の一部なのですから。
今回の例では、TileListを継承して criteria を public なデータメンバーとして持たせます。

package
{

import mx.controls.TileList;

public class CatalogList extends TileList
{
public function CatalogList()
{
super();
}

private var _criteria:Number = 10;

public function get critera() : Number
{
return _criteria;
}

public function set criteria( value:Number ) : void
{
_criteria = value;
}
}
}

itemRenderer の外に存在するコントロール (今回の例では、Sliderコントロール) がこのクラスの criteria プロパティに値をセットすることで、itemRenderer に criteria の値を通知できるようになりました。

■ listData

itemRenderer は、itemRenderer がセットされたリスト自身の情報と、itemRenderer がどの行と列(DataGrid のように列を持つコンポーネントの時のみ)描画しているかを知ることができます。
この情報が listData です。listData を使うと、次のように BookItemRenderer.mxml を書き直すことができます。

override public function set data( value:Object ) : void
{
super.data = value;
var criteria:Number = (listData.owner as MyTileList).criteria;
if( data.price < criteria ) alpha = 0.4;
else alpha = 1;
}

先に見せた BooktItemRenderer.mxml の <mx:Script> タグ内にこのメソッドを追加してみてください。

listData プロパティは itemRenderer が属するコントロールへの参照である owner プロパティを持っています。
今回のサンプルでは、owner は TileList を継承した MyTitleList です。 owener プロパティを MyTitleList へキャストすることにより、criteria へアクセス可能になります。

■ IDropInListItemRenderer

listData は、IDropInListItemRenderer インターフェイスを実装している itemRenderer にのみ存在します。
残念ながら、コンテナ(HBoxなど) はそのインターフェイスを実装していません。
コントロール (ButtonやLabelなど) はそのインターフェイスを実装していますが、コンテナの場合は自分でそのインターフェイスを実装しなければなりません。

このインターフェイスの実装方法は簡単で、Flexのドキュメントにも記述されています。
IDropInListItemRenderer を実装した BookItemRenderer クラスを自分で作るには、次のようにします。

1. IDropInListItemRenderer インターフェイスを実装したクラスを用意する

<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" ... implements="mx.controls.listClasses.IDropInListItemRenderer">

2. listData の set と get メソッドを itemRenderer となるクラスの <mx:Script> タグ内に追加する。

import mx.controls.listClasses.BaseListData;

private var _listData:BaseListData;
public function get listData() : BaseListData
{
return _listData;
}
public function set listData( value:BaseListData ) : void
{
_listData = value;
}

itemRenderer が IDropInListItemRenderer インターフェイスを実装していれば、list コンポーネントは listData を全ての itemRenderer にセットします。

■ invalidateList()

criteriaをクラスに(正常に機能するものとして)含めるのは少々複雑で、単にcriteriaに値を代入しただけではFlex frameworkはその値の変化に気付いてくれません。criteriaの値の変更時には、その変更を伝えるイベントのトリガーが必要です。
次のコードは、set criteria メソッドを変更したものです。

public function set criteria( value:Number ) : void
{
_criteria = value;

invalidateList();
}

_criteria へ値をセットした後にinvalidateList()を呼び出しています。
invalidateList()を呼び出すことにより、全てのitemRenderer に dataProvider の値でデータをリセットさせます。リセットすることにより set data メソッドが再度呼ばれます。
これらの処理は次のように表現できます。

1. itemRenderer は与えられたデータをどのように表示するか判断するために、list オーナー(itemRendererが属しているlistコンポーネント) のcriteriaの値をチェックします。
2. Flex の list クラスを継承した listオーナーには、itemRenderer が読み込むことができる public なプロパティがあり、そのプロパティに外部のコード(他のコントロールやActionScriptコード)が値をセットします。
3. list のプロパティがセット(値が変更)されると、そのlistの invalidateList() メソッドが呼び出されます。それが itemRenderer をリフレッシュさせるトリガーとなり、結果としてdataがリセットされます。 ( そしてステップ1へとまた戻ります。)

■ Event

以前の記事で、itemRenderer とアプリケーションの他の部分のデータをやりとりのために、どのようにイベントをバブリングさせれば良いか示しました。 それはそれで簡単で良いのですが、「itemRendererの役割はデータを表現(表示)すること、コントロールの役割はデータを操作すること」という考えに基づいた、より良い方法があるのではないと考えています。

MyTileList コントロールの本質は、売り物の本のカタログを表示することです。ユーザーがある本を選んで購入しようとしたときに、それをアプリケーションに伝えるのはlist コントロールの役割であるべきです。これをコードで書くと次のようになります。

<CatalogList bookBuy="addToCart(event)" />

現在の状況では、イベントはバブルアップして TileList を通り抜けてしまいます。
イベントをバブリングさせる方法では bookBuy イベントを リスト (TileList) コントロールに関連付けないので、コントロールをアプリケーションの別の場所に移動させられます。
例えば、bookBuy イベントのハンドラをメインの Application に記述すると、( その bookBuy イベントをディスパッチする ) リストコントロールをアプリケーションの別の場所に移動させたいとした時に、ハンドラも共に移動しなければいけません。
逆に、もしイベントをリストコントロールに関連付けていれば、そのコントロールを移動させるだけで済みます。

Button のクリック・イベントが実はButtonによりディスパッチされるのではなく、Button 内の別の何か他のものによりディスパッチされてバブリングされてくるものだとしたらどうでしょうか。
<mx:Button click=”doLogin()” label=”Log in” />とは書けなくなります。 doLogin() メソッドをどこか別の箇所に移動しなければならず実装がややこしくなります。

おわかりいただけたでしょうか。では、イベントをバブリングさせる方法から listコントロールにイベントをディスパッチさせる方法へと、サンプルを変更する手順を説明します。

第1に、CatalogList にメタデータを追加して、コンパイラにそのコントロールがイベントをディスパッチすることを知らせます。

import events.BuyBookEvent;
import mx.controls.TileList;

[Event(name="buyBook",type="events.BuyBookEvent")]

public class CatalogList extends TileList
{

第2に、CatalogList にイベントをディスパッチさせるメソッドを追加します。 このメソッドは itemRenderer により呼び出されます。

Second, add a function to CatalogList to dispatch the event. This function will be called by the itemRenderer instances:

	public function dispatchBuyEvent( item:Object ) : void
{
var event:BuyBookEvent = new BuyBookEvent();
event.bookData = item;
dispatchEvent( event );
}

}

第3に、itemRenderer 内の Buy ボタンが上記メソッドを呼び出すようにコードを変更します。

Third, change the Buy button code in the itemRenderer to invoke the function:

			<mx:Button label="Buy" fillColors="[0x99ff99,0x99ff99]">
<mx:click>
<![CDATA[
(listData.owner as CatalogList).dispatchBuyEvent(data);
]]>
</mx:click>
</mx:Button>

これで itemRenderer 内のボタンは、dataを引数としてlistコントロールのdispatchBuyEvent() メソッドを呼び出すことができるようになりました。
そうすることにより、アプリケーションの他の部分とのデータのやりとりを行うという責務をlistコントロールに移しています。

今回のサンプルの list コントロールは、data を保持するイベントをディスパッチします。
ActionScriptとMXML(CatalogList.as ファイルで[Event]メタデータで指定されているので)のどちらでも好きな方を使って、アプリケーションはこのイベントに対するイベントリスナをセットすることができます。
[Event]メタデータを使うと、あなたのコードは他の開発者にとって使いやすくなります。

■ Summary

itemRenderer は、イベントを用いてアクションを伝達するべきです。 カスタム・イベントを用いることによりデータを受け渡すことができるので、イベントを受け取る側はわざわざ itemRenderer にまでデータを取得しにいく必要がありません。

itemRenderer は set data メソッドをオーバーライドして、その data の変更に反応するようにしなければいけません。
そのメソッド内では listData.owner から必要なデータにアクセスできます。 また static なクラスや、メインのアプリケーション( Application.application )に保存されたデータにもアクセスできます。

次回の記事では、itemRenderer の状態について見ていきます。

itemRenderer パート2 : external itemRenderer

itemRendererシリーズのPart1では、inline itemRenderer について説明しました。
inline itemRendererとは、MXML タグと ActionScript のコードが itemRenderer を適用するコンポーネントと同じファイルに存在するものです。

inline itemRenderer は別ファイルで定義されたクラスと同等だと説明しましたよね。
実際にFlex コンパイラは内部的に、インラインで書かれたコードを抜き出してクラスを作成しています。

今回の記事では、自分でそのクラスを作ってみます。
inline itemRendererの利点は、itemRendererを適用するコンポーネントと同じ個所に記述できるというものですが、itemRendererが複雑になってしまったときに読みづらいという欠点にもなります。

itemRenderer を外部ファイルに切り出すことには、いくつかの利点があります。
・itemRendererを複数のリストで利用できる
・メンテナンス性に優れる
・Flex Builderのデザイン・ビューで大枠をデザインすることができる

■An MXML itemRenderer

前回の記事では、DataGrid用に少々複雑なitemRendererを紹介しました。

<mx:DataGridColumn headerText="Title" dataField="title">
<mx:itemRenderer>
<mx:Component>
<mx:HBox paddingLeft="2">
<mx:Script>
<![CDATA[
override public function set data( value:Object ) : void {
super.data = value;
var today:Number = (new Date()).time;
var pubDate:Number = Date.parse(data.date);
if( pubDate > today ) setStyle("backgroundColor",0xff99ff);
else setStyle("backgroundColor",0xffffff);
}
]]>
</mx:Script>
<mx:Image source="{data.image}" width="50" height="50" scaleContent="true" />
<mx:Text width="100%" text="{data.title}" />
</mx:HBox>
</mx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>

itemRendererの基本はHBoxであり、Image とText を含んでいます。背景色は item レコードの pubDate フィールドの値によって決まります。
これと同じitemRendererを外部ファイルとして書く方法を説明しましょう。

1. もし Flex Builder を使っているのなら、新規 MXML コンポーネントを作成してください。(コンポーネント名は GridColumnSimpleRenderer とします。好きな名前を付けてもらって結構です。) ルートタグは HBox とします。サイズは気にする必要はありません。
2. もし SDK を使っているのなら、新規 MXML ファイルを作成してください。ファイル名は GridColumnSimpleRenderer.mxml としました。ルートタグは HBox とします。
3. 新規作成したファイルに上記コードの <mx:HBox>タグ内を全てコピーしてください。( <mx:HBox>タグ自体は新規作成したファイルに存在するものなのでコピーする必要はありません。 )

結果は以下のようになります。

<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300">
<mx:Script>
<![CDATA[
override public function set data( value:Object ) : void {
super.data = value;
var today:Number = (new Date()).time;
var pubDate:Number = Date.parse(data.date);
if( pubDate > today ) setStyle("backgroundColor",0xff99ff);
else setStyle("backgroundColor",0xffffff);
}
]]>
</mx:Script>
<mx:Image source="{data.image}" width="50" height="50" scaleContent="true" />
<mx:Text width="100%" text="{data.title}" />
</mx:HBox>

4. ファイルを保存します。

DataGridColumn の定義からインライン itemRenderer を削除して、下記コードと入れ替えます。

<mx:DataGridColumn headerText="Title" dataField="title" itemRenderer="GridColumnSimpleRenderer">

アプリケーションを実行してみると、おっと..。DataGridの行の縦サイズが大きすぎますね。
これは itemRenderer の height プロパティに 300 が指定されているからです。

■ itemRenderer の width と height について

List コントロールは itemRenderer の width を自動で調節します。従って、今回の例の width=”400″ という指定は無視されます。
ユーザがカラムやリストの幅を変更するとitemRenderer の幅も変わってしまうのを考慮して、itemRendererを実装してください。

heightに関しては話が別です。もし List の rowHeight プロパティに明示的に値を指定すると、全ての行がその指定した高さになり、itemRenderer で指定した height は無視されます。
しかし、List の variableRowHeight プロパティに true をセットすると、itemRenderer に指定した height が優先されます。
今回の例では height に 300 をセットしているので、各行の高さは 300 ピクセルになります。

itemRendererから明示的に値を指定しているheightを削除することにより、正常に表示されるようになります。

■ Dynamically Changing the itemRenderer

今回の例では set data メソッドをオーバーライドし itemRenderer の背景色の変更する処理を行っています。
これはよく使う方法です。
set data メソッドをオーバーライドすることにより、新しい行のデータが data プロパティにセットされたタイミングで独自の処理を行わせることができます。(今回の例では、styleを変更しています)

よくあるミスとして:

・super.data = value; の記述を忘れてしまう。 これはとても重要です。この記述を忘れてしまうと itemRenderer の挙動がおかしくなってしまいます。

・条件に合致しなかったときに styleを戻す処理を忘れてしまう。 今回の例では、pubDate が未来の日付の時だけ背景色を変更し、過去の日付の時に背景色をデフォルトに戻し忘れてしまう。
itemRendererは再利用されるので、必ずデフォルトに戻すといった処理を含めてください。

■ ActionScript itemRenderer

さて、もう一つ itemRenderer を書いてみましょう。今度は ActionScript のクラスを使います。
前回の記事で、次のようなインライン itemRenderer を持つ TileList を使用しました:

<mx:itemRenderer>
<mx:Component>
<mx:HBox verticalAlign="top">
<mx:Image source="{data.image}" />
<mx:VBox height="115" verticalAlign="top" verticalGap="0">
<mx:Text text="{data.title}" fontWeight="bold" width="100%"/>
<mx:Spacer height="20" />
<mx:Label text="{data.author}" />
<mx:Label text="Available {data.date}" />
<mx:Spacer height="100%" />
<mx:HBox width="100%" horizontalAlign="right">
<mx:Button label="Buy" fillColors="[0x99ff99,0x99ff99]">
<mx:click>
<![CDATA[
var e:BuyBookEvent = new BuyBookEvent();
e.bookData = data;
dispatchEvent(e);
]]>
</mx:click>
</mx:Button>
</mx:HBox>
</mx:VBox>
</mx:HBox>
</mx:Component>
</mx:itemRenderer>

この itemRenderer を外部ファイルの ActionScript で記述します。 以下のステップに従って進めてください。

1. ActionScript クラスの作成. クラス名は BookTileRenderer にし、インラインitemRendererと同様 HBox を継承します。

package
{
import flash.events.MouseEvent;

import mx.containers.HBox;
import mx.containers.VBox;
import mx.controls.Button;
import mx.controls.Image;
import mx.controls.Label;
import mx.controls.Spacer;
import mx.controls.Text;

public class BookTileRenderer extends HBox
{
public function BookTileRenderer()
{
super();
}

}
}

2. 子コンポーネントへの参照を保持するメンバ変数を定義します。

private var coverImage:Image;
private var titleText:Text;
private var spacer1:Spacer;
private var authorLabel:Label;
private var pubdateLabel:Label;
private var spacer2:Spacer;
private var buyButton:Button;

3. createChildren() メソッドをオーバーライドし、子コンポーネントをインスタンス化しHBoxに追加していきます。

override protected function createChildren():void
{
coverImage = new Image();
addChild(coverImage);

var innerBox:VBox = new VBox();
innerBox.explicitHeight = 115;
innerBox.percentWidth = 100;
innerBox.setStyle("verticalAlign","top");
innerBox.setStyle("verticalGap", 0);
addChild(innerBox);

titleText = new Text();
titleText.setStyle("fontWeight","bold");
titleText.percentWidth = 100;
innerBox.addChild(titleText);

spacer1 = new Spacer();
spacer1.explicitHeight = 20;
innerBox.addChild(spacer1);

authorLabel = new Label();
innerBox.addChild(authorLabel);

pubdateLabel = new Label();
innerBox.addChild(pubdateLabel);

spacer2 = new Spacer();
spacer2.percentHeight = 100;
innerBox.addChild(spacer2);

var buttonBox:HBox = new HBox();
buttonBox.percentWidth = 100;
buttonBox.setStyle("horizontalAlign","right");
innerBox.addChild(buttonBox);

buyButton = new Button();
buyButton.label = "Buy";
buyButton.setStyle("fillColors",[0x99ff99,0x99ff99]);
buyButton.addEventListener(MouseEvent.CLICK, handleBuyClick);
buttonBox.addChild(buyButton);
}

親と子の関係が分かりやすくなるように、インデントを入れてみました。 Buyボタンへのクリック・イベントのリスナーの追加も忘れずに行ってください。

4. commitProperties() メソッドをオーバーライドし、子コンポーネントに data の値をセットします。

override protected function commitProperties():void
{
super.commitProperties();

coverImage.source = data.image;
titleText.text = data.title;
authorLabel.text = data.author;
pubdateLabel.text = data.date;
}

5. Buyボタンのクリック・イベントのハンドラーを追加します。

private function handleBuyClick( event:MouseEvent ) : void
{
var e:BuyBookEvent = new BuyBookEvent();
e.bookData = data;
dispatchEvent(e);
}

6. メインのアプリケーションの TileList の itemRenderer を、 ActionScript で記述したものに変更します。
inline itemRenderer を削除して、TileList タグ内の itemRenderer プロパティに新たな itemRenderer のクラス名を記述します。

<mx:TileList id="mylist" x="29" y="542" width="694" itemRenderer="BookTileRenderer" 
dataProvider="{testData.book}" height="232" columnWidth="275" rowHeight="135" >

もしFlexの既存のコンテナクラス(HBoxなど)を基底クラスとするのであれば、私はわざわざ ActionScript を用いてitemRendererを作ろうとはしません。
見て分かる通りMXMLで作成したものと比べてとても複雑になりますし、実のところパフォーマンスもそれほどよくなりませんから。

■ 再利用可能な itemRenderer

次のコードは、CurrencyFormatterを用いて数字を表示する itemRenderer です。ファイル名を PriceFormatter.mxml とします。

<?xml version="1.0" encoding="utf-8"?>
<mx:Text xmlns:mx="http://www.adobe.com/2006/mxml">

<mx:Script>
<![CDATA[
import mx.controls.dataGridClasses.DataGridListData;

[Bindable] private var formattedValue:String;

override public function set data(value:Object):void
{
super.data = value;

formattedValue = cfmt.format( Number(data[(listData as DataGridListData).dataField]) );
}
]]>
</mx:Script>

<mx:CurrencyFormatter precision="2" id="cfmt" />

<mx:text>{formattedValue}</mx:text>

</mx:Text>

この itemRenderer のポイントは赤字の部分です。ここで、バインダブルな formattedValue 変数をセットしています。
まず、cfmt という id の <mx:CurrentFormatter>タグがあるのが分かると思います(ここも ActionScriptで記述しても構いません)。
上記のコードでは CurrentFormatter の format() メソッドを呼び出した結果を formattedValue にセットしています。

format() メソッドは引数にNumber型のオブジェクトを取るので、引数をNumberにキャストしています。
なぜなら、リストのdataProviderはXMLであり、XMLに含まれるものは全てテキストだからです。
data として Object を使えば、これを数値型の値にすることができますが、Number型にキャストして問題になることはありません。

ご存じの通り、data とは itemRenderer で表示する値を保持した単なるプロパティです。よって、[ ] 表記を用いてデータのフィールドにアクセスすることも可能です。
例えば、data['price'] は金額のカラムです。
しかし、itemRenderer を再利用するには、特定のカラム名を指定するのではなく、もっと汎用的なデータの指定方法が必要となります。

そこで listData の登場です。IDropInListItemRenderer インターフェイスを実装する全ての Flex コンポーネントは、listData プロパティを持っています。

Text, Label, Button, CheckBox などほぼ全てのコントールは IDropInListItemRenderer インターフェイスを実装しています。
一方、HBox, Canvas などほぼ全てのコンテナは、IDropInListItemRenderer インターフェイスを実装していません。
もしコンテナクラスを継承した itemRenderer で listData を使いたければ、自前でIDropInListItemRendererを実装しなければなりません。
これについては次回の記事で説明します。

itemRenderer に与えられた listData には、rowIndex プロパティや、自身(itemRenderer)を保持している DataGrid, List, TileList などのコンポーネントへの参照である owner プロパティが存在します。

DataGrid で itemRenderer を使用すると、listData は実際には DataGridListData 型のオブジェクトになります。
DataGridListData には、columnIndex プロパティや カラム (DataGridColumn) の名前を表す dataFiled プロパティなどもあります。

先ほどの行を、内側から一つ一つ説明していきましょう。

* listData as DataGridListData – listData を DataGridListData にキャストすることにより dataField プロパティにアクセスできるようにします。
* .dataField – そのカラムに表示されるフィールドです。 これを用いることにより itemRenderer を汎用的にできます。 dataField を用いることで、この itemRenderer を複数のカラムに使用できます。 今回の例では、dataField プロパティの値は ‘price’ です。
* data[ ... ] – 与えられたレコードの特定のフィールドにアクセスします。今回の例では、price カラムです。
* Number( … ) – format() メソッドはNumber型の引数を取るので、Number型へのキャストを行っています。
* cfmt.format( … ) – 引数に与えられた値を通貨の表記に変換しています。

■ Summary

itemRenderer を実装する方法(inline, external)を説明しましたが、自分が実装しやすい方法で良いと思います。
ActionScriptだけを使う人もいます。FlexとActionScriptの経験を積めば、この方法がうまく行くでしょう。
MXML はシンプルな itemRenderer を手早く仕上げるのに向いています.

今後は UIComponentを継承した ActionScript クラスとして記述したもっと効率的なitemRendererを作成していきます。
次回の記事では、itemRenderer とアプリケーションの他の部分とのデータのやりとりについて説明します。

Flex & BlazeDS & Google Maps インテグレーション

先週の Flex User Group にて発表させて頂きましたFlex、BlazeDS と Google Mapsを組み合わせたアプリを公開いたしましたのでご報告させて頂きます。

アプリケーションということで MyFlight と命名いたしました。

BlazeDS の Messaging 機能にて各飛行機の緯度経度座標を client へ随時送信します。
RemoteObject 機能を用いているのは、
1.飛行中の飛行機をクリックした時のポップアップ・ウィンドウ内に表示するデータをサーバーから取得する際
2.flight history (フライト履歴) を AdvancedDataGrid に表示する際
3.空港の検索の際
となっております。

勉強会の際には Flex と BlazeDS の通信には Http Streaming を用いていましたが、公開したアプリでは Client Polling に変更しました。

尚、BlazeDS の Http Streaming を用いた data push のデモビデオをYouTubeにアップさせて頂きました。

■ ロンドン ヒースロー 空港から同時に500機の飛行機を飛ばすデモ

■ ランダムで選択した空港から同時に500機の飛行機を飛ばすデモ