"xmlrpc.client" --- XML-RPC 客户端访问
**************************************

**源代码:** Lib/xmlrpc/client.py

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

XML-RPC 是一种远程过程调用方法，它以使用 HTTP(S) 传递的 XML 作为载体。
通过它，客户端可以在远程服务器（服务器以 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 (统一资源定位符)，通常就是服务器的 URL。 可选的第二
   个参数为传输工厂实例；在默认情况下对于 https: URL 是一个内部
   "SafeTransport" 实例，在其他情况下则是一个内部 HTTP "Transport" 实
   例。 可选的第三个参数为编码格式，默认为 UTF-8。 可选的第四个参数为
   调试旗标。

   下列形参控制所返回代理实例的使用。 如果 *allow_none* 为真值，则
   Python 常量 "None" 将被转写至 XML；默认行为是针对 "None" 引发
   "TypeError"。 这是对 XML-RPC 规格的一个常用扩展，但并不被所有客户端
   和服务器所支持；请参阅 http://ontosys.com/xml-rpc/extensions.php 了
   解详情。 *use_builtin_types* 旗标可被用来将日期/时间值表示为
   "datetime.datetime" 对象而将二进制数据表示为 "bytes" 对象；此旗标默
   认为假值。 "datetime.datetime", "bytes" 和 "bytearray" 对象可以被传
   给调用操作。 *headers* 形参为可选的随每次请求发送的 HTTP 标头序列，
   其形式为包含代表标头名称和值的二元组序列 (例如 "[('Header-Name',
   'value')]")。 已淘汰的 *use_datetime* 旗标与 *use_builtin_types* 类
   似但它只针对日期/时间值。

在 3.3 版更改: 增加了 *use_builtin_types* 旗标。

在 3.8 版更改: 增加了 *headers* 形参。HTTP 和 HTTPS 传输均支持用于
HTTP 基本身份验证的 URL 语法扩展: "http://user:pass@host:port/path"。
"user:pass" 部分将以 base64 编码为 HTTP 'Authorization' 标头，并在发起
调用 XML-RPC 方法时作为连接过程的一部分发送给远程服务器。 你只需要在远
程服务器要求基本身份验证账号和密码时使用此语法。 如果提供了 HTTPS URL
，*context* 可以为 "ssl.SSLContext" 并配置有下层 HTTPS 连接的 SSL 设置
。返回的实例是一个代理对象，具有可被用来在远程服务器上发起相应 RPC 调
用的方法。 如果远程服务器支持内省 API，则也可使用该代理对象在远程服务
器上查询它所支持的方法（服务发现）并获取其他服务器相关的元数据适用的类
型（即可通过 XML 生成 marshall 对象），包括如下类型（除了已说明的例外
，它们都会被反 marshall 为同样的 Python 类型）:

+------------------------+---------------------------------------------------------+
| XML-RPC类型            | Python 类型                                             |
|========================|=========================================================|
| "boolean"              | "bool"                                                  |
+------------------------+---------------------------------------------------------+
| "int", "i1", "i2",     | "int" 的范围从 -2147483648 到 2147483647。值将获得      |
| "i4", "i8" 或者        | "<int>" 标志。                                          |
| "biginteger"           |                                                         |
+------------------------+---------------------------------------------------------+
| "double" 或 "float"    | "float"。值将获得 "<double>" 标志。                     |
+------------------------+---------------------------------------------------------+
| "string"               | "str"                                                   |
+------------------------+---------------------------------------------------------+
| "array"                | "list" 或 "tuple" 包含整合元素。数组以 "lists" 形式返回 |
|                        | 。                                                      |
+------------------------+---------------------------------------------------------+
| "struct"               | "dict"。 键必须为字符串，值可以为任何适用的类型。 可以  |
|                        | 传入用户自定义类 的对象；只有其 "__dict__" 属性会被传输 |
|                        | 。                                                      |
+------------------------+---------------------------------------------------------+
| "dateTime.iso8601"     | "DateTime" 或 "datetime.datetime"。返回的类型取决于     |
|                        | *use_builtin_types* 和 *use_datetime* 标志的值。        |
+------------------------+---------------------------------------------------------+
| "base64"               | "Binary", "bytes" 或 "bytearray"。返回的类型取决于      |
|                        | *use_builtin_types* 标志的值。                          |
+------------------------+---------------------------------------------------------+
| "nil"                  | "None" 常量。仅当 *allow_none* 为true时才允许传递。     |
+------------------------+---------------------------------------------------------+
| "bigdecimal"           | "decimal.Decimal".  仅返回类型。                        |
+------------------------+---------------------------------------------------------+

这是This is the full set of data types supported by XML-RPC 所支持数据
类型的完整集合。 方法调用也可能引发一个特殊的 "Fault" 实例，用来提示
XML-RPC 服务器错误，或是用 "ProtocolError" 来提示 HTTP/HTTPS 传输层中
的错误。 "Fault" 和 "ProtocolError" 都派生自名为 "Error" 的基类。 请注
意 xmlrpc client 模块目前不可 marshal 内置类型的子类的实例。当传入字符
串时，XML 中的特殊字符如 "<", ">" 和 "&" 将被自动转义。 但是，调用方有
责任确保字符串中没有 XML 中不允许的字符，例如 ASCII 值在 0 和 31 之间
的控制字符（当然，制表、换行和回车除外）；不这样做将导致 XML-RPC 请求
的 XML 格式不正确。 如果你必须通过 XML-RPC 传入任意字节数据，请使用
"bytes" 或 "bytearray" 类或者下文描述的 "Binary" 包装器类。"Server" 被
保留作为 "ServerProxy" 的别名用于向下兼容。 新的代码应当使用
"ServerProxy"。

在 3.5 版更改: 增加了 *context* 参数。

在 3.6 版更改: 增加了对带有前缀的类型标签的支持 (例如 "ex:nil")。 增加
了对反 marshall 被 Apache XML-RPC 实现用于表示数值的附加类型的支持:
"i1", "i2", "i8", "biginteger", "float" 和 "bigdecimal"。 请参阅
https://ws.apache.org/xmlrpc/types.html 了解详情。

参见:

  XML-RPC HOWTO
     以多种语言对 XML-RPC 操作和客户端软件进行了很好的说明。 包含 XML-
     RPC 客户端开发者所需知道的几乎任何事情。

  XML-RPC Introspection
     描述了用于内省的 XML-RPC 协议扩展。

  XML-RPC Specification
     官方规范说明。


ServerProxy 对象
================

"ServerProxy" 实例有一个方法与 XML-RPC 服务器所接受的每个远程过程调用
相对应。 调用该方法会执行一个 RPC，通过名称和参数签名来调度（例如同一
个方法名可通过多个参数签名来重载）。 RPC 结束时返回一个值，它可以是适
用类型的返回数据或是表示错误的 "Fault" 或 "ProtocolError" 对象。

支持 XML 内省 API 的服务器还支持一些以保留的 "system" 属性分组的通用方
法:

ServerProxy.system.listMethods()

   此方法返回一个字符串列表，每个字符串都各自对应 XML-RPC 服务器所支持
   的（非系统）方法。

ServerProxy.system.methodSignature(name)

   此方法接受一个形参，即某个由 XML-RPC 服务器所实现的方法名称。 它返
   回一个由此方法可能的签名组成的数组。 一个签名就是一个类型数组。 这
   些类型中的第一个是方法的的返回类型，其余的均为形参。

   由于允许多个签名（即重载），此方法是返回一个签名列表而非一个单例。

   签名本身被限制为一个方法所期望的最高层级形参。 举例来说如果一个方法
   期望有一个结构体数组作为形参，并返回一个字符串，则其签名就是
   "string, array"。 如果它期望有三个整数并返回一个字符串，则其签名是
   "string, int, int, int"。

   如果方法没有定义任何签名，则将返回一个非数组值。 在 Python 中这意味
   着返回值的类型为列表以外的类型。

ServerProxy.system.methodHelp(name)

   此方法接受一个形参，即 XML-RPC 服务器所实现的某个方法的名称。 它返
   回描述相应方法用法的文档字符串。 如果没有可用的文档字符串，则返回空
   字符串。 文档字符串可以包含 HTML 标记。

在 3.5 版更改: "ServerProxy" 的实例支持 *context manager* 协议用于关闭
下层传输。

以下是一个可运行的示例。 服务器端代码:

   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

   该类的初始化可以使用距离 Unix 纪元的秒数、时间元组、ISO 8601 时间/
   日期字符串或 "datetime.datetime" 实例。 它具有下列方法，主要是为
   marshall 和反 marshall 代码的内部使用提供支持:

   decode(string)

      接受一个字符串作为实例的新时间值。

   encode(out)

      将此 "DateTime" 条目的 XML-RPC 编码格式写入到 *out* 流对象。

   它还通过富比较和 "__repr__()" 方法来支持某些 Python 内置运算符。

以下是一个可运行的示例。 服务器端代码:

   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" 对象具有下列方法，支持这些方法主要是供 marshall 和反
   marshall 代码在内部使用:

   decode(bytes)

      接受一个 base64 "bytes" 对象并将其解码为实例的新数据。

   encode(out)

      将此二进制条目的 XML-RPC base 64 编码格式写入到 *out* 流对象。

      被编码数据将依据 **RFC 2045 第 6.8 节** 每 76 个字符换行一次，这
      是撰写 XML-RPC 规范说明时 base64 规范的事实标准。

   它还通过 "__eq__()" 和 "__ne__()" 方法来支持某些 Python 内置运算符
   。

该二进制对象的示例用法。 我们将通过 XMLRPC 来传输一张图片:

   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 标签的内容。 Fault 对象具有下列属
   性:

   faultCode

      一个指明 fault 类型的整数。

   faultString

      一个包含与 fault 相关联的诊断消息的字符串。

在接下来的示例中我们将通过返回一个复数类型的值来故意引发一个 "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 对象
==============

"MultiCall" 对象提供了一种将对远程服务器的多个调用封装为一个单独请求的
方式 [1]。

class xmlrpc.client.MultiCall(server)

   创建一个用于盒式方法调用的对象。 *server* 是调用的最终目标。 可以对
   结果对象发起调用，但它们将立即返回 "None"，并只在 "MultiCall" 对象
   中存储调用名称和形参。 调用该对象本身会导致所有已存储的调用作为一个
   单独的 "system.multicall" 请求被发送。 此调用的结果是一个
   *generator*；迭代这个生成器会产生各个结果。

以下是该类的用法示例。 服务器端代码:

   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* 为真
   值时则转换为一个请求。 *params* 可以是一个参数元组或是一个 "Fault"
   异常类的实例。 如果 *methodresponse* 为真值，只有单独的值可以被返回
   ，这意味着 *params* 的长度必须为 1。 如果提供了 *encoding*，则在生
   成的 XML 会使用该编码格式；默认的编码格式为 UTF-8。 Python 的
   "None" 值不可在标准 XML-RPC 中使用；要通过扩展来允许使用它，请为
   *allow_none* 提供一个真值。

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

   将一个 XML-RPC 请求或响应转换为 Python 对象 "(params, methodname)"
   。 *params* 是一个参数元组；*methodname* 是一个字符串，或者如果数据
   包没有提供方法名则为 "None"。 如果 XML-RPC 数据包是代表一个故障条件
   ，则此函数将引发一个 "Fault" 异常。 *use_builtin_types* 旗标可被用
   于将日期/时间值表示为 "datetime.datetime" 对象并将二进制数据表示为
   "bytes" 对象；此旗标默认为假值。

   已过时的 *use_datetime* 旗标与 *use_builtin_types* 类似但只作用于日
   期/时间值。

   在 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)

要通过 HTTP 代理访问一个 XML-RPC 服务器，你必须自行定义一个传输。 下面
的例子演示了具体做法:

   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。
