AMF実装におけるハマリどころ
T2にてAMF実装を行っていますが、そこで調査してはまった事を書いておきます。
ハマリ1(we return null)
blazeDSのclientからメッセージ送信した際に、送信メッセージ内のclass名が空で飛んできたせいでうまく動作しないところが出てきた。
半泣きになって調べて、BlazeDSの実装を見て修正したがイマイチ納得出来てなかったところ
カタヤマンことid:c9katayamaが飯田橋の彼方からやってきてモヤモヤを開放してくれた。
これに関してはカタヤマンがエントリを書いてくれるはずなので割愛しますが、Adobeの熱い思いがBlazeDSのソースからは読み取れた。
私たちはNULLを返すのだと。
ハマリ2
JavaFXで独自実装で手軽にAMF通信したいと思いT2からAMF部だけ切り出してクライアントを作ってみた。
BlazeDS実装のサーバ側とT2実装のサーバ側と正常に通信出来たので、これはもうOKだろうと思ったので
調子に乗って他のAMF3実装でも動作確認してみようかなと軽いノリで思ったのが運の尽きでした。
結局、python実装、PHP実装、Ruby実装、Red5と次々にソースを確認するはめになってしまった。
他のAMF3実装で確認した代表的なものは以下。
PyAMF
最初に試したPyAMFは、ボディ部の先頭バイトが0x0a(AMF0配列)であるかどうかをチェックしていたために自作ったクライアントから
メッセージがうまく送れないのではまった。
自作が悪いのかと思い、BlazeDSのクライアントで試すがこれもNG。この時点でPyAMFが悪いのかと錯覚。
いろいろ調査した結果、flexからの送信しか受付ないんだという強い意思がPyAMF実装からは感じられた。
つまりPyAMFと通信するクライアントを書くには、flexが送るバイナリと同等なものを送らないといけないので注意。
先頭は0x0aにしないといけない。
- PyAMFにおけるチェック(pyamf/remoting/__init__.py)
def _read_body(stream, decoder, strict=False): def _read_args(): """ @raise pyamf.DecodeError: Array type required for request body. """ if stream.read(1) != '\x0a': raise pyamf.DecodeError("Array type required for request body")
読みこみだけでは無く、書き込み時もPyAMFに関しては同様。
- PyAMFにおいてbody部を書き込む際には、Responseじゃない場合は、0x0aをwriteしている
def _encode_body(message): if isinstance(message, Response): encoder.writeElement(message.body) return stream.write('\x0a') stream.write_ulong(len(message.body)) for x in message.body: encoder.writeElement(x)
Zend-AMF(Zend/Amf/Request.php)
Zend-AMFだとコメントには、AMF0配列の中にAMF3メッセージだよとそれらしく書いてあるが特別チェックは行っていない。
条件を満たしてる時に、配列として扱ってるだけでPyAMFのようにはじいていない。
なのでBlazeDSのクライアントでもうまく通信は可能である。
<?php public function readBody() { $targetURI = $this->_inputStream->readUTF(); $responseURI = $this->_inputStream->readUTF(); $length = $this->_inputStream->readLong(); try { $data = $this->_deserializer->readTypeMarker(); } catch (Exception $e) { require_once 'Zend/Amf/Exception.php'; throw new Zend_Amf_Exception('Unable to parse '.$targetURI.' body data '.$e->getMessage()); } // Check for AMF3 objectEncoding if ($this->_deserializer->getObjectEncoding() == Zend_Amf_Constants::AMF3_OBJECT_ENCODING) { /* * When and AMF3 message is sent to the server it is nested inside * an AMF0 array called Content. The following code gets the object * out of the content array and sets it as the message data. */ if(is_array($data) && $data[0] instanceof Zend_Amf_Value_Messaging_AbstractMessage){ $data = $data[0]; }
rubyamf
http://www.rubyamf.org/
rubyamfもZend-AMF同様、先頭バイトは特にチェックしていないのでBlazeDSのクライアントで通信可能。
amf_deserializer.rb
def bodys @amf0_object_default_members_ignore = {} #find the total number of body elements body_count = read_int16_network #Loop over all the body elements 0.upto(body_count - 1) do reset_referencables #The target method target = read_utf #The unique id that the client understands response = read_utf #Get the length of the body element length = read_word32_network #Grab the type of the element type = read_byte #Turn the argument elements into real data value = read(type) #new body body = AMFBody.new(target,response,value) #add the body to the amfobj @amfobj.add_body(body) end end
まとめ
AMF3通信においてボディ部の先頭は、0x0a(AMF0配列)でなければならないかどうかは仕様としてどこかに明確に提示されているのか見つからなかった。
確認した中でPyAMF以外は、そうじゃなくても動作する作りになっている。
これだけ確認することで、やっとどこでも動くクライアントが作れるが正直追いかけどころ間違えてます。
バイナリデータを解析することによってヘッダ部やボディ部も多少暗記できたが、この特技はどこにも使う用途は無いだろう。
id:shot6とカタヤマンは、夜になると延々とAMFのテストしかしないので何を話しかけても無視されます。
話しかけて無視されても気にしないで下さい。夜はAMFのテストの時間なんです。
とりあえず、独自実装でJavaFXからAMFでさくっとつなげるものは出来たが使う場面は無い。