xmlrpclib – XML-RPC 通信のためのクライアントライブラリ

目的:XML-RPC 通信のためのクライアントライブラリ
利用できるバージョン:2.2 以上

既に XML-RPC サーバを構築する SimpleXMLRPCServer を紹介しました。 xmlrpclib モジュールを使用すると Python からどんな言語で書かれた XML-RPC サーバとも通信することができます。

Note

本稿で紹介する全てのサンプルはソースの再配布が可能な xmlrpclib_server.py で定義されたサーバを使用します。ここではリファレンスのために繰り返して説明します。

from SimpleXMLRPCServer import SimpleXMLRPCServer
from xmlrpclib import Binary
import datetime

server = SimpleXMLRPCServer(('localhost', 9000), logRequests=True, allow_none=True)
server.register_introspection_functions()
server.register_multicall_functions()

class ExampleService:
    
    def ping(self):
        """Simple function to respond when called to demonstrate connectivity."""
        return True
        
    def now(self):
        """Returns the server current date and time."""
        return datetime.datetime.now()

    def show_type(self, arg):
        """Illustrates how types are passed in and out of server methods.
        
        Accepts one argument of any type.  
        Returns a tuple with string representation of the value, 
        the name of the type, and the value itself.
        """
        return (str(arg), str(type(arg)), arg)

    def raises_exception(self, msg):
        "Always raises a RuntimeError with the message passed in"
        raise RuntimeError(msg)

    def send_back_binary(self, bin):
        "Accepts single Binary argument, unpacks and repacks it to return it"
        data = bin.data
        response = Binary(data)
        return response

server.register_instance(ExampleService())

try:
    print 'Use Control-C to exit'
    server.serve_forever()
except KeyboardInterrupt:
    print 'Exiting'

サーバへ接続する

クライアントからサーバへ接続する最も簡単な方法はサーバの URI を渡して ServerProxy オブジェクトをインスタンス化することです。例えば、デモサーバはローカルホストの9000番ポートで起動します。

import xmlrpclib

server = xmlrpclib.ServerProxy('http://localhost:9000')
print 'Ping:', server.ping()

このケースでは、そのサーバサービスの ping() メソッドは引数を受け取らず、1つのブーリアン値を返します。

$ python xmlrpclib_ServerProxy.py
Ping: True

代替の通信方法をサポートするために他のオプションが利用できます。ベーシック認証がそうであるように HTTP と HTTPS の両方ともそのボックスの外部でサポートされます。通信チャンネルがサポートされている種別ではなかった場合、通信クラスのみを提供する必要があります。それはおもしろい練習問題になります。例えば SMTP 上で XML-RPC を実装することです。全く使い物にはなりませんが、おもしろいです。

verbose オプションは通信エラーが発生したときに解決するためのデバッグ情報を出力します。

import xmlrpclib

server = xmlrpclib.ServerProxy('http://localhost:9000', verbose=True)
print 'Ping:', server.ping()
$ python xmlrpclib_ServerProxy_verbose.py
Ping: connect: (localhost, 9000)
connect fail: ('localhost', 9000)
connect: (localhost, 9000)
connect fail: ('localhost', 9000)
connect: (localhost, 9000)
send: 'POST /RPC2 HTTP/1.0\r\nHost: localhost:9000\r\nUser-Agent: xmlrpclib.py/1.0.1 (by www.pythonware.com)\r\nContent-Type: text/xml\r\nContent-Length: 98\r\n\r\n'
send: "<?xml version='1.0'?>\n<methodCall>\n<methodName>ping</methodName>\n<params>\n</params>\n</methodCall>\n"
reply: 'HTTP/1.0 200 OK\r\n'
header: Server: BaseHTTP/0.3 Python/2.5.1
header: Date: Sun, 06 Jul 2008 19:56:13 GMT
header: Content-type: text/xml
header: Content-length: 129
body: "<?xml version='1.0'?>\n<methodResponse>\n<params>\n<param>\n<value><boolean>1</boolean></value>\n</param>\n</params>\n</methodResponse>\n"
True

他のシステムで使用する必要があるなら、デフォルトエンコーディングを UTF-8 から変更することができます。

import xmlrpclib

server = xmlrpclib.ServerProxy('http://localhost:9000', encoding='ISO-8859-1')
print 'Ping:', server.ping()

サーバは自動的に正しいエンコーディングを検出すべきです。

$ python xmlrpclib_ServerProxy_encoding.py
Ping: True

allow_none オプションは Python の None の値を自動的に nil 値へ変換するかどうか、又はそれがエラーを引き起こすかを制御します。

import xmlrpclib

server = xmlrpclib.ServerProxy('http://localhost:9000', allow_none=True)
print 'Allowed:', server.show_type(None)

server = xmlrpclib.ServerProxy('http://localhost:9000', allow_none=False)
print 'Not allowed:', server.show_type(None)

エラーは、クライアントが None を許容しない場合はローカル側で発生しますが、サーバ内で None を許容しないように設定されている場合はサーバ側で発生させることもできます。

$ python xmlrpclib_ServerProxy_allow_none.py
Allowed: ['None', "<type 'NoneType'>", None]
Not allowed:
Traceback (most recent call last):
  File "/Users/dhellmann/Documents/PyMOTW/in_progress/xmlrpclib/xmlrpclib_ServerProxy_allow_none.py", line 17, in <module>
    print 'Not allowed:', server.show_type(None)
  File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 1147, in __call__
    return self.__send(self.__name, args)
  File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 1431, in __request
    allow_none=self.__allow_none)
  File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 1080, in dumps
    data = m.dumps(params)
  File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 623, in dumps
    dump(v, write)
  File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 635, in __dump
    f(self, value, write)
  File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 639, in dump_nil
    raise TypeError, "cannot marshal None unless allow_none is enabled"
TypeError: cannot marshal None unless allow_none is enabled

use_datetime オプションは datetime と関連オブジェクトをプロキシへ渡す、又はサーバからそれらのオブジェクトを受け取ります。もし use_datetime が False なら、内部の DateTime クラスが日付表示の代わりに使用されます。

データ型

XML-RPC プロトコルは制限された共通データ型セットを認識します。そのデータ型は引数として渡されるか、値を返します。そして、もっと複雑なデータ構造を作成するために組み合わせることができます。

import xmlrpclib
import datetime

server = xmlrpclib.ServerProxy('http://localhost:9000')

for t, v in [ ('boolean', True), 
              ('integer', 1),
              ('floating-point number', 2.5),
              ('string', 'some text'), 
              ('datetime', datetime.datetime.now()),
              ('array', ['a', 'list']),
              ('array', ('a', 'tuple')),
              ('structure', {'a':'dictionary'}),
            ]:
    print '%-22s:' % t, server.show_type(v)

単純なデータ型です。

$ python xmlrpclib_types.py
boolean               : ['True', "<type 'bool'>", True]
integer               : ['1', "<type 'int'>", 1]
floating-point number : ['2.5', "<type 'float'>", 2.5]
string                : ['some text', "<type 'str'>", 'some text']
datetime              : ['20080706T16:22:49', "<type 'instance'>", <DateTime '20080706T16:22:49' at a5d030>]
array                 : ["['a', 'list']", "<type 'list'>", ['a', 'list']]
array                 : ["['a', 'tuple']", "<type 'list'>", ['a', 'tuple']]
structure             : ["{'a': 'dictionary'}", "<type 'dict'>", {'a': 'dictionary'}]

任意の複雑な値を作成するためにネストすることができます。

import xmlrpclib
import datetime
import pprint

server = xmlrpclib.ServerProxy('http://localhost:9000')

data = { 'boolean':True, 
         'integer': 1,
         'floating-point number': 2.5,
         'string': 'some text',
         'datetime': datetime.datetime.now(),
         'array': ['a', 'list'],
         'array': ('a', 'tuple'),
         'structure': {'a':'dictionary'},
         }
arg = []
for i in range(3):
    d = {}
    d.update(data)
    d['integer'] = i
    arg.append(d)

print 'Before:'
pprint.pprint(arg)

print
print 'After:'
pprint.pprint(server.show_type(arg)[-1])
$ python xmlrpclib_types_nested.py
Before:
[{'array': ('a', 'tuple'),
  'boolean': True,
  'datetime': datetime.datetime(2008, 7, 6, 16, 24, 52, 348849),
  'floating-point number': 2.5,
  'integer': 0,
  'string': 'some text',
  'structure': {'a': 'dictionary'}},
 {'array': ('a', 'tuple'),
  'boolean': True,
  'datetime': datetime.datetime(2008, 7, 6, 16, 24, 52, 348849),
  'floating-point number': 2.5,
  'integer': 1,
  'string': 'some text',
  'structure': {'a': 'dictionary'}},
 {'array': ('a', 'tuple'),
  'boolean': True,
  'datetime': datetime.datetime(2008, 7, 6, 16, 24, 52, 348849),
  'floating-point number': 2.5,
  'integer': 2,
  'string': 'some text',
  'structure': {'a': 'dictionary'}}]

After:
[{'array': ['a', 'tuple'],
  'boolean': True,
  'datetime': <DateTime '20080706T16:24:52' at a5be18>,
  'floating-point number': 2.5,
  'integer': 0,
  'string': 'some text',
  'structure': {'a': 'dictionary'}},
 {'array': ['a', 'tuple'],
  'boolean': True,
  'datetime': <DateTime '20080706T16:24:52' at a5bf30>,
  'floating-point number': 2.5,
  'integer': 1,
  'string': 'some text',
  'structure': {'a': 'dictionary'}},
 {'array': ['a', 'tuple'],
  'boolean': True,
  'datetime': <DateTime '20080706T16:24:52' at a5bf80>,
  'floating-point number': 2.5,
  'integer': 2,
  'string': 'some text',
  'structure': {'a': 'dictionary'}}]

オブジェクトを渡す

Python クラスのインスタンスは構造体のように扱われてディクショナリとして渡されます。それはディクショナリの値のようにそのオブジェクトの属性を持ちます。

import xmlrpclib

class MyObj:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def __repr__(self):
        return 'MyObj(%s, %s)' % (repr(self.a), repr(self.b))

server = xmlrpclib.ServerProxy('http://localhost:9000')

o = MyObj(1, 'b goes here')
print 'o=', o
print server.show_type(o)

o2 = MyObj(2, o)
print 'o2=', o2
print server.show_type(o2)

クラスの一部としてインスタンス化される、サーバ(又はクライアント)へ渡される値は何もエンコードしないので、クライアントからサーバへ渡されてクライアントへ返された値はディクショナリになります。

$ python xmlrpclib_types_object.py
o= MyObj(1, 'b goes here')
["{'a': 1, 'b': 'b goes here'}", "<type 'dict'>", {'a': 1, 'b': 'b goes here'}]
o2= MyObj(2, MyObj(1, 'b goes here'))
["{'a': 2, 'b': {'a': 1, 'b': 'b goes here'}}", "<type 'dict'>", {'a': 2, 'b': {'a': 1, 'b': 'b goes here'}}]

バイナリデータ

サーバへ渡される全ての値はエンコードとエスケープが自動的に行われます。しかし、データ型によっては無効な XML データの文字を含む可能性があります。例えば、バイナリイメージのデータは ASCII の制御文字 0 から 31 の範囲のバイト値を含む可能性があります。もしバイナリデータを渡す必要があるなら Binary クラスを使用して通信のためにエンコードすることが最も良い方法です。

import xmlrpclib

server = xmlrpclib.ServerProxy('http://localhost:9000')

s = 'This is a string with control characters' + '\0'
print 'Local string:', s

data = xmlrpclib.Binary(s)
print 'As binary:', server.send_back_binary(data)

print 'As string:', server.show_type(s)

show_type() へ NULL バイトを含む文字列を渡すと XML パーサで例外が発生します。

$ python xmlrpclib_Binary.py
Local string: This is a string with control characters
As binary: This is a string with control characters
As string:
Traceback (most recent call last):
  File "/Users/dhellmann/Documents/PyMOTW/in_progress/xmlrpclib/xmlrpclib_Binary.py", line 21, in <module>
    print 'As string:', server.show_type(s)
  File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 1147, in __call__
    return self.__send(self.__name, args)
  File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 1437, in __request
    verbose=self.__verbose
  File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 1201, in request
    return self._parse_response(h.getfile(), sock)
  File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 1340, in _parse_response
    return u.close()
  File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 787, in close
    raise Fault(**self._stack[0])
xmlrpclib.Fault: <Fault 1: "<class 'xml.parsers.expat.ExpatError'>:not well-formed (invalid token): line 6, column 55">

Binary オブジェクトは pickle を使用してオブジェクトを送信するために使用することもできます。どんな実行コードが送信されて実行されるかに関しては通常セキュリティの問題があります(例えば、通信チェネルがセキュアだと確信がない限りこれを実行してはいけません)。

import xmlrpclib
import cPickle as pickle

class MyObj:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def __repr__(self):
        return 'MyObj(%s, %s)' % (repr(self.a), repr(self.b))

server = xmlrpclib.ServerProxy('http://localhost:9000')

o = MyObj(1, 'b goes here')
print 'Local:', o, id(o)

print 'As object:', server.show_type(o)

p = pickle.dumps(o)
b = xmlrpclib.Binary(p)
r = server.send_back_binary(b)

o2 = pickle.loads(r.data)
print 'From pickle:', o2, id(o2)

Binary インスタンスのデータ属性が pickle 化されたオブジェクトを含みます。そのため、使用する前に unpickle 化しなければならないことを覚えておいてください。その結果は(新たな id 値を持つ)違うオブジェクトです。

$ python xmlrpclib_Binary_pickle.py
Local: MyObj(1, 'b goes here') 9620936
As object: ["{'a': 1, 'b': 'b goes here'}", "<type 'dict'>", {'a': 1, 'b': 'b goes here'}]
From pickle: MyObj(1, 'b goes here') 11049200

例外の取り扱い

XML-RPC サーバは他の言語でも実装される可能性があるので、例外クラスは直接的には転送されません。その代わり、サーバ側で発生した例外は Fault オブジェクトへ変換されてクライアント側でローカルの例外のように発生します。

import xmlrpclib

server = xmlrpclib.ServerProxy('http://localhost:9000')
try:
    server.raises_exception('A message')
except Exception, err:
    print 'Fault code:', err.faultCode
    print 'Message   :', err.faultString
$ python xmlrpclib_exception.py
Fault code: 1
Message   : <type 'exceptions.RuntimeError'>:A message

MultiCall

Multicall は1度に1つ以上の呼び出しの送信を許容する XML-RPC プロトコルの機能拡張です。そして、呼び出し側へまとめてレスポンスが返されます。Python 2.4 で MultiCall クラスが xmlrpclib に追加されました。 MultiCall インスタンスを使用するには ServerProxy と同様にそのインスタンスでメソッドを実行します。そして、引数なしでそのオブジェクトを呼び出します。その出力はそのメソッドの実行結果のイテレータになります。

import xmlrpclib

server = xmlrpclib.ServerProxy('http://localhost:9000')

multicall = xmlrpclib.MultiCall(server)
multicall.ping()
multicall.show_type(1)
multicall.show_type('string')

for i, r in enumerate(multicall()):
    print i, r
$ python xmlrpclib_MultiCall.py
0 True
1 ['1', "<type 'int'>", 1]
2 ['string', "<type 'str'>", 'string']

もしその呼び出しの1つが Fault を引き起こすか、例外を発生させる場合、実行結果がイテレータから生成されるときに例外が発生します。そして、後続の実行結果は生成されません。

import xmlrpclib

server = xmlrpclib.ServerProxy('http://localhost:9000')

multicall = xmlrpclib.MultiCall(server)
multicall.ping()
multicall.show_type(1)
multicall.raises_exception('Next to last call stops execution')
multicall.show_type('string')

for i, r in enumerate(multicall()):
    print i, r
$ python xmlrpclib_MultiCall_exception.py
0 True
1 ['1', "<type 'int'>", 1]
Traceback (most recent call last):
  File "/Users/dhellmann/Documents/PyMOTW/in_progress/xmlrpclib/xmlrpclib_MultiCall_exception.py", line 21, in <module>
    for i, r in enumerate(multicall()):
  File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/xmlrpclib.py", line 949, in __getitem__
    raise Fault(item['faultCode'], item['faultString'])
xmlrpclib.Fault: <Fault 1: "<type 'exceptions.RuntimeError'>:Next to last call stops execution">

See also

xmlrpclib
本モジュールの標準ライブラリドキュメント
SimpleXMLRPCServer
XML-RPC サーバの1つの実装
Bookmark and Share