ドメイン制限のないHTTPリクエストを行えるようになったURLRequest

今回は、AIRが持つネットワークアクセス機能のうち、flash.net.URLRequestに関連する機能をご紹介したい。

URLRequestは、これまでのFlexにも存在したクラスで、HTTPリクエストを実現するためのクラスだ。しかし、ブラウザ上で動作するFlash/Flexアプリケーションには、セキュリティを考慮した制限が設けられており、URLRequestを用いた通信を行う先は、SWFファイルのダウンロード元ドメインに制限されていた。

こうしたURLRequestに関する制限は、AIRアプリケーション上で使用する場合には一切存在しない。どのドメインにもHTTPリクエストを行うことができる。と言うことは、様々なサイトが提供するWeb APIの呼び出しを自由に行うこともできる、つまりAIR上でマッシュアップを行うことは非常に容易だ、ということに繋がる。インターネット上のサービスを用いて高度なアプリケーションを作成するのが、AIRであれば非常に簡単だというわけだ。

今回の記事は、URLRequestのこうした機能拡張を試すため、非常に単純なTwitterのクライアントをサンプルコードとして用意した。URLRequestについての簡単な説明を行った後、サンプルの解説を行いたい。

URLRequestの使用法

URLRequestクラスは、単一のHTTPリクエストを表すクラスである。だが、このクラス自体にはネットワークアクセスを実行するメソッドなどは定義されていない。このクラスが行うのは、アクセス先のURLとそのURLに対するアクセス方法をカプセル化することだ。以下のコードが、URLRequestクラスの典型的な使用法だ。

リスト:URLRequestクラスの使用法

// (1) アクセス先のURL
var url:String = "http://twitter.com/statuses/friends_timeline/" + userName + ".xml?count=20";

// (2) URLをコンストラクタの引数として、
//     `URLRequest`クラスのインスタンス作成
var req:URLRequest = new URLRequest(url);

// (3) 指定したURLのダウンロードを行うため、
//     `flash.net.URLLoader`クラスにインスタンスを渡す
var loader:URLLoader = new URLLoader(req);

(1) URL文字列の作成。AIR環境において、URLRequestに指定することのできるURLスキーマは以下の通りだ。

  • http

  • https

  • file

  • app-strage

  • app-resource

fileプロトコルを扱えることに注目。AIR環境であれば、ローカルファイルシステムからのファイル読み込みにも、URLRequestクラスを用いることができる。 app-strageは、AIRが規定しているデータ保存用ディレクトリ、app-resourceはアプリケーションディスクリプタファイルの存在する場所だ。詳しくは、当連載の第6回「ファイル操作用API(1) - ファイル/ディレクトリを統一的に扱えるFileクラス」を見ていただきたい。

(2) URLRequestクラスのコンストラクタにURL文字列を渡している。URLの指定方法には、この他にもURLRequestクラスのurlプロパティにアクセスするというやり方もある。

リスト:URLRequestクラスのurlプロパティにアクセスし、URLを指定

var req:URLRequest = new URLRequest();
req.url = "http://www.google.com";

(3) flash.net.URLLoaderクラスを用いて、URLRequestが示すURLからリソースをダウンロードする。URLRequestを利用するクラスはURLLoaderの他にもいくつかある。そうしたクラスで、どのようにURLRequestを用いるかを以下に示す(コード中の"urlRequest"は、URLRequest型のインスタンスとする)。

flash.net.URLLoaderクラス - 指定したURLから、リソースを取得する。

  • 使用法1 - コンストラクタにURLRequestを渡す

    var loader:URLLoader = new URLLoader(urlRequest);
  • 使用法2 - load()メソッドの引数にURLRequestを渡す

    var loader:URLLoader = new URLLoader();
    loader.load(urlRequest);

flash.net.URLStreamクラス - 指定したURLからリソースを取得する。flash.utils.IDataInputインタフェースを実装しており、低レベルなデータ読み出しを行うためのreadXXX()メソッドを備える。

  • 使用法 - load()メソッドの引数にURLRequestを渡す

    var urlStream:URLStream = new URLStream();
    urlStream.load(urlRequest);
    
    ...
    
    urlStream.close();

flash.net.FileReferenceクラス - 単一のファイルをダウンロード/アップロードする機能を有するクラス。当連載の第6回で説明した、ローカルシステム上のファイルやディレクトリを抽象化したflash.filesystem.Fileクラスは、FileReferenceを継承している。

  • 使用法1 - ファイルのダウンロード

    var fileReference:FileReference = new FileReference();
    
    // ダウンロードに伴い発生するイベントのハンドラを追加
    fileReference.addEventListener(Event.COMPLETE, onDownloadComplete)
    
    ...
    
    fileReference.download(urlRequest);
  • 使用法2 - ファイルのアップロード

    var fileReference:FileReference = new FileReference();
    
    // 「browse()」メソッドでファイルを選択された際のリスナ
    fileReference.addEventListener(Event.SELECT, function() {
    
    // アップロード先のURLを指定
    urlRequest.url = "http://your.upload.site.com/upload.hander.cfm";
    
    
    // アップロード開始
    fileReference.upload(urlRequest);
    
    });
    
    // アップロードするファイルを選択するダイアログを表示
    fileReference.browse();

flash.html.HTMLControlクラス - AIRの持つHTMLブラウジングエンジンを表すクラス。詳しくは当連載の第9回を見ていただきたい。

  • 使用法 - load()メソッドを使用したWebページの読み込み

    var html:HTMLControl = new HTMLControl();
    html.width = 200;
    html.height = 150;
    
    // Webページを読み込み
    html.load(urlRequest);
    
    var sprite:Sprite = new Sprite();
    sprite.addChild(html);

URLRequestを用いたサンプル - 簡易版Twitterクライアント

では、URLRequestを実際に用いたサンプルとしてTwitterにアクセスして、直近20件のコメントを取得するプログラムを紹介する。

Twitterからは公式にActionScriptのライブラリが提供されており、そちらを用いてWeb APIにアクセスする方が簡単かつ望ましいが、今回はURLRequestを用いたHTTPアクセスを自前で行ってみる。

エラー処理などはきちんと実装していないので、あくまでサンプルと割り切っていただければ幸いだ。

サンプルアプリケーションを起動するとログインダイアログが表示される

ログインに成功すると、データグリッド内にTwitterユーザのアイコン、ユーザ名、コメントが表示される

今回のサンプルコードは少々長いので、先にポイントを絞った解説を行う。記事の最後にサンプルの全文を掲載する。

サンプルの全体像としては、ログイン用のダイアログであるLoginDialog.mxmlとメインの処理(Twitterからのデータ取得)を担当するAIRURLRequestExample.mxmlの二つのMXMLファイルから成る。

ログインに成功すると、"onLoginSuccess"イベントが発生するので、そのイベントを捕捉し、Twitterからのデータ取得を行う。

まず、ログインを行う処理が以下のコードだ。

// ログインボタン押下字の処理
private function login():void {
    // (1) 以降のURLRequestが全て認証情報付きで行われるように、デフォルト値としてセット
    URLRequestDefaults.setLoginCredentialsForHost("twitter.com", userName.text, password.text);

    // (2) 以降、TwitterにHTTPリクエストを送信する処理
    var req:URLRequest = new URLRequest("http://twitter.com/statuses/update.xml");

    // POSTメソッドでなければいけない
    req.method = "POST";

    // Twitterは明示的に認証を行うためのAPIが提供されていないので、
    // 長さゼロの文字列でステータスを変更してみることによって代用
    var variables:URLVariables = new URLVariables();
    variables.status = "";

    // HTTPリクエストを実行。
    var loader:URLLoader = new URLLoader(req);

    // (3) HTTPレスポンスイベントを捕捉
    loader.addEventListener(HTTPStatusEvent.HTTP_RESPONSE_STATUS, onHTTPResponse);
}

(1) TwitterのAPIは、今のところ明示的に認証を行うためのAPIが用意されておらず、単純なHTTPのBasic認証を用いている。Basic認証のユーザ名とパスワードを送信するには、以下の2つの方法を用いることができる。

  • URLRequest.setLoginCredentials(userName, password) - 単一のHTTPリクエストに、認証情報を付与する
  • URLRequestDefaults.setLoginCredentialsForHost(hostName, userName, password) - ホスト名を指定し、そのホストに要求された際使用するデフォルトの認証情報をセットする。

ここでは後者の方法を用いている。URLRequestDefaultsクラスは、URLRequestクラスで使用されるデフォルト値を指定するためのクラスだ。すべてのプロパティやメソッドがstaticで、ここで使用した認証情報以外にも、キャッシュを使用するか(useCacheプロパティ)、クッキーの管理を行うか(manageCookieプロパティ)などを指定することができる。詳しくはリファレンスをご覧いただきたい。

(2) URLRequestクラスにHTTPリクエストの情報をセットし、URLLoaderクラスを用いて、http://twitter.comにPOSTリクエストを行っている。コメントにもあるとおり、Twitterには現在認証を行うための専用APIが存在しない。そのため、認証情報付きで、しかもTwitterのステータスに何の影響も与えない方法(空文字列を現在のステータスとして送信)を用いることでログイン処理の代用としている。

(3) HTTPStatusEvent.HTTP_RESPONSE_STATUSイベントを捕捉するハンドラを追加している。同イベントはFlex3で追加されたもので、HTTPレスポンスが到着した後一番早いタイミングで呼び出される。

では、ログインが成功した際呼び出されるイベントハンドラ(AIRURLRequestExample.mxml内)を見てみよう。

// (4) URLRequestを用いてTwitterのデータを取得(直近20件に限定)
var req:URLRequest = new URLRequest(
    "http://twitter.com/statuses/friends_timeline/" + userName + ".xml?count=20");

// URLLoaderで読み込み
var loader:URLLoader = new URLLoader(req);

// (5) 読み込み完了時の処理
loader.addEventListener(Event.COMPLETE, function(event:Event):void {
    var responseXML:XML = new XML(loader.data);
    for each (var status:XML in responseXML.children()) {
        timeline.addItem(
            {
                image: status.user.profile_image_url,
                name: status.user.name,
                text: status.text
            });
    }
});

(4) ここでは、TwitterのAPIに基づき、直近20件のコメントを取得するようURL文字列を構築している。「count=20」が、取得する件数を限定するためのパラメータだ。実際に何度か試したところ、countパラメータを使用しない場合、データの取得に時間がかかる上、レスポンスデータが不安定になるようである。

(5) URLLoaderによるリソースのダウンロードが完了した際、呼び出されるハンドラを登録している。Event.COMPLETEを指定して、関数オブジェクトを渡せば良い。内部の処理については細かく解説しないが、Twitterが返したXMLデータ変換してデータグリッドに追加している、というのが処理の根幹だ。

以上で、flash.net.URLRequestクラスに関する説明は終わりとする。AIR上であれば、ドメインの制限なくHTTPリクエストを行い、Web APIの呼び出しを行うことが出来ることがお解りだろう。

最後に、今回のサンプルコードを載せておく。

リスト: AIRURLRequestExample.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()" width="454" height="256">
        <mx:Script>
            <![CDATA[
                import mx.collections.ArrayCollection;
                import mx.managers.PopUpManager;

                // ログインダイアログ
                private var loginDialog:LoginDialog;

                // データグリッドと連動するコレクション
                [Bindable]
                private var timeline:ArrayCollection = new ArrayCollection();

                // 初期化
                private function init():void {
                    // ログインダイアログの作成
                    loginDialog = PopUpManager.createPopUp(this, LoginDialog, true) as LoginDialog;
                    // ログイン成功時のイベントリスナを追加
                    loginDialog.addEventListener(LoginDialog.ON_LOGIN_SUCCESS, onLoginSuccess);
                    // ダイアログを中央に表示
                    PopUpManager.centerPopUp(loginDialog);
                }
                // ログイン成功時の処理
                private function onLoginSuccess(e:Event):void {
                    // ポップアップを除去
                    PopUpManager.removePopUp(loginDialog);

                    // 入力されたユーザ名(ログイン失敗を考慮していない)
                    var userName:String = loginDialog.userName.text;

                    // (4) URLRequestを用いてTwitterのデータを取得(直近20件に限定)
                    var req:URLRequest = new URLRequest(
                        "http://twitter.com/statuses/friends_timeline/" + userName + ".xml?count=20");

                    // URLLoaderで読み込み
                    var loader:URLLoader = new URLLoader(req);

                    // (5) 読み込み完了時の処理
                    loader.addEventListener(Event.COMPLETE, function(event:Event):void {
                        var responseXML:XML = new XML(loader.data);
                        for each (var status:XML in responseXML.children()) {
                            timeline.addItem(
                                {
                                    image: status.user.profile_image_url,
                                    name: status.user.name,
                                    text: status.text
                                });
                        }
                    });
                }
            ]]>
        </mx:Script>
        <mx:DataGrid x="0" y="0" width="100%" height="100%" dataProvider="{timeline}">
                <mx:columns>
                        <mx:DataGridColumn headerText="画像" dataField="image" width="40">
                            <mx:itemRenderer>
                                <mx:Component><mx:Image width="32" height="32"/></mx:Component>
                            </mx:itemRenderer>
                        </mx:DataGridColumn>
                        <mx:DataGridColumn headerText="ユーザ名" dataField="name" width="120"/>
                        <mx:DataGridColumn headerText="コメント" dataField="text" wordWrap="true"/>
                </mx:columns>
        </mx:DataGrid>
</mx:WindowedApplication>

リスト: LoginDialog.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" width="320" height="200" title="ログイン">
    <mx:Metadata>
        [Event("onLoginSuccess")]
    </mx:Metadata>
    <mx:Script>
        <![CDATA[
            import mx.managers.PopUpManager;
            import mx.controls.Alert;

            // ログイン成功イベントを表す文字列
            public static const ON_LOGIN_SUCCESS:String = "onLoginSuccess";

            // ログインボタン押下字の処理
            private function login():void {
                // (1) 以降のURLRequestが全て認証情報付きで行われるように、デフォルト値としてセット
                URLRequestDefaults.setLoginCredentialsForHost("twitter.com", userName.text, password.text);

                // (2) 以降、TwitterにHTTPリクエストを送信する処理
                var req:URLRequest = new URLRequest("http://twitter.com/statuses/update.xml");
                // POSTメソッドでなければいけない
                req.method = "POST";

                // Twitterは明示的に認証を行うためのAPIが提供されていないので、
                // 長さゼロの文字列でステータスを変更してみることによって代用
                var variables : URLVariables = new URLVariables ();
                variables.status = "";

                // HTTPリクエストを実行。
                var loader:URLLoader = new URLLoader(req);

                // (3) HTTPレスポンスイベントを捕捉
                loader.addEventListener(HTTPStatusEvent.HTTP_RESPONSE_STATUS, onHTTPResponse);
            }
            private function onHTTPResponse(event:HTTPStatusEvent):void {
                // HTTPレスポンスステータスが401(Unauthorized)だった場合、アラートを表示
                if (event.status == 401) {
                    Alert.show("ログインできません。", "エラー");
                    return;
                }
                // イベントを送出
                dispatchEvent(new Event(ON_LOGIN_SUCCESS));
            }
        ]]>
    </mx:Script>
    <mx:Label text="ユーザ名とパスワードを入力してログインしてください。" fontWeight="bold"/>
    <mx:Form width="100%" height="85" y="21">
        <mx:FormItem label="ユーザ名">
            <mx:TextInput id="userName"/>
        </mx:FormItem>
        <mx:FormItem label="パスワード">
            <mx:TextInput id="password" displayAsPassword="true"/>
        </mx:FormItem>
    </mx:Form>
    <mx:Button label="ログイン" x="203" y="101" click="login()"/>
</mx:TitleWindow>