"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" 现在默认会执行所
有必要的证书和主机名检查。

适用范围: not WASI.

此模块在 WebAssembly 平台上无效或不可用。 请参阅 WebAssembly 平台 了解
详情。

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 标头序列，
   其形式为包含代表标头名称和值的 2 元组序列。 (例如 "[('Header-Name',
   'value')]")。 如果提供了 HTTPS URL，则 *context* 可以为
   "ssl.SSLContext" 并配置底层 HTTPS 连接的 SSL 设置。 已过时的
   *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 方法时作为连接过程
   的一部分发送给远程服务器。 你只需要在远程服务器要求基本身份验证用户
   名和密码时使用此语法。

   返回的实例是一个代理对象，具有可被用来在远程服务器上发起相应 RPC 调
   用的方法。 如果远程服务器支持内省 API，则也可使用该代理对象在远程服
   务器上查询它所支持的方法（服务发现）并获取其他服务器相关的元数据

   适用的类型（即可通过 XML 进行编组），包括如下类型（除了已说明的例外
   ，它们都会被反编组为同样的 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* 为真值时才允许传递。     |
   +------------------------+---------------------------------------------------------+
   | "bigdecimal"           | "decimal.Decimal"。 仅返回类型。                        |
   +------------------------+---------------------------------------------------------+

   这是 XML-RPC 所支持数据类型的完整集合。 方法调用也可能引发一个特殊
   的 "Fault" 实例，用来提示 XML-RPC 服务器错误，或是用
   "ProtocolError" 来提示 HTTP/HTTPS 传输层中的错误。 "Fault" 和
   "ProtocolError" 都派生自名为 "Error" 的基类。 请注意 xmlrpc client
   模块目前不可编组内置类型的子类的实例。

   当传入字符串时，XML 中的特殊字符如 "<", ">" 和 "&" 将被自动转义。
   但是，调用方有责任确保字符串中没有 XML 中不允许的字符，例如 ASCII
   值在 0 和 31 之间的控制字符（当然，制表、换行和回车除外）；不这样做
   将导致 XML-RPC 请求的 XML 格式不正确。 如果你必须通过 XML-RPC 传入
   任意字节数据，请使用 "bytes" 或 "bytearray" 类或者下文描述的
   "Binary" 包装器类。

   "Server" 被保留作为 "ServerProxy" 的别名用于向下兼容。 新的代码应当
   使用 "ServerProxy"。

   在 3.5 版本发生变更: 增加了 *context* 参数。

   在 3.6 版本发生变更: 增加了对带有前缀的类型标签的支持 (例如
   "ex:nil")。 增加了对反编组被 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 对象
================

A "ServerProxy" instance has a method corresponding to each remote
procedure call accepted by the XML-RPC server.  Calling the method
performs an RPC, dispatched by both name and argument signature (e.g.
the same method name can be overloaded with multiple argument
signatures).  The RPC finishes either by returning data in a
conformant type or by raising a "Fault" or "ProtocolError" exception
indicating an error.

支持 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" 实例。 它具有下列方法，主要是为编
   组和反编组代码的内部使用提供支持:

   decode(string)

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

   encode(out)

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

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

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

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

   def today():
       today = dt.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 as dt

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

   today = proxy.today()
   # 将 ISO 8601 字符串转换为日期时间对象
   converted = dt.datetime.strptime(today.value, "%Y%m%dT%H:%M:%S")
   print(f"Today: {converted.strftime('%d.%m.%Y, %H:%M')}")


Binary 对象
===========

class xmlrpc.client.Binary

   该类的初始化可以使用字节数据（可包括 NUL）。 对 "Binary" 对象内容的
   主要访问是由一个属性来提供的:

   data

      被 "Binary" 实例封装的二进制数据。 该数据以 "bytes" 对象的形式提
      供。

   "Binary" 对象具有下列方法，支持这些方法主要是供编组和反编组代码在内
   部使用:

   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

   # 将发生编组错误因为我们将返回一个复数
   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

   # 创建一个 ServerProxy，所用 URI 不与 XMLRPC 请求对应
   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)

   Create an object used to boxcar method calls. *server* is the
   eventual target of the call. Calls can be made to the result
   object, but they will immediately return "None", and only store the
   call name and arguments in the "MultiCall" object. Calling the
   object itself causes all stored calls to be transmitted as a single
   "system.multicall" request. The result of this call is a
   *generator*; iterating over this generator yields the individual
   results.

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

   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

   # 一个带有简单算术函数的简单服务器
   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* 旗标。


客户端用法的示例
================

   # 简单的测试程序（来自 XML-RPC 规范说明）
   from xmlrpc.client import ServerProxy, Error

   # server = ServerProxy("http://localhost:8000") # 本地服务器
   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] 此做法最早出现在 xmlrpc.com 上的一次讨论 中。
