Contents
XML-RPC 简介
全称 XML Remote Procedure Call ,即 XML 远程方法调用。它使用 http/https 作为传输协议,XML 作为传送信息的编码格式。
下面每个格式中的 XML 不暂时不作具体解释了,基本一看就明了,而且解析该内容的具体工作是由 xmlrpclib 模块完成的,到讲解 xmlrpclib 模块时再作具体的分析。
如果想要深入了解 XML-RPC,请参见 XML-RPC 主页 和 WiKi XML-RPC
请求消息
一个 XML-RPC 请求必须为 POST 请求,它的消息体为 XML 格式。
请求首行中的 URL 一般来说为 /RPC2 ,但不作强制要求,可为空,也可为 / 。
Tip
但本模块只支持 ('/', '/RPC2')
User-Agent 和 Host 必须要指定。
Content-Type 必须指定为 text/xml 。
Content-Length 必须指定,并且一定要等于消息体的长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | POST /RPC2 HTTP/1.0
User-Agent: Frontier/5.1.2 (WinNT)
Host: betty.userland.com
Content-Type: text/xml
Content-length: 181
<?xml version="1.0"?>
<methodCall>
<methodName>examples.getStateName</methodName>
<params>
<param>
<value><i4>41</i4></value>
</param>
</params>
</methodCall>
|
响应消息
除非有底层错误,否则响应首行永远为 200 OK 。
Content-Type 必须指定为 text/xml 。
Content-Length 必须指定,并且一定要等于消息体的长度。
一个 XML-RPC 响应的消息体也为 XML 格式。
消息体中只能有一个 <methodResponse></methodResponse> 。
如果为正确响应,则里面为 <params></params>
如果为错误响应,则里面为 <fault></fault>
正确响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
HTTP/1.1 200 OK Connection: close Content-Length: 158 Content-Type: text/xml Date: Fri, 17 Jul 1998 19:55:08 GMT Server: UserLand Frontier/5.1.2-WinNT <?xml version="1.0"?> <methodResponse> <params> <param> <value><string>South Dakota</string></value> </param> </params> </methodResponse>
错误响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
HTTP/1.1 200 OK Connection: close Content-Length: 426 Content-Type: text/xml Date: Fri, 17 Jul 1998 19:55:02 GMT Server: UserLand Frontier/5.1.2-WinNT <?xml version="1.0"?> <methodResponse> <fault> <value> <struct> <member> <name>faultCode</name> <value><int>4</int></value> </member> <member> <name>faultString</name> <value><string>Too many parameters.</string></value> </member> </struct> </value> </fault> </methodResponse>
简介
本模块提供的 XML-RPC Server 基本有以下几种使用方法。
使用函数来注册 RPC 方法。
1 2 3 4
server = SimpleXMLRPCServer(("localhost", 8000)) server.register_function(pow) server.register_function(lambda x,y: x+y, 'add') server.serve_forever()
使用一个实例来注册 RPC 方法,该实例的所有可调用属性(方法)都会被注册为 RPC 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
class MyFuncs: def __init__(self): import string self.string = string def _listMethods(self): return list_public_methods(self) + \ ['string.' + method for method in list_public_methods(self.string)] def pow(self, x, y): return pow(x, y) def add(self, x, y) : return x + y server = SimpleXMLRPCServer(("localhost", 8000)) server.register_introspection_functions() server.register_instance(MyFuncs()) server.serve_forever()
使用一个拥有定制化分发方法的实例来注册 RPC 方法,由于该实例自己实现了 RPC 方法分发机制,所以 RPC 方法的注册由该实例来决定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
class Math: def _listMethods(self): return ['add', 'pow'] def _methodHelp(self, method): if method == 'add': return "add(2,3) => 5" elif method == 'pow': return "pow(x, y[, z]) => number" else: return "" def _dispatch(self, method, params): if method == 'pow': return pow(*params) elif method == 'add': return params[0] + params[1] else: raise 'bad method' server = SimpleXMLRPCServer(("localhost", 8000)) server.register_introspection_functions() server.register_instance(Math()) server.serve_forever()
继承 SimpleXMLRPCServer 来实现自己的 XML-RPC Server ,需要重载 _dispatch 方法来实现自己 RPC 方法分发机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
class MathServer(SimpleXMLRPCServer): def _dispatch(self, method, params): try: # We are forcing the 'export_' prefix on methods that are # callable through XML-RPC to prevent potential security # problems func = getattr(self, 'export_' + method) except AttributeError: raise Exception('method "%s" is not supported' % method) else: return func(*params) def export_add(self, x, y): return x + y server = MathServer(("localhost", 8000)) server.serve_forever()
使用 CGI 脚本来提供 RPC 方法的注册与分发功能 :
1 2 3
server = CGIXMLRPCRequestHandler() server.register_function(pow) server.handle_request()
辅助函数
resolve_dotted_attribute
获取某对象的属性。比如:
resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
if allow_dotted_names:
attrs = attr.split('.')
else:
attrs = [attr]
for i in attrs:
if i.startswith('_'):
raise AttributeError(
'attempt to access private attribute "%s"' % i
)
else:
obj = getattr(obj,i)
return obj
|
list_public_methods
列出某个对象所有 公共的 且 可调用的 属性(方法)。
1 2 3 4 | def list_public_methods(obj):
return [member for member in dir(obj)
if not member.startswith('_') and
hasattr(getattr(obj, member), '__call__')]
|
remove_duplicates
去除列表中的重复元素。比如:
[2,2,2,1,3,3] => [3,1,2]
1 2 3 4 5 6 | def remove_duplicates(lst):
u = {}
for x in lst:
u[x] = 1
return u.keys()
|
代码中利用了字典中的 Key 的唯一性的特点来完成重复元素的过滤。
在支持 set (集合)数据结构的 Python 版本中,可以这样写。
1 2 | def remove_duplicates(lst):
return list(set(lst))
|
SimpleXMLRPCDispatcher
SimpleXMLRPCDispatcher 以 MixIn 的形式来提供 RPC 方法的注册与分发功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | class SimpleXMLRPCDispatcher:
def __init__(self, allow_none=False, encoding=None):
self.funcs = {} # 经过注册的 RPC 方法映射关系:{ 方法名 : 方法对象 }
self.instance = None # 经过注册的实例(其方法都会被当作为 RPC 方法)
self.allow_none = allow_none
self.encoding = encoding
def register_instance(self, instance, allow_dotted_names=False):
""" 注册一个实例,该实例的所有方法都会被当作 RPC 方法
.. warning::
使用 `allow_dotted_names` 参数可能会导致安全问题,
调用者可以访问到实例所在模块的全局变量,甚至可以执行任意代码。
"""
self.instance = instance
self.allow_dotted_names = allow_dotted_names
def register_function(self, function, name = None):
""" 将一个函数注册为 RPC 方法 """
if name is None:
name = function.__name__
self.funcs[name] = function
def register_introspection_functions(self):
""" 注册 RPC 内省方法
1. system.listMethods
2. system.methodHelp
3. system.methodSignature
"""
self.funcs.update({'system.listMethods' : self.system_listMethods,
'system.methodSignature' : self.system_methodSignature,
'system.methodHelp' : self.system_methodHelp})
def register_multicall_functions(self):
""" 注册 RPC 多调方法
使用该方法,可以一次提交多个需要执行的 RPC 方法,然后服务端一次将所有结果返回
这样主要为了减少请求响应次数
"""
self.funcs.update({'system.multicall' : self.system_multicall})
def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
""" 反序列化客户端的请求消息体,获取其中的方法名及参数,并调用注册的对应的 PRC 方法 """
try:
# 具体的反序列化操作是由 `xmlrpclib` 模块来完成的,其实就是解析请求消息中的 XML,
# 获取其中的方法名及参数
params, method = xmlrpclib.loads(data)
if dispatch_method is not None:
response = dispatch_method(method, params)
else:
response = self._dispatch(method, params)
response = (response,)
response = xmlrpclib.dumps(response, methodresponse=1,
allow_none=self.allow_none, encoding=self.encoding)
except Fault, fault:
response = xmlrpclib.dumps(fault, allow_none=self.allow_none,
encoding=self.encoding)
except:
# report exception back to server
exc_type, exc_value, exc_tb = sys.exc_info()
response = xmlrpclib.dumps(
xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)),
encoding=self.encoding, allow_none=self.allow_none,
)
return response
def system_listMethods(self):
""" 列出服务端提供的所有 RPC 方法 """
methods = self.funcs.keys()
if self.instance is not None:
if hasattr(self.instance, '_listMethods'):
methods = remove_duplicates(
methods + self.instance._listMethods()
)
elif not hasattr(self.instance, '_dispatch'):
methods = remove_duplicates(
methods + list_public_methods(self.instance)
)
methods.sort()
return methods
def system_methodSignature(self, method_name):
""" 返回函数名对应函数的签名(参数类型列表)
但由于 Python 函数的参数不需要指定其类型,所以函数签名功能也无法实现
"""
return 'signatures not supported'
def system_methodHelp(self, method_name):
""" 返回 RPC 方法的帮助文档 (docstring)"""
method = None
if method_name in self.funcs:
method = self.funcs[method_name]
elif self.instance is not None:
if hasattr(self.instance, '_methodHelp'):
return self.instance._methodHelp(method_name)
elif not hasattr(self.instance, '_dispatch'):
try:
method = resolve_dotted_attribute(
self.instance,
method_name,
self.allow_dotted_names
)
except AttributeError:
pass
if method is None:
return ""
else:
import pydoc
return pydoc.getdoc(method)
def system_multicall(self, call_list):
"""system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => [[4], ...]
"""
results = []
for call in call_list:
method_name = call['methodName']
params = call['params']
try:
results.append([self._dispatch(method_name, params)])
except Fault, fault:
results.append(
{'faultCode' : fault.faultCode,
'faultString' : fault.faultString}
)
except:
exc_type, exc_value, exc_tb = sys.exc_info()
results.append(
{'faultCode' : 1,
'faultString' : "%s:%s" % (exc_type, exc_value)}
)
return results
def _dispatch(self, method, params):
""" 分发 RPC 方法,本质就是根据传入的方法名及参数来调用内部已注册的 RPC 方法
.. note::
如果同时注册了函数和实例,那么函数的优先级比实例中的方法高
"""
func = None
try:
func = self.funcs[method]
except KeyError:
if self.instance is not None:
if hasattr(self.instance, '_dispatch'):
return self.instance._dispatch(method, params)
else:
try:
func = resolve_dotted_attribute(
self.instance,
method,
self.allow_dotted_names
)
except AttributeError:
pass
if func is not None:
return func(*params)
else:
raise Exception('method "%s" is not supported' % method)
|
register_introspection_functions
XML-RPC 的内省协议并不是 XML-RPC 标准的一部分,不过大多数 XML-RPC 的客户端及服务端都实现该内省协议。
详情可参见 XML-RPC Introspection
allow_dotted_names
在上面的注释中有提到,如果打开该参数,会允许调用者访问实例所在模块的所有变量,甚至执行任意代码。
那么调用者是如何做到的呢?
1 2 3 4 5 6 | class MyFuncs:
def div(self, x, y):
return x // y
server = SimpleXMLRPCServer(("localhost", 8000))
server.register_instance(MyFuncs())
|
以上面的代码为例:
1 | >>> MyFuncs.div.im_func.func_globals
|
运行上面的代码(输出太多,我这里就不贴出来了),你会看到 MyFuncs 所在模块的所有全局变量都能够访问。
如果你在模块导入了 os 、 command 、 subprocess 等模块,那么调用者就能调用任意命令了。
想深入了解,参见 Injecting arbitrary code into a Python SimpleXMLRPC Server
如果非要用 allow_dotted_names ,也是有解决办法的。
在 `MyFuncs` 中定义 `_dispatch` 方法,实现自己的 RPC 方法分发。
SimpleXMLRPCRequestHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
# RPC 路径上面在简介 XML-RPC 时就提到过
rpc_paths = ('/', '/RPC2')
# 响应数据的最大传输单元,如果原始响应数据超过该值,则尝试用 GZIP 进行压缩
encode_threshold = 1400 #a common MTU
# Nagle 算法会让数据发送延时,对于 RPC 这种实时应用,需要禁用它。
# 但是一旦禁用 Nagle 算法,任何大小的数据包就会立即发送,
# 这样就需要上层来控制每次尽量发送尽可能多的数据包
# 其实就是关闭底层的 Nagle 算法,然后上层自己完成 Nagle 算法的类似功能。
wbufsize = -1
disable_nagle_algorithm = True
# a re to match a gzip Accept-Encoding
aepattern = re.compile(r"""
\s* ([^\s;]+) \s* #content-coding
(;\s* q \s*=\s* ([0-9\.]+))? #q
""", re.VERBOSE | re.IGNORECASE)
def accept_encodings(self):
r = {}
ae = self.headers.get("Accept-Encoding", "")
for e in ae.split(","):
match = self.aepattern.match(e)
if match:
v = match.group(3)
v = float(v) if v else 1.0
r[match.group(1)] = v
return r
def is_rpc_path_valid(self):
""" 验证 RPC 路径 """
if self.rpc_paths:
return self.path in self.rpc_paths
else:
return True
def do_POST(self):
""" 所有的 XML-PRC 都是 POST 请求 """
if not self.is_rpc_path_valid():
self.report_404()
return
try:
# 这里为何要多次读取,请参见下面的 `Issue792570`
max_chunk_size = 10*1024*1024
size_remaining = int(self.headers["content-length"])
L = []
while size_remaining:
chunk_size = min(size_remaining, max_chunk_size)
chunk = self.rfile.read(chunk_size)
if not chunk:
break
L.append(chunk)
size_remaining -= len(L[-1])
data = ''.join(L)
data = self.decode_request_content(data)
if data is None:
return #response has been sent
# getattr(self, '_dispatch', None)
# 是为了兼容以前的版本,以前的版本的分发方法 `_dispatch` 是在本类中的。
response = self.server._marshaled_dispatch(
data, getattr(self, '_dispatch', None), self.path
)
except Exception, e: # This should only happen if the module is buggy
# internal error, report as HTTP server error
self.send_response(500)
# Send information about the exception if requested
if hasattr(self.server, '_send_traceback_header') and \
self.server._send_traceback_header:
self.send_header("X-exception", str(e))
self.send_header("X-traceback", traceback.format_exc())
self.send_header("Content-length", "0")
self.end_headers()
else:
# 如果返回的数据太大,则尝试进行 GZIP 压缩
self.send_response(200)
self.send_header("Content-type", "text/xml")
if self.encode_threshold is not None:
if len(response) > self.encode_threshold:
q = self.accept_encodings().get("gzip", 0)
if q:
try:
response = xmlrpclib.gzip_encode(response)
self.send_header("Content-Encoding", "gzip")
except NotImplementedError:
pass
self.send_header("Content-length", str(len(response)))
self.end_headers()
self.wfile.write(response)
def decode_request_content(self, data):
""" 解码 GZIP 格式的请求消息,具体参见下面的 `HTTP 压缩 ` """
encoding = self.headers.get("content-encoding", "identity").lower()
if encoding == "identity": # 原始数据,未经过压缩
return data
if encoding == "gzip": # 经过 GZIP 格式压缩的数据
try:
return xmlrpclib.gzip_decode(data)
except NotImplementedError:
self.send_response(501, "encoding %r not supported" % encoding)
except ValueError:
self.send_response(400, "error decoding gzip content")
else:
self.send_response(501, "encoding %r not supported" % encoding)
self.send_header("Content-length", "0")
self.end_headers()
def report_404(self):
self.send_response(404)
response = 'No such page'
self.send_header("Content-type", "text/plain")
self.send_header("Content-length", str(len(response)))
self.end_headers()
self.wfile.write(response)
def log_request(self, code='-', size='-'):
if self.server.logRequests:
BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
|
MTU
通信术语: 最大传输单元(Maximum Transmission Unit,MTU)
指一种通信协议的某一层上面一次传输所能通过的最大数据包大小(以字节为单位)
上面的 encode_threshold 设置为 1400 ,算是 TCP 协议中的常用值。
代码中使用 encode_threshold 主要是为了压缩响应数据的长度,尽量使用较少的传输数次数将响应数据发送给客户端。
Issue792570
该 Bug 产生的原因是服务端的 socket 在进行 read 操作时,如果一次性读取大量的数据,有可能会导致读取的数据不完整,导致后面进行 XML 解析时失败。
HTTP 压缩
HTTP 压缩是在 Web 服务器和浏览器间传输压缩文本内容的方法,主要为了更加有效节约带宽流量。当然,它的代价就是增加了服务器的一些 CPU 开销。
其实 HTTP 压缩本质上来说是 HTTP 内容编码的子集。
HTTP 内容编码定义了一些标准的内容编码类型,并允许用扩展编码的形式添加更多的编码。
Content-Encoding 就是用标准化的代号来说明编码时所用到的算法。
HTTP 压缩采用的常用标准化代码如下:
- gzip 消息体内容用 gzip 数据流进行封装
- compress 消息体内容用 compress 数据流进行封装
- deflate 消息体内容是原始格式、没有数据头的 DEFLATE 数据流
- identity 表明没有对消息体内容进行编码,当没有 Content-Encoding 头部时,本情况为默认值
Tip
虽说 HTTP/1.1 版本才正式规定了 HTTP 压缩 ,但是在实际应用中,
服务端一般不根据 HTTP 版本号,而是根据请求消息头部中的 Accept-Encoding 值
来判定客户端支持哪些压缩格式的。
SimpleXMLRPCServer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class SimpleXMLRPCServer(SocketServer.TCPServer,
SimpleXMLRPCDispatcher):
allow_reuse_address = True
_send_traceback_header = False
def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
self.logRequests = logRequests
SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
SocketServer.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
# 参见下面的 `Issue1222790`
if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
flags |= fcntl.FD_CLOEXEC
fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
|
_send_traceback_header
调试参数。如果该参数为 True,XML-RPC 在处理请求消息的解析及 RPC 方法分发时发生异常,响应消息头部就会带上异常栈信息。
Warning
该参数只能用于调试环境,当用于生产环境时,必须关闭。不然有可能暴露内部的代码实现。
Issue1222790
该 Bug 描述的情况是,服务端提供的 RPC 方法中有可能调用(linux: fork+exec)外部程序,那么该外部程序就会继承服务端打开的 socket 句柄(fork 函数的功能),如果此时服务端在该外部程序退出之前就退出了,外部程序就会占用该 socket 句柄(该句柄绑定的 IP:PORT 也就会一直被占用),服务端进程再次拉起时,会尝试绑定原来的 IP:PORT,但由于其被占用,服务端就会报错误 Address already in use ,无法正常工作。
那么对该 socket 句柄设置了 FD_CLOEXEC 标志后,在 fork 出来的子进程里执行 exec 来调用外部程序时,就会自动关闭该 socket 句柄,外部程序就会不占用服务端绑定的 IP:PORT。
MultiPathXMLRPCServer
多路分发 XML-RPC 服务
它和 SimpleXMLRPCServer 的区别在于,它支持注册多个 Dispatcher 实例并同时提供 XML-RPC 服务,多个实例之间互不冲突(绑定的 URL 路径不同,当然不会冲突)。
Dispatcher 一般指的是 SimpleXMLRPCDispatcher 或者是基于 SimpleXMLRPCDispatcher 的类。
Tip
该类在 Python 官方文档里未介绍,网上提及到该类的博文也很少,怪哉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | class MultiPathXMLRPCServer(SimpleXMLRPCServer):
def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests, allow_none,
encoding, bind_and_activate)
self.dispatchers = {} # 该字典维护了多个 Dispatcher 实例
self.allow_none = allow_none
self.encoding = encoding
def add_dispatcher(self, path, dispatcher):
self.dispatchers[path] = dispatcher
return dispatcher
def get_dispatcher(self, path):
return self.dispatchers[path]
def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
try:
response = self.dispatchers[path]._marshaled_dispatch(
data, dispatch_method, path)
except:
# report low level exception back to server
# (each dispatcher should have handled their own
# exceptions)
exc_type, exc_value = sys.exc_info()[:2]
response = xmlrpclib.dumps(
xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)),
encoding=self.encoding, allow_none=self.allow_none)
return response
|
CGIXMLRPCRequestHandler
通过 CGI 的形式来进行提供 XML-RPC 服务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
"""Simple handler for XML-RPC data passed through CGI."""
def __init__(self, allow_none=False, encoding=None):
SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
def handle_xmlrpc(self, request_text):
response = self._marshaled_dispatch(request_text)
print 'Content-Type: text/xml'
print 'Content-Length: %d' % len(response)
print
sys.stdout.write(response)
def handle_get(self):
""" 在博文开关就说明了 XML-RCP 只使用 POST 方法,
所以如果使用 GET 方法调用 CGI 脚本,就返回 400 错误
"""
code = 400
message, explain = \
BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
{
'code' : code,
'message' : message,
'explain' : explain
}
print 'Status: %d %s' % (code, message)
print 'Content-Type: %s' % BaseHTTPServer.DEFAULT_ERROR_CONTENT_TYPE
print 'Content-Length: %d' % len(response)
print
sys.stdout.write(response)
def handle_request(self, request_text = None):
""" 入口方法,
* 默认从标准输入读入请求消息中的 XML 格式数据
* 也支持从函数的参数传入请求消息中的 XML 格式数据
然后对该数据进行解析,取出其中的方法名和参数,并进行 RPC 方法的分发
"""
if request_text is None and \
os.environ.get('REQUEST_METHOD', None) == 'GET':
self.handle_get()
else:
# POST data is normally available through stdin
try:
length = int(os.environ.get('CONTENT_LENGTH', None))
except (TypeError, ValueError):
length = -1
if request_text is None:
request_text = sys.stdin.read(length)
self.handle_xmlrpc(request_text)
|