最近悩んでいたのが、「Flex上のボタンをクリックしたら別ウィンドウで帳票を出す」という機能の実現方法ですが、なんとか実現できたのでまとめてみました。
AS2のころは、getURLメソッドを使用して、リクエストパラメータのvalue部分にエンコードしたXMLをつっこんでサーバにPOST、というアクロバットなやりかたをしていました。
AS3からはAMF3が使えるようになったので、できればnavigateToURLのリクエスト時に、RemoteObjectで送信するのと同じメッセージを送りたい。
なぜなら、サーバサイドでのフレームワークの処理と、ユーザーが書くハンドリング処理が、通常のRemoteObjectを使用したときと同様のコーディングで出来るからです。
T2の場合、Pageクラスの戻り値をNavigationで返せることになっています。
このため、通常のRemoteObjectを使用した通信ではAMF3をレスポンスにするNavigationを返しますが、PDFを返す場合は、PDFのバイトをレスポンスにするNavigationを返してあげれば、うまく処理できることになります。
Pageクラスをイメージで書くと、こうなります。
//通常のAMFハンドリング @Page("amf") public class AmfPage { @Amf public Navigation hoge(HogeDto dto){ return AmfResponse.to("AMF3で返すよ"); } } //リクエストはAMF3、ただし別ブラウザで帳票を出すため戻り値はPDFデータ @Page("amf.pdf") public class AmfPdfPage { @Amf public Navigation hoge(HogeDto dto){ byte[] pdfData = PdfFactory.create();//なんらかのPDF生成ロジック return Pdf.to(pdfData); } }
だいぶ分かりやすいのではないでしょうか。私は分かりやすい。
ということでこれを実現するべく、ひとまずnavigateToURLメソッドを調査しました。
navigateToURL(request:URLRequest, window:String):void
http://livedocs.adobe.com/flex/201_jp/html/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Book_Parts&file=passingarguments_086_10.html
シグネチャはこんな具合です。
第一引数はURLRequest,第二引数はウィンドウ名です。ウィンドウ名は"_blank"とか"_self"とかのおなじみのやつですね。
ということでキモになるのがURLRequestですが、このような定義になっています。
URLRequestのパブリックプロパティ(Flexで使えるもの) contentType:String dataプロパティのコンテンツの MIME コンテンツタイプ。 data:Object URLリクエストで送信されるデータを含むオブジェクトです。 digest:String Flash Player キャッシュに保存される (または Flash Player キャッシュから取得される) 署名付き Adobe プラットフォームコンポーネントを一意に識別するストリング。 method:String HTTP フォーム送信メソッドを制御します。 requestHeaders:Array HTTP リクエストヘッダの配列が HTTP リクエストに追加されます。 url:String リクエストされる URL です。
http://livedocs.adobe.com/flex/3_jp/langref/flash/net/URLRequest.html
dataプロパティが、リクエスト時に送るデータです。dataプロパティには、
URLRequest API は、ストリング以外に、バイナリ POST と URL エンコード変数も
サポートします。データオブジェクトには、ByteArray、URLVariables、または
String オブジェクトを指定できます。
と書いてあり、このプロパティにAMF3の入ったByteArrayを入れればバイナリPOSTができるため、うまく行きそうです。
オブジェクトをAMF3にシリアライズするには、ByteArrayクラスが使用できます。こんな感じです。
var data:HogeDto = new HogeDto();//データクラス var byteData:ByteArray = new ByteArray(); byteData.objectEncoding = ObjectEncoding.AMF3; byteData.writeObject(hogeDto);
JavaのObjectOutputStreamと似た感じですね。
ここまでくれば、あとはメッセージを組み立てるだけです。
RemoteObjectは、サーバ呼び出しをする場合、「RemotingMessage」というクラスを使用します。
このクラスの中に、サーバ側のあて先や呼び出しメソッド、メソッド引数のデータを格納しています。
ということで、おもむろにRemotingMessageを作成してみます。
var dto:HogeDto = new HogeDto(); var remotingMessage:RemotingMessage = new RemotingMessage(); remotingMessage.destination = "amf.pdf";//あて先 remotingMessage.operation = "hoge";//呼び出しメソッド remotingMessage.body=[dto];//メソッド引数
上記のAmfPdfPageを呼ぶ場合は、このような感じでしょう。
送信するデータもできたので、早速送信してみます。
通信相手としてS2Flex2のAMF3実装をお借りしてみました。
var dto:HogeDto = new HogeDto(); var remotingMessage:RemotingMessage = new RemotingMessage(); remotingMessage.destination = "amf.pdf";//あて先 remotingMessage.operation = "hoge";//呼び出しメソッド remotingMessage.body=[dto];//メソッド引数 var byteData:ByteArray = new ByteArray(); byteData.objectEncoding = ObjectEncoding.AMF3; byteData.writeObject(remotingMessage); var request:URLRequest = new URLRequest("amf.pdf"); request.method="POST"; request.data = byteData; navigateToURL(request,"_blank");
結果はというと。。。だめでした。何かが足りないようです。
調べたところ、データ先頭部分の読み込みでエラーになっていました。どうやらヘッダーデータ的なものが必要なようです。
調べてみると、AMF3にはデータの先頭にエンベロープ部があり、それが必要なようです。
(AMF3の仕様書にはデータフォーマットは書いてありますが、エンベロープの記述がない。)
エンベロープに関しては、このページを参考にさせてもらいました。大変わかりやすいです。
http://cgi39.plala.or.jp/klove/w/k.cgi?page=amf3%A4%CE%A4%DE%A4%C8%A4%E1
http://osflash.org/documentation/amf/envelopes/remoting
AMFバージョンやボディ部のサイズ指定などがいるようです。
ひとまずこのエントリの内容を元にByteArrayにバイトを書いていき、送信してみました。
var dto:HogeDto = new HogeDto(); var remotingMessage:RemotingMessage = new RemotingMessage(); remotingMessage.destination = "amf.pdf";//あて先 remotingMessage.operation = "hoge";//呼び出しメソッド remotingMessage.body=[dto];//メソッド引数 var bodyByte:ByteArray = new ByteArray(); bodyByte.objectEncoding = ObjectEncoding.AMF3; bodyByte.writeByte(0x0A);//AMF0配列データ型 bodyByte.writeInt(1);//AMF0配列の長さ RemotingMessage1つなので1 bodyByte.writeByte(0x11)//AMF3データ型 bodyByte.writeObject(remotingMessage); var target:String = "amf.pdf.hoge";//あて先+メソッド var response:String = "/1";//レスポンスID 帳票の場合はどの道使わない var messageByte:ByteArray = new ByteArray(); messageByte.writeShort(0x03);//AMFバージョン messageByte.writeShort(0x00);//Header数 ヘッダーはいらないので0 messageByte.writeShort(0x01);//Body数 AMF0の配列を1ついれるので1 messageByte.writeShort(target.length);//targetのサイズ messageByte.writeUTFBytes(target);//target文字列 messageByte.writeShort(response.length);//responseのサイズ messageByte.writeUTFBytes(response);//response文字列 messageByte.writeInt(bodyByte.length);//シリアライズしたBodyのサイズ messageByte.writeBytes(bodyByte);//Bodyのシリアライズデータ var request:URLRequest = new URLRequest("amf.pdf"); request.method="POST"; request.data = messageByte; navigateToURL(request,"_blank");
肝は、ボディデータの先頭にAMF0の配列を書くこと、その後にAMF3のデータタイプを示す0x11を書くことのようです。
(それに気がつくまでに相当かかりました。。。)
最終的には、こんな感じのコードでうまく通信できました。
ということでAMF3でポップアップに帳票を出す作戦は大成功です。やったよ母さん!