"xmlrpc.client" --- XML-RPC クライアントアクセス
************************************************

**ソースコード:** Lib/xmlrpc/client.py

======================================================================

XML-RPCはXMLを利用した遠隔手続き呼び出し(Remote Procedure Call)の一種
で、HTTP(S)をトランスポートとして使用します。XML- RPCでは、クライアン
トはリモートサーバ(URIで指定されたサーバ)上のメソッドをパラメータを指
定して呼び出し、構造化されたデータを取得します。このモジュールは、XML-
RPCクライアントの開発をサポートしており、Pythonオブジェクトに適合する
転送用XMLの変換の全てを行います。

警告:

  "xmlrpc.client" モジュールは悪意を持って構築されたデータに対して安全
  ではありません。信頼できないデータや認証されていないデータを解析する
  必要がある場合は、XML の脆弱性 を参照してください。

バージョン 3.5 で変更: HTTPS URI の場合、 "xmlrpc.client" は必要な証明
書検証とホスト名チェックを全てデフォルトで行います。

class xmlrpc.client.ServerProxy(uri, transport=None, encoding=None, verbose=False, allow_none=False, use_datetime=False, use_builtin_types=False, *, headers=(), context=None)

   "ServerProxy" は、リモートのXML-RPCサーバとの通信を管理するオブジェ
   クトです。最初のパラメータはURI(Uniform Resource Indicator)で、通常
   はサーバのURLを指定します。2番目のパラメータにはトランスポート・フ
   ァクトリを指定する事ができます。トランスポート・ファクトリを省略し
   た場合、URLが https: ならモジュール内部の "SafeTransport" インスタ
   ンスを使用し、それ以外の場合にはモジュール内部の "Transport" インス
   タンスを使用します。オプションの 3 番目の引数はエンコード方法で、デ
   フォルトでは UTF-8 です。オプションの 4 番目の引数はデバッグフラグ
   です。

   The following parameters govern the use of the returned proxy
   instance. If *allow_none* is true,  the Python constant "None" will
   be translated into XML; the default behaviour is for "None" to
   raise a "TypeError". This is a commonly-used extension to the XML-
   RPC specification, but isn't supported by all clients and servers;
   see http://ontosys.com/xml-rpc/extensions.php for a description.
   The *use_builtin_types* flag can be used to cause date/time values
   to be presented as "datetime.datetime" objects and binary data to
   be presented as "bytes" objects; this flag is false by default.
   "datetime.datetime", "bytes" and "bytearray" objects may be passed
   to calls. The *headers* parameter is an optional sequence of HTTP
   headers to send with each request, expressed as a sequence of
   2-tuples representing the header name and value. (e.g. *[('Header-
   Name', 'value')]*). The obsolete *use_datetime* flag is similar to
   *use_builtin_types* but it applies only to date/time values.

バージョン 3.3 で変更: *use_builtin_types* フラグが追加されました。

バージョン 3.8 で変更: *headers* 引数が追加されました。HTTP及びHTTPS通
信の両方で、 "http://user:pass@host:port/path" のようなHTTP基本認証の
ための拡張URL構文をサポートしています。 "user:pass" はbase64でエンコー
ドしてHTTPの'Authorization 'ヘッダとなり、XML-RPCメソッド呼び出し時に
接続処理の一部としてリモートサーバに送信されます。リモートサーバが基本
認証を要求する場合のみ、この機能を利用する必要があります。HTTPS URL が
与えられたなら、 *context* は "ssl.SSLContext" にでき、基底のHTTP接続
のSSL設定を設定します。生成されるインスタンスはリモートサーバへのプロ
クシオブジェクトで、RPC呼び出しを行う為のメソッドを持ちます。リモート
サーバがイントロスペクション APIをサポートしている場合は、リモートサー
バのサポートするメソッドを検索 (サービス検索)やサーバのメタデータの取
得なども行えます。以下の型をXMLに変換(XMLを通じてマーシャルする)する事
ができます (特別な指定がない限り、逆変換でも同じ型として変換されます):

+------------------------+---------------------------------------------------------+
| XML-RPC の型           | Python の型                                             |
|========================|=========================================================|
| "boolean"              | "bool"                                                  |
+------------------------+---------------------------------------------------------+
| "int", "i1", "i2",     | "int" in range from -2147483648 to 2147483647. Values   |
| "i4", "i8" または      | get the "<int>" tag.                                    |
| "biginteger"           |                                                         |
+------------------------+---------------------------------------------------------+
| "double" または        | "float".  Values get the "<double>" tag.                |
| "float"                |                                                         |
+------------------------+---------------------------------------------------------+
| "string"               | "str"                                                   |
+------------------------+---------------------------------------------------------+
| "array"                | 適合する要素を持つ "list" または "tuple"。array は      |
|                        | "list" として返しま す。                                |
+------------------------+---------------------------------------------------------+
| "struct"               | "dict" 。キーは文字列のみ。全ての値は変換可能でなくては |
|                        | ならない。ユー ザー定義型を渡すこともできます。         |
|                        | "__dict__" の属性のみ転送されます。                     |
+------------------------+---------------------------------------------------------+
| "dateTime.iso8601"     | "DateTime" または "datetime.datetime"。返される型は     |
|                        | *use_builtin_types* および *use_datetime* の値に依りま  |
|                        | す。                                                    |
+------------------------+---------------------------------------------------------+
| "base64"               | "Binary"、 "bytes" または "bytearray"。返される型は     |
|                        | *use_builtin_types* フラグの値に依ります。              |
+------------------------+---------------------------------------------------------+
| "nil"                  | "None" 定数。 *allow_none* が真の場合にのみ渡すことが出 |
|                        | 来ます。                                                |
+------------------------+---------------------------------------------------------+
| "bigdecimal"           | "decimal.Decimal".  Returned type only.                 |
+------------------------+---------------------------------------------------------+

上記のXML-RPCでサポートする全データ型を使用することができます。メソッ
ド呼び出し時、XML- RPCサーバエラーが発生すると "Fault" インスタンスを
送出し、HTTP/HTTPSトランスポート層でエラーが発生した場合には
"ProtocolError" を送出します。 "Error" をベースとする "Fault" と
"ProtocolError" の両方が発生します。現在のところxmlrpclibでは組み込み
型のサブクラスのインスタンスをマーシャルすることはできません。文字列を
渡す場合、 "<", ">", "&" などのXMLで特殊な意味を持つ文字は自動的にエス
ケープされます。しかし、ASCII値0〜31の制御文字(もちろん、タブ'TAB',改
行'LF',リターン'CR'は除く)などのXMLで使用することのできない文字を使用
することはできず、使用するとそのXML-RPCリクエストはwell-formedなXMLと
はなりません。そのようなバイト列を渡す必要がある場合は、 "bytes",
"bytearray" クラスまたは後述の "Binary" ラッパクラスを使用してください
。"Server" は、後方互換性の為に "ServerProxy" の別名として残されていま
す。新しいコードでは "ServerProxy" を使用してください。

バージョン 3.5 で変更: *context* 引数が追加されました。

バージョン 3.6 で変更: Added support of type tags with prefixes (e.g.
"ex:nil"). Added support of unmarshalling additional types used by
Apache XML-RPC implementation for numerics: "i1", "i2", "i8",
"biginteger", "float" and "bigdecimal". See
http://ws.apache.org/xmlrpc/types.html for a description.

参考:

  XML-RPC HOWTO
     数種類のプログラミング言語で記述された XML-RPCの操作とクライアン
     トソフトウェアの素晴らしい説明が掲載されています。 XML- RPCクライ
     アントの開発者が知っておくべきことがほとんど全て記載されています
     。

  XML-RPC Introspection
     インストロペクションをサポートする、 XML-RPC プロトコルの拡張を解
     説しています。

  XML-RPC Specification
     公式の仕様

  XML-RPC 非公式正誤表
     Fredrik Lundh による "unofficial errata, intended to clarify
     certain details in the XML-RPC specification, as well as hint at
     'best practices' to use when designing your own XML-RPC
     implementations."


ServerProxy オブジェクト
========================

"ServerProxy" インスタンスの各メソッドはそれぞれXML-RPCサーバの遠隔手
続き呼び出しに対応しており、メソッドが呼び出されると名前と引数をシグネ
チャとしてRPCを実行します(同じ名前のメソッドでも、異なる引数シグネチャ
によってオーバロードされます)。RPC実行後、変換された値を返すか、または
"Fault" オブジェクトもしくは "ProtocolError" オブジェクトでエラーを通
知します。

予約属性 "system" から、XMLイントロスペクションAPIの一般的なメソッドを
利用する事ができます:

ServerProxy.system.listMethods()

   XML-RPCサーバがサポートするメソッド名(system以外)を格納する文字列の
   リストを返します。

ServerProxy.system.methodSignature(name)

   XML-RPCサーバで実装されているメソッドの名前を指定し、利用可能なシグ
   ネチャの配列を取得します。シグネチャは型のリストで、先頭の型は戻り
   値の型を示し、以降はパラメータの型を示します。

   XML-RPCでは複数のシグネチャ(オーバロード)を使用することができるので
   、単独のシグネチャではなく、シグネチャのリストを返します。

   シグネチャは、メソッドが使用する最上位のパラメータにのみ適用されま
   す。例えばあるメソッドのパラメータが構造体の配列で戻り値が文字列の
   場合、シグネチャは単に"string, array" となります。パラメータが三つ
   の整数で戻り値が文字列の場合は"string, int, int, int"となります。

   メソッドにシグネチャが定義されていない場合、配列以外の値が返ります
   。 Pythonでは、この値はlist以外の値となります。

ServerProxy.system.methodHelp(name)

   XML-RPCサーバで実装されているメソッドの名前を指定し、そのメソッドを
   解説する文書文字列を取得します。文書文字列を取得できない場合は空文
   字列を返します。文書文字列にはHTMLマークアップが含まれます。

バージョン 3.5 で変更: Instances of "ServerProxy" support the *context
manager* protocol for closing the underlying transport.

以下は、動作する例です。サーバ側のコード:

   from xmlrpc.server import SimpleXMLRPCServer

   def is_even(n):
       return n % 2 == 0

   server = SimpleXMLRPCServer(("localhost", 8000))
   print("Listening on port 8000...")
   server.register_function(is_even, "is_even")
   server.serve_forever()

上記のサーバーに対するクライアントコード:

   import xmlrpc.client

   with xmlrpc.client.ServerProxy("http://localhost:8000/") as proxy:
       print("3 is even: %s" % str(proxy.is_even(3)))
       print("100 is even: %s" % str(proxy.is_even(100)))


DateTime オブジェクト
=====================

class xmlrpc.client.DateTime

   このクラスは、エポックからの秒数、タプルで表現された時刻、ISO 8601
   形式の時間/日付文字列、 "datetime.datetime", のインスタンスのいずれ
   かで初期化することができます。このクラスには以下のメソッドがあり、
   主にコードをマーシャル/アンマーシャルするための内部処理を行います:

   decode(string)

      文字列をインスタンスの新しい時間を示す値として指定します。

   encode(out)

      出力ストリームオブジェクト *out* に、XML-RPCエンコーディングの
      "DateTime" 値を出力します。

   また、比較と "__repr__()" で定義される演算子を使用することができま
   す。

以下は、動作する例です。サーバ側のコード:

   import datetime
   from xmlrpc.server import SimpleXMLRPCServer
   import xmlrpc.client

   def today():
       today = datetime.datetime.today()
       return xmlrpc.client.DateTime(today)

   server = SimpleXMLRPCServer(("localhost", 8000))
   print("Listening on port 8000...")
   server.register_function(today, "today")
   server.serve_forever()

上記のサーバーに対するクライアントコード:

   import xmlrpc.client
   import datetime

   proxy = xmlrpc.client.ServerProxy("http://localhost:8000/")

   today = proxy.today()
   # convert the ISO8601 string to a datetime object
   converted = datetime.datetime.strptime(today.value, "%Y%m%dT%H:%M:%S")
   print("Today: %s" % converted.strftime("%d.%m.%Y, %H:%M"))


Binary オブジェクト
===================

class xmlrpc.client.Binary

   このクラスは、バイト列データ(NULを含む)で初期化することができます。
   "Binary" の内容は、属性で参照します:

   data

      "Binary" インスタンスがカプセル化しているバイナリデータ。このデ
      ータは "bytes" オブジェクトです。

   "Binary" オブジェクトは以下のメソッドを持ち、主に内部的にマーシャル
   /アンマーシャル時に使用されます:

   decode(bytes)

      指定されたbase64 "bytes" オブジェクトをデコードし、インスタンス
      のデータとします。

   encode(out)

      バイナリ値をbase64でエンコードし、出力ストリームオブジェクト
      *out* に出力します。

      エンコードされたデータは、 **RFC 2045 section 6.8** にある通り、
      76文字ごとに改行されます。これは、XMC-RPC仕様が作成された時のデ
      ・ファクト・スタンダードのbase64です。

   また、 "__eq__()" および "__ne__()" メソッドで定義される演算子を使
   用することができます。

バイナリオブジェクトの使用例です。 XML-RPCごしに画像を転送します。

   from xmlrpc.server import SimpleXMLRPCServer
   import xmlrpc.client

   def python_logo():
       with open("python_logo.jpg", "rb") as handle:
           return xmlrpc.client.Binary(handle.read())

   server = SimpleXMLRPCServer(("localhost", 8000))
   print("Listening on port 8000...")
   server.register_function(python_logo, 'python_logo')

   server.serve_forever()

クライアントは画像を取得して、ファイルに保存します。

   import xmlrpc.client

   proxy = xmlrpc.client.ServerProxy("http://localhost:8000/")
   with open("fetched_python_logo.jpg", "wb") as handle:
       handle.write(proxy.python_logo().data)


Fault オブジェクト
==================

class xmlrpc.client.Fault

   "Fault" オブジェクトは、XML-RPCのfaultタグの内容をカプセル化してお
   り、以下の属性を持ちます:

   faultCode

      失敗のタイプを示す文字列。

   faultString

      失敗の診断メッセージを含む文字列。

以下のサンプルでは、複素数型のオブジェクトを返そうとして、故意に
"Fault" を起こしています。

   from xmlrpc.server import SimpleXMLRPCServer

   # A marshalling error is going to occur because we're returning a
   # complex number
   def add(x, y):
       return x+y+0j

   server = SimpleXMLRPCServer(("localhost", 8000))
   print("Listening on port 8000...")
   server.register_function(add, 'add')

   server.serve_forever()

上記のサーバーに対するクライアントコード:

   import xmlrpc.client

   proxy = xmlrpc.client.ServerProxy("http://localhost:8000/")
   try:
       proxy.add(2, 5)
   except xmlrpc.client.Fault as err:
       print("A fault occurred")
       print("Fault code: %d" % err.faultCode)
       print("Fault string: %s" % err.faultString)


ProtocolError オブジェクト
==========================

class xmlrpc.client.ProtocolError

   "ProtocolError" オブジェクトはトランスポート層で発生したエラー(URI
   で指定したサーバが見つからなかった場合に発生する404 'not found'など
   )の内容を示し、以下の属性を持ちます:

   url

      エラーの原因となったURIまたはURL。

   errcode

      エラーコード。

   errmsg

      エラーメッセージまたは診断文字列。

   headers

      エラーの原因となったHTTP/HTTPSリクエストを含む辞書。

次の例では、不適切な URI を利用して、故意に "ProtocolError" を発生させ
ています:

   import xmlrpc.client

   # create a ServerProxy with a URI that doesn't respond to XMLRPC requests
   proxy = xmlrpc.client.ServerProxy("http://google.com/")

   try:
       proxy.some_method()
   except xmlrpc.client.ProtocolError as err:
       print("A protocol error occurred")
       print("URL: %s" % err.url)
       print("HTTP/HTTPS headers: %s" % err.headers)
       print("Error code: %d" % err.errcode)
       print("Error message: %s" % err.errmsg)


MultiCall オブジェクト
======================

遠隔のサーバに対する複数の呼び出しをひとつのリクエストにカプセル化する
方法は、http://www.xmlrpc.com/discuss/msgReader%241208 で示されていま
す。

class xmlrpc.client.MultiCall(server)

   巨大な (boxcar) メソッド呼び出しに使えるオブジェクトを作成します。
   *server* には最終的に呼び出しを行う対象を指定します。作成した
   MultiCall オブジェクトを使って呼び出しを行うと、即座に "None" を返
   し、呼び出したい手続き名とパラメタを "MultiCall" オブジェクトに保存
   するだけに留まります。オブジェクト自体を呼び出すと、それまでに保存
   しておいたすべての呼び出しを単一の "system.multicall" リクエストの
   形で伝送します。呼び出し結果は *ジェネレータ* になります。このジェ
   ネレータにわたってイテレーションを行うと、個々の呼び出し結果を返し
   ます。

以下は、このクラスの使用例です。サーバ側のコード:

   from xmlrpc.server import SimpleXMLRPCServer

   def add(x, y):
       return x + y

   def subtract(x, y):
       return x - y

   def multiply(x, y):
       return x * y

   def divide(x, y):
       return x // y

   # A simple server with simple arithmetic functions
   server = SimpleXMLRPCServer(("localhost", 8000))
   print("Listening on port 8000...")
   server.register_multicall_functions()
   server.register_function(add, 'add')
   server.register_function(subtract, 'subtract')
   server.register_function(multiply, 'multiply')
   server.register_function(divide, 'divide')
   server.serve_forever()

上記のサーバーに対するクライアントコード:

   import xmlrpc.client

   proxy = xmlrpc.client.ServerProxy("http://localhost:8000/")
   multicall = xmlrpc.client.MultiCall(proxy)
   multicall.add(7, 3)
   multicall.subtract(7, 3)
   multicall.multiply(7, 3)
   multicall.divide(7, 3)
   result = multicall()

   print("7+3=%d, 7-3=%d, 7*3=%d, 7//3=%d" % tuple(result))


補助関数
========

xmlrpc.client.dumps(params, methodname=None, methodresponse=None, encoding=None, allow_none=False)

   *params* を XML-RPC リクエストの形式に変換します。 *methodresponse*
   が真の場合、XML-RPC レスポンスの形式に変換します。 *params* に指定
   できるのは、引数からなるタプルか "Fault" 例外クラスのインスタンスで
   す。 *methodresponse* が真の場合、単一の値だけを返します。従って、
   *params* の長さも 1 でなければなりません。 *encoding* を指定した場
   合、生成される XML のエンコード方式になります。デフォルトは UTF-8
   です。 Python の "None" は標準の XML-RPC には利用できません。
   "None" を使えるようにするには、 *allow_none* を真にして、拡張機能つ
   きにしてください。

xmlrpc.client.loads(data, use_datetime=False, use_builtin_types=False)

   XML-RPC リクエストまたはレスポンスを "(params, methodname)" の形式
   をとる Python オブジェクトにします。 *params* は引数のタプルです。
   *methodname* は文字列で、パケット中にメソッド名がない場合には
   "None" になります。例外条件を示す XML-RPC パケットの場合には、
   "Fault" 例外を送出します。 *use_builtin_types* フラグは
   "datetime.datetime" のオブジェクトとして日付/時刻を、 "bytes" のオ
   ブジェクトとしてバイナリデータを表現する時に使用し、デフォルトでは
   false に設定されています。

   非推奨となった *use_datetime* フラグは *use_builtin_types* に似てい
   ますが date/time 値にのみ適用されます。

   バージョン 3.3 で変更: *use_builtin_types* フラグが追加されました。


クライアントのサンプル
======================

   # simple test program (from the XML-RPC specification)
   from xmlrpc.client import ServerProxy, Error

   # server = ServerProxy("http://localhost:8000") # local server
   with ServerProxy("http://betty.userland.com") as proxy:

       print(proxy)

       try:
           print(proxy.examples.getStateName(41))
       except Error as v:
           print("ERROR", v)

XML-RPC サーバに HTTP プロキシを経由して接続する場合、カスタムトランス
ポートを定義する必要があります。以下に例を示します:

   import http.client
   import xmlrpc.client

   class ProxiedTransport(xmlrpc.client.Transport):

       def set_proxy(self, host, port=None, headers=None):
           self.proxy = host, port
           self.proxy_headers = headers

       def make_connection(self, host):
           connection = http.client.HTTPConnection(*self.proxy)
           connection.set_tunnel(host, headers=self.proxy_headers)
           self._connection = host, connection
           return connection

   transport = ProxiedTransport()
   transport.set_proxy('proxy-server', 8080)
   server = xmlrpc.client.ServerProxy('http://betty.userland.com', transport=transport)
   print(server.examples.getStateName(41))


クライアントとサーバーの利用例
==============================

SimpleXMLRPCServer の例 を参照してください。

-[ 脚注 ]-

[1] このアプローチは >>`<<a discussion on xmlrpc.com >>`<<_ にて最初に
    著されました。
