XML-RPCサーバとかの実験

ちょっとした進捗管理の実験に、ちょっとしたサーバを立てようと思って、しかしフレームワーク使うほどでもないなあとか、既存のもの使うのやだなあとか、なんか新しいもの勉強したいなあ、とかいう欲求からついカッとなって自前で変なものを作ろうとした際に、可能な限り環境非依存でとか考えてXML-RPCを使ったりした*1。のでその記録。
XML-RPC自体は単なる文字列(XML)によるHTTPでのRPC通信でSOAPの親戚みたいな感じ。同じドメインとしては DCOMとか CORBAとかがある。至極重そうなんだけど、圧縮は効くし「思ったより速い」んだそうな(ホントですかい)。

requirements

pythonでclientやるには xmlrpclibというのを使うと良いらしい。rubyだとxmlrpc/(server|client)とかいうやつで、ブロックでaddhandlerできたりして楽しそう。しかしなんと目的のデータセットが入ったマシンにrubyが入ってない(泣)。残念
xmlrpclibはデフォルトでも入っているみたいなんだけど、いろいろいじってみようかと思って一応ローカルに引っ張ってきた。

 % easy_install --install-dir=~/lib/python/site-packages --build-dir=~/tmp/ xmlrpclib

サーバ

で、サーバはSimpleXMLRPCServerがあるのでこれを使う。使い方はいわゆるpythonの〜Serverタイプの書き方そのまんまって感じで。

from SimpleXMLRPCServer import SimpleXMLRPCServer

svr = SimpleXMLRPCServer(('', 8090))

svr.register_function(lambda x,y: x+y, 'add')
svr.register_function(lambda x,y: x-y, 'sub')
svr.register_function(lambda x,y: x*y, 'mul')
svr.register_function(lambda x,y: x/y, 'div')

svr.serve_forever()

本当はクラスを定義してPOSTリクエストを処理するのが正しいッぽい。ちゃんと調べてない。

テスト

>>> s = xmlrpclib.ServerProxy("http://quartz:8090/test.cgi")
>>> s.add(32,44)
76
>>> s.sub(32,44)
-12
>>> s.mul(32,44)
1408
>>> s.div(32,44)
0
>>> s.div(32.0,44)
0.72727272727272729
>>> s.div(32,"hoge")
Traceback (most recent call last):
  :
xmlrpclib.Fault: <Fault 1: "<type 'exceptions.TypeError'>:unsupported operand type(s) for /: 'int' and 'str'">
>>> s.add(1)
Traceback (most recent call last):
  :
xmlrpclib.Fault: <Fault 1: "<type 'exceptions.TypeError'>:<lambda>() takes exactly 2 arguments (1 given)">
>>>

おお動いてる。

しかし。。。

>>> timeit.Timer('s.add(3,3)', 'import xmlrpclib; s=xmlrpclib.ServerProxy("http
://quartz:8090/test.cgi")').timeit(1)
5.0199999809265137
>>> timeit.Timer('s.div(3,3)', 'import xmlrpclib; s=xmlrpclib.ServerProxy("http
://quartz:8090/test.cgi")').timeit(1)
5.0139999389648438

て感じにすばらしくうすノロい。こんなもんなんだろうか。使えない気がしてきた。ちゃんとクラスでやって、ハンドラ実装すれば早くなるんだろうか。どのあたりが「思ったよりも速い」の? なんか僕間違ってるのかな?

中身はいったいどんなもの?

基本的にはパラメータとかをXMLシリアライズ(マーシャリング)したものをHTTPで送りつけたり返してもらったりするだけ。xmlrpclib.dumpsでどんなデータを送っているかを見ることが出来る。

>>> print xmlrpclib.dumps(tuple([{'int': 1, 'float': 3.141592, 'string': "msg in a capacitor", 'array': [1,2,3,4], 'tuple': (1,2,3,4) }]), 'rene.versioncheck')
<?xml version='1.0'?>
 <methodCall>
  <methodName>rene.versioncheck</methodName>
  <params>
   <param>
    <value><struct>
      <member>
       <name>int</name>
       <value><int>1</int></value>
      </member>
      <member>
       <name>array</name>
       <value><array><data>
         <value><int>1</int></value>
         <value><int>2</int></value>
         <value><int>3</int></value>
         <value><int>4</int></value>
         </data></array></value>
      </member>
      <member>
       <name>float</name>
       <value><double>3.1415920000000002</double></value>
      </member>
      <member>
       <name>string</name>
       <value><string>msg in a capacitor</string></value>
      </member>
      <member>
       <name>tuple</name>
       <value><array><data>
         <value><int>1</int></value>
         <value><int>2</int></value>
         <value><int>3</int></value>
         <value><int>4</int></value>
         </data></array></value>
      </member>
     </struct></value>
   </param>
  </params>
 </methodCall>

わー。なんぞこのもったいないお化け。paramでvalueで、かつstruct なのか。うーんよくわかんない。
逆方向はloads().

>>> print xmlrpclib.loads( xmlrpclib.dumps(tuple([{'int': 1, 'float': 3.141592, 'string': "msg in a capacitor", 'array': [1,2,3,4], 'tuple': (1,2,3,4) }]), 'rene.versioncheck'))
(({'int': 1, 'array': [1, 2, 3, 4], 'float': 3.1415920000000002, 'string': 'msg in a capacitor', 'tuple': [1, 2, 3, 4]},), u'rene.versioncheck')

ふむ、tupleだろうがarrayだろうが全てはarrayにされてしまうのね。

で、結局

深追いするのが嫌で、普通にSimpleHTTPServerで作ってしまった。家帰って時間があったらもう少し見てみよう。

追記:なんか寝ぼけてタグに [ajax]を入れていた間抜けな僕。書きかけて途中でやめた別の記事の残骸だったり・・・

*1:いやちょっとそれ既存のものでは