jpayne@68
|
1 #
|
jpayne@68
|
2 # XML-RPC CLIENT LIBRARY
|
jpayne@68
|
3 # $Id$
|
jpayne@68
|
4 #
|
jpayne@68
|
5 # an XML-RPC client interface for Python.
|
jpayne@68
|
6 #
|
jpayne@68
|
7 # the marshalling and response parser code can also be used to
|
jpayne@68
|
8 # implement XML-RPC servers.
|
jpayne@68
|
9 #
|
jpayne@68
|
10 # Notes:
|
jpayne@68
|
11 # this version is designed to work with Python 2.1 or newer.
|
jpayne@68
|
12 #
|
jpayne@68
|
13 # History:
|
jpayne@68
|
14 # 1999-01-14 fl Created
|
jpayne@68
|
15 # 1999-01-15 fl Changed dateTime to use localtime
|
jpayne@68
|
16 # 1999-01-16 fl Added Binary/base64 element, default to RPC2 service
|
jpayne@68
|
17 # 1999-01-19 fl Fixed array data element (from Skip Montanaro)
|
jpayne@68
|
18 # 1999-01-21 fl Fixed dateTime constructor, etc.
|
jpayne@68
|
19 # 1999-02-02 fl Added fault handling, handle empty sequences, etc.
|
jpayne@68
|
20 # 1999-02-10 fl Fixed problem with empty responses (from Skip Montanaro)
|
jpayne@68
|
21 # 1999-06-20 fl Speed improvements, pluggable parsers/transports (0.9.8)
|
jpayne@68
|
22 # 2000-11-28 fl Changed boolean to check the truth value of its argument
|
jpayne@68
|
23 # 2001-02-24 fl Added encoding/Unicode/SafeTransport patches
|
jpayne@68
|
24 # 2001-02-26 fl Added compare support to wrappers (0.9.9/1.0b1)
|
jpayne@68
|
25 # 2001-03-28 fl Make sure response tuple is a singleton
|
jpayne@68
|
26 # 2001-03-29 fl Don't require empty params element (from Nicholas Riley)
|
jpayne@68
|
27 # 2001-06-10 fl Folded in _xmlrpclib accelerator support (1.0b2)
|
jpayne@68
|
28 # 2001-08-20 fl Base xmlrpclib.Error on built-in Exception (from Paul Prescod)
|
jpayne@68
|
29 # 2001-09-03 fl Allow Transport subclass to override getparser
|
jpayne@68
|
30 # 2001-09-10 fl Lazy import of urllib, cgi, xmllib (20x import speedup)
|
jpayne@68
|
31 # 2001-10-01 fl Remove containers from memo cache when done with them
|
jpayne@68
|
32 # 2001-10-01 fl Use faster escape method (80% dumps speedup)
|
jpayne@68
|
33 # 2001-10-02 fl More dumps microtuning
|
jpayne@68
|
34 # 2001-10-04 fl Make sure import expat gets a parser (from Guido van Rossum)
|
jpayne@68
|
35 # 2001-10-10 sm Allow long ints to be passed as ints if they don't overflow
|
jpayne@68
|
36 # 2001-10-17 sm Test for int and long overflow (allows use on 64-bit systems)
|
jpayne@68
|
37 # 2001-11-12 fl Use repr() to marshal doubles (from Paul Felix)
|
jpayne@68
|
38 # 2002-03-17 fl Avoid buffered read when possible (from James Rucker)
|
jpayne@68
|
39 # 2002-04-07 fl Added pythondoc comments
|
jpayne@68
|
40 # 2002-04-16 fl Added __str__ methods to datetime/binary wrappers
|
jpayne@68
|
41 # 2002-05-15 fl Added error constants (from Andrew Kuchling)
|
jpayne@68
|
42 # 2002-06-27 fl Merged with Python CVS version
|
jpayne@68
|
43 # 2002-10-22 fl Added basic authentication (based on code from Phillip Eby)
|
jpayne@68
|
44 # 2003-01-22 sm Add support for the bool type
|
jpayne@68
|
45 # 2003-02-27 gvr Remove apply calls
|
jpayne@68
|
46 # 2003-04-24 sm Use cStringIO if available
|
jpayne@68
|
47 # 2003-04-25 ak Add support for nil
|
jpayne@68
|
48 # 2003-06-15 gn Add support for time.struct_time
|
jpayne@68
|
49 # 2003-07-12 gp Correct marshalling of Faults
|
jpayne@68
|
50 # 2003-10-31 mvl Add multicall support
|
jpayne@68
|
51 # 2004-08-20 mvl Bump minimum supported Python version to 2.1
|
jpayne@68
|
52 # 2014-12-02 ch/doko Add workaround for gzip bomb vulnerability
|
jpayne@68
|
53 #
|
jpayne@68
|
54 # Copyright (c) 1999-2002 by Secret Labs AB.
|
jpayne@68
|
55 # Copyright (c) 1999-2002 by Fredrik Lundh.
|
jpayne@68
|
56 #
|
jpayne@68
|
57 # info@pythonware.com
|
jpayne@68
|
58 # http://www.pythonware.com
|
jpayne@68
|
59 #
|
jpayne@68
|
60 # --------------------------------------------------------------------
|
jpayne@68
|
61 # The XML-RPC client interface is
|
jpayne@68
|
62 #
|
jpayne@68
|
63 # Copyright (c) 1999-2002 by Secret Labs AB
|
jpayne@68
|
64 # Copyright (c) 1999-2002 by Fredrik Lundh
|
jpayne@68
|
65 #
|
jpayne@68
|
66 # By obtaining, using, and/or copying this software and/or its
|
jpayne@68
|
67 # associated documentation, you agree that you have read, understood,
|
jpayne@68
|
68 # and will comply with the following terms and conditions:
|
jpayne@68
|
69 #
|
jpayne@68
|
70 # Permission to use, copy, modify, and distribute this software and
|
jpayne@68
|
71 # its associated documentation for any purpose and without fee is
|
jpayne@68
|
72 # hereby granted, provided that the above copyright notice appears in
|
jpayne@68
|
73 # all copies, and that both that copyright notice and this permission
|
jpayne@68
|
74 # notice appear in supporting documentation, and that the name of
|
jpayne@68
|
75 # Secret Labs AB or the author not be used in advertising or publicity
|
jpayne@68
|
76 # pertaining to distribution of the software without specific, written
|
jpayne@68
|
77 # prior permission.
|
jpayne@68
|
78 #
|
jpayne@68
|
79 # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
|
jpayne@68
|
80 # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
|
jpayne@68
|
81 # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
|
jpayne@68
|
82 # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
|
jpayne@68
|
83 # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
jpayne@68
|
84 # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
|
jpayne@68
|
85 # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
jpayne@68
|
86 # OF THIS SOFTWARE.
|
jpayne@68
|
87 # --------------------------------------------------------------------
|
jpayne@68
|
88
|
jpayne@68
|
89 """
|
jpayne@68
|
90 An XML-RPC client interface for Python.
|
jpayne@68
|
91
|
jpayne@68
|
92 The marshalling and response parser code can also be used to
|
jpayne@68
|
93 implement XML-RPC servers.
|
jpayne@68
|
94
|
jpayne@68
|
95 Exported exceptions:
|
jpayne@68
|
96
|
jpayne@68
|
97 Error Base class for client errors
|
jpayne@68
|
98 ProtocolError Indicates an HTTP protocol error
|
jpayne@68
|
99 ResponseError Indicates a broken response package
|
jpayne@68
|
100 Fault Indicates an XML-RPC fault package
|
jpayne@68
|
101
|
jpayne@68
|
102 Exported classes:
|
jpayne@68
|
103
|
jpayne@68
|
104 ServerProxy Represents a logical connection to an XML-RPC server
|
jpayne@68
|
105
|
jpayne@68
|
106 MultiCall Executor of boxcared xmlrpc requests
|
jpayne@68
|
107 DateTime dateTime wrapper for an ISO 8601 string or time tuple or
|
jpayne@68
|
108 localtime integer value to generate a "dateTime.iso8601"
|
jpayne@68
|
109 XML-RPC value
|
jpayne@68
|
110 Binary binary data wrapper
|
jpayne@68
|
111
|
jpayne@68
|
112 Marshaller Generate an XML-RPC params chunk from a Python data structure
|
jpayne@68
|
113 Unmarshaller Unmarshal an XML-RPC response from incoming XML event message
|
jpayne@68
|
114 Transport Handles an HTTP transaction to an XML-RPC server
|
jpayne@68
|
115 SafeTransport Handles an HTTPS transaction to an XML-RPC server
|
jpayne@68
|
116
|
jpayne@68
|
117 Exported constants:
|
jpayne@68
|
118
|
jpayne@68
|
119 (none)
|
jpayne@68
|
120
|
jpayne@68
|
121 Exported functions:
|
jpayne@68
|
122
|
jpayne@68
|
123 getparser Create instance of the fastest available parser & attach
|
jpayne@68
|
124 to an unmarshalling object
|
jpayne@68
|
125 dumps Convert an argument tuple or a Fault instance to an XML-RPC
|
jpayne@68
|
126 request (or response, if the methodresponse option is used).
|
jpayne@68
|
127 loads Convert an XML-RPC packet to unmarshalled data plus a method
|
jpayne@68
|
128 name (None if not present).
|
jpayne@68
|
129 """
|
jpayne@68
|
130
|
jpayne@68
|
131 import base64
|
jpayne@68
|
132 import sys
|
jpayne@68
|
133 import time
|
jpayne@68
|
134 from datetime import datetime
|
jpayne@68
|
135 from decimal import Decimal
|
jpayne@68
|
136 import http.client
|
jpayne@68
|
137 import urllib.parse
|
jpayne@68
|
138 from xml.parsers import expat
|
jpayne@68
|
139 import errno
|
jpayne@68
|
140 from io import BytesIO
|
jpayne@68
|
141 try:
|
jpayne@68
|
142 import gzip
|
jpayne@68
|
143 except ImportError:
|
jpayne@68
|
144 gzip = None #python can be built without zlib/gzip support
|
jpayne@68
|
145
|
jpayne@68
|
146 # --------------------------------------------------------------------
|
jpayne@68
|
147 # Internal stuff
|
jpayne@68
|
148
|
jpayne@68
|
149 def escape(s):
|
jpayne@68
|
150 s = s.replace("&", "&")
|
jpayne@68
|
151 s = s.replace("<", "<")
|
jpayne@68
|
152 return s.replace(">", ">",)
|
jpayne@68
|
153
|
jpayne@68
|
154 # used in User-Agent header sent
|
jpayne@68
|
155 __version__ = '%d.%d' % sys.version_info[:2]
|
jpayne@68
|
156
|
jpayne@68
|
157 # xmlrpc integer limits
|
jpayne@68
|
158 MAXINT = 2**31-1
|
jpayne@68
|
159 MININT = -2**31
|
jpayne@68
|
160
|
jpayne@68
|
161 # --------------------------------------------------------------------
|
jpayne@68
|
162 # Error constants (from Dan Libby's specification at
|
jpayne@68
|
163 # http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php)
|
jpayne@68
|
164
|
jpayne@68
|
165 # Ranges of errors
|
jpayne@68
|
166 PARSE_ERROR = -32700
|
jpayne@68
|
167 SERVER_ERROR = -32600
|
jpayne@68
|
168 APPLICATION_ERROR = -32500
|
jpayne@68
|
169 SYSTEM_ERROR = -32400
|
jpayne@68
|
170 TRANSPORT_ERROR = -32300
|
jpayne@68
|
171
|
jpayne@68
|
172 # Specific errors
|
jpayne@68
|
173 NOT_WELLFORMED_ERROR = -32700
|
jpayne@68
|
174 UNSUPPORTED_ENCODING = -32701
|
jpayne@68
|
175 INVALID_ENCODING_CHAR = -32702
|
jpayne@68
|
176 INVALID_XMLRPC = -32600
|
jpayne@68
|
177 METHOD_NOT_FOUND = -32601
|
jpayne@68
|
178 INVALID_METHOD_PARAMS = -32602
|
jpayne@68
|
179 INTERNAL_ERROR = -32603
|
jpayne@68
|
180
|
jpayne@68
|
181 # --------------------------------------------------------------------
|
jpayne@68
|
182 # Exceptions
|
jpayne@68
|
183
|
jpayne@68
|
184 ##
|
jpayne@68
|
185 # Base class for all kinds of client-side errors.
|
jpayne@68
|
186
|
jpayne@68
|
187 class Error(Exception):
|
jpayne@68
|
188 """Base class for client errors."""
|
jpayne@68
|
189 __str__ = object.__str__
|
jpayne@68
|
190
|
jpayne@68
|
191 ##
|
jpayne@68
|
192 # Indicates an HTTP-level protocol error. This is raised by the HTTP
|
jpayne@68
|
193 # transport layer, if the server returns an error code other than 200
|
jpayne@68
|
194 # (OK).
|
jpayne@68
|
195 #
|
jpayne@68
|
196 # @param url The target URL.
|
jpayne@68
|
197 # @param errcode The HTTP error code.
|
jpayne@68
|
198 # @param errmsg The HTTP error message.
|
jpayne@68
|
199 # @param headers The HTTP header dictionary.
|
jpayne@68
|
200
|
jpayne@68
|
201 class ProtocolError(Error):
|
jpayne@68
|
202 """Indicates an HTTP protocol error."""
|
jpayne@68
|
203 def __init__(self, url, errcode, errmsg, headers):
|
jpayne@68
|
204 Error.__init__(self)
|
jpayne@68
|
205 self.url = url
|
jpayne@68
|
206 self.errcode = errcode
|
jpayne@68
|
207 self.errmsg = errmsg
|
jpayne@68
|
208 self.headers = headers
|
jpayne@68
|
209 def __repr__(self):
|
jpayne@68
|
210 return (
|
jpayne@68
|
211 "<%s for %s: %s %s>" %
|
jpayne@68
|
212 (self.__class__.__name__, self.url, self.errcode, self.errmsg)
|
jpayne@68
|
213 )
|
jpayne@68
|
214
|
jpayne@68
|
215 ##
|
jpayne@68
|
216 # Indicates a broken XML-RPC response package. This exception is
|
jpayne@68
|
217 # raised by the unmarshalling layer, if the XML-RPC response is
|
jpayne@68
|
218 # malformed.
|
jpayne@68
|
219
|
jpayne@68
|
220 class ResponseError(Error):
|
jpayne@68
|
221 """Indicates a broken response package."""
|
jpayne@68
|
222 pass
|
jpayne@68
|
223
|
jpayne@68
|
224 ##
|
jpayne@68
|
225 # Indicates an XML-RPC fault response package. This exception is
|
jpayne@68
|
226 # raised by the unmarshalling layer, if the XML-RPC response contains
|
jpayne@68
|
227 # a fault string. This exception can also be used as a class, to
|
jpayne@68
|
228 # generate a fault XML-RPC message.
|
jpayne@68
|
229 #
|
jpayne@68
|
230 # @param faultCode The XML-RPC fault code.
|
jpayne@68
|
231 # @param faultString The XML-RPC fault string.
|
jpayne@68
|
232
|
jpayne@68
|
233 class Fault(Error):
|
jpayne@68
|
234 """Indicates an XML-RPC fault package."""
|
jpayne@68
|
235 def __init__(self, faultCode, faultString, **extra):
|
jpayne@68
|
236 Error.__init__(self)
|
jpayne@68
|
237 self.faultCode = faultCode
|
jpayne@68
|
238 self.faultString = faultString
|
jpayne@68
|
239 def __repr__(self):
|
jpayne@68
|
240 return "<%s %s: %r>" % (self.__class__.__name__,
|
jpayne@68
|
241 self.faultCode, self.faultString)
|
jpayne@68
|
242
|
jpayne@68
|
243 # --------------------------------------------------------------------
|
jpayne@68
|
244 # Special values
|
jpayne@68
|
245
|
jpayne@68
|
246 ##
|
jpayne@68
|
247 # Backwards compatibility
|
jpayne@68
|
248
|
jpayne@68
|
249 boolean = Boolean = bool
|
jpayne@68
|
250
|
jpayne@68
|
251 ##
|
jpayne@68
|
252 # Wrapper for XML-RPC DateTime values. This converts a time value to
|
jpayne@68
|
253 # the format used by XML-RPC.
|
jpayne@68
|
254 # <p>
|
jpayne@68
|
255 # The value can be given as a datetime object, as a string in the
|
jpayne@68
|
256 # format "yyyymmddThh:mm:ss", as a 9-item time tuple (as returned by
|
jpayne@68
|
257 # time.localtime()), or an integer value (as returned by time.time()).
|
jpayne@68
|
258 # The wrapper uses time.localtime() to convert an integer to a time
|
jpayne@68
|
259 # tuple.
|
jpayne@68
|
260 #
|
jpayne@68
|
261 # @param value The time, given as a datetime object, an ISO 8601 string,
|
jpayne@68
|
262 # a time tuple, or an integer time value.
|
jpayne@68
|
263
|
jpayne@68
|
264
|
jpayne@68
|
265 # Issue #13305: different format codes across platforms
|
jpayne@68
|
266 _day0 = datetime(1, 1, 1)
|
jpayne@68
|
267 if _day0.strftime('%Y') == '0001': # Mac OS X
|
jpayne@68
|
268 def _iso8601_format(value):
|
jpayne@68
|
269 return value.strftime("%Y%m%dT%H:%M:%S")
|
jpayne@68
|
270 elif _day0.strftime('%4Y') == '0001': # Linux
|
jpayne@68
|
271 def _iso8601_format(value):
|
jpayne@68
|
272 return value.strftime("%4Y%m%dT%H:%M:%S")
|
jpayne@68
|
273 else:
|
jpayne@68
|
274 def _iso8601_format(value):
|
jpayne@68
|
275 return value.strftime("%Y%m%dT%H:%M:%S").zfill(17)
|
jpayne@68
|
276 del _day0
|
jpayne@68
|
277
|
jpayne@68
|
278
|
jpayne@68
|
279 def _strftime(value):
|
jpayne@68
|
280 if isinstance(value, datetime):
|
jpayne@68
|
281 return _iso8601_format(value)
|
jpayne@68
|
282
|
jpayne@68
|
283 if not isinstance(value, (tuple, time.struct_time)):
|
jpayne@68
|
284 if value == 0:
|
jpayne@68
|
285 value = time.time()
|
jpayne@68
|
286 value = time.localtime(value)
|
jpayne@68
|
287
|
jpayne@68
|
288 return "%04d%02d%02dT%02d:%02d:%02d" % value[:6]
|
jpayne@68
|
289
|
jpayne@68
|
290 class DateTime:
|
jpayne@68
|
291 """DateTime wrapper for an ISO 8601 string or time tuple or
|
jpayne@68
|
292 localtime integer value to generate 'dateTime.iso8601' XML-RPC
|
jpayne@68
|
293 value.
|
jpayne@68
|
294 """
|
jpayne@68
|
295
|
jpayne@68
|
296 def __init__(self, value=0):
|
jpayne@68
|
297 if isinstance(value, str):
|
jpayne@68
|
298 self.value = value
|
jpayne@68
|
299 else:
|
jpayne@68
|
300 self.value = _strftime(value)
|
jpayne@68
|
301
|
jpayne@68
|
302 def make_comparable(self, other):
|
jpayne@68
|
303 if isinstance(other, DateTime):
|
jpayne@68
|
304 s = self.value
|
jpayne@68
|
305 o = other.value
|
jpayne@68
|
306 elif isinstance(other, datetime):
|
jpayne@68
|
307 s = self.value
|
jpayne@68
|
308 o = _iso8601_format(other)
|
jpayne@68
|
309 elif isinstance(other, str):
|
jpayne@68
|
310 s = self.value
|
jpayne@68
|
311 o = other
|
jpayne@68
|
312 elif hasattr(other, "timetuple"):
|
jpayne@68
|
313 s = self.timetuple()
|
jpayne@68
|
314 o = other.timetuple()
|
jpayne@68
|
315 else:
|
jpayne@68
|
316 otype = (hasattr(other, "__class__")
|
jpayne@68
|
317 and other.__class__.__name__
|
jpayne@68
|
318 or type(other))
|
jpayne@68
|
319 raise TypeError("Can't compare %s and %s" %
|
jpayne@68
|
320 (self.__class__.__name__, otype))
|
jpayne@68
|
321 return s, o
|
jpayne@68
|
322
|
jpayne@68
|
323 def __lt__(self, other):
|
jpayne@68
|
324 s, o = self.make_comparable(other)
|
jpayne@68
|
325 return s < o
|
jpayne@68
|
326
|
jpayne@68
|
327 def __le__(self, other):
|
jpayne@68
|
328 s, o = self.make_comparable(other)
|
jpayne@68
|
329 return s <= o
|
jpayne@68
|
330
|
jpayne@68
|
331 def __gt__(self, other):
|
jpayne@68
|
332 s, o = self.make_comparable(other)
|
jpayne@68
|
333 return s > o
|
jpayne@68
|
334
|
jpayne@68
|
335 def __ge__(self, other):
|
jpayne@68
|
336 s, o = self.make_comparable(other)
|
jpayne@68
|
337 return s >= o
|
jpayne@68
|
338
|
jpayne@68
|
339 def __eq__(self, other):
|
jpayne@68
|
340 s, o = self.make_comparable(other)
|
jpayne@68
|
341 return s == o
|
jpayne@68
|
342
|
jpayne@68
|
343 def timetuple(self):
|
jpayne@68
|
344 return time.strptime(self.value, "%Y%m%dT%H:%M:%S")
|
jpayne@68
|
345
|
jpayne@68
|
346 ##
|
jpayne@68
|
347 # Get date/time value.
|
jpayne@68
|
348 #
|
jpayne@68
|
349 # @return Date/time value, as an ISO 8601 string.
|
jpayne@68
|
350
|
jpayne@68
|
351 def __str__(self):
|
jpayne@68
|
352 return self.value
|
jpayne@68
|
353
|
jpayne@68
|
354 def __repr__(self):
|
jpayne@68
|
355 return "<%s %r at %#x>" % (self.__class__.__name__, self.value, id(self))
|
jpayne@68
|
356
|
jpayne@68
|
357 def decode(self, data):
|
jpayne@68
|
358 self.value = str(data).strip()
|
jpayne@68
|
359
|
jpayne@68
|
360 def encode(self, out):
|
jpayne@68
|
361 out.write("<value><dateTime.iso8601>")
|
jpayne@68
|
362 out.write(self.value)
|
jpayne@68
|
363 out.write("</dateTime.iso8601></value>\n")
|
jpayne@68
|
364
|
jpayne@68
|
365 def _datetime(data):
|
jpayne@68
|
366 # decode xml element contents into a DateTime structure.
|
jpayne@68
|
367 value = DateTime()
|
jpayne@68
|
368 value.decode(data)
|
jpayne@68
|
369 return value
|
jpayne@68
|
370
|
jpayne@68
|
371 def _datetime_type(data):
|
jpayne@68
|
372 return datetime.strptime(data, "%Y%m%dT%H:%M:%S")
|
jpayne@68
|
373
|
jpayne@68
|
374 ##
|
jpayne@68
|
375 # Wrapper for binary data. This can be used to transport any kind
|
jpayne@68
|
376 # of binary data over XML-RPC, using BASE64 encoding.
|
jpayne@68
|
377 #
|
jpayne@68
|
378 # @param data An 8-bit string containing arbitrary data.
|
jpayne@68
|
379
|
jpayne@68
|
380 class Binary:
|
jpayne@68
|
381 """Wrapper for binary data."""
|
jpayne@68
|
382
|
jpayne@68
|
383 def __init__(self, data=None):
|
jpayne@68
|
384 if data is None:
|
jpayne@68
|
385 data = b""
|
jpayne@68
|
386 else:
|
jpayne@68
|
387 if not isinstance(data, (bytes, bytearray)):
|
jpayne@68
|
388 raise TypeError("expected bytes or bytearray, not %s" %
|
jpayne@68
|
389 data.__class__.__name__)
|
jpayne@68
|
390 data = bytes(data) # Make a copy of the bytes!
|
jpayne@68
|
391 self.data = data
|
jpayne@68
|
392
|
jpayne@68
|
393 ##
|
jpayne@68
|
394 # Get buffer contents.
|
jpayne@68
|
395 #
|
jpayne@68
|
396 # @return Buffer contents, as an 8-bit string.
|
jpayne@68
|
397
|
jpayne@68
|
398 def __str__(self):
|
jpayne@68
|
399 return str(self.data, "latin-1") # XXX encoding?!
|
jpayne@68
|
400
|
jpayne@68
|
401 def __eq__(self, other):
|
jpayne@68
|
402 if isinstance(other, Binary):
|
jpayne@68
|
403 other = other.data
|
jpayne@68
|
404 return self.data == other
|
jpayne@68
|
405
|
jpayne@68
|
406 def decode(self, data):
|
jpayne@68
|
407 self.data = base64.decodebytes(data)
|
jpayne@68
|
408
|
jpayne@68
|
409 def encode(self, out):
|
jpayne@68
|
410 out.write("<value><base64>\n")
|
jpayne@68
|
411 encoded = base64.encodebytes(self.data)
|
jpayne@68
|
412 out.write(encoded.decode('ascii'))
|
jpayne@68
|
413 out.write("</base64></value>\n")
|
jpayne@68
|
414
|
jpayne@68
|
415 def _binary(data):
|
jpayne@68
|
416 # decode xml element contents into a Binary structure
|
jpayne@68
|
417 value = Binary()
|
jpayne@68
|
418 value.decode(data)
|
jpayne@68
|
419 return value
|
jpayne@68
|
420
|
jpayne@68
|
421 WRAPPERS = (DateTime, Binary)
|
jpayne@68
|
422
|
jpayne@68
|
423 # --------------------------------------------------------------------
|
jpayne@68
|
424 # XML parsers
|
jpayne@68
|
425
|
jpayne@68
|
426 class ExpatParser:
|
jpayne@68
|
427 # fast expat parser for Python 2.0 and later.
|
jpayne@68
|
428 def __init__(self, target):
|
jpayne@68
|
429 self._parser = parser = expat.ParserCreate(None, None)
|
jpayne@68
|
430 self._target = target
|
jpayne@68
|
431 parser.StartElementHandler = target.start
|
jpayne@68
|
432 parser.EndElementHandler = target.end
|
jpayne@68
|
433 parser.CharacterDataHandler = target.data
|
jpayne@68
|
434 encoding = None
|
jpayne@68
|
435 target.xml(encoding, None)
|
jpayne@68
|
436
|
jpayne@68
|
437 def feed(self, data):
|
jpayne@68
|
438 self._parser.Parse(data, 0)
|
jpayne@68
|
439
|
jpayne@68
|
440 def close(self):
|
jpayne@68
|
441 try:
|
jpayne@68
|
442 parser = self._parser
|
jpayne@68
|
443 except AttributeError:
|
jpayne@68
|
444 pass
|
jpayne@68
|
445 else:
|
jpayne@68
|
446 del self._target, self._parser # get rid of circular references
|
jpayne@68
|
447 parser.Parse(b"", True) # end of data
|
jpayne@68
|
448
|
jpayne@68
|
449 # --------------------------------------------------------------------
|
jpayne@68
|
450 # XML-RPC marshalling and unmarshalling code
|
jpayne@68
|
451
|
jpayne@68
|
452 ##
|
jpayne@68
|
453 # XML-RPC marshaller.
|
jpayne@68
|
454 #
|
jpayne@68
|
455 # @param encoding Default encoding for 8-bit strings. The default
|
jpayne@68
|
456 # value is None (interpreted as UTF-8).
|
jpayne@68
|
457 # @see dumps
|
jpayne@68
|
458
|
jpayne@68
|
459 class Marshaller:
|
jpayne@68
|
460 """Generate an XML-RPC params chunk from a Python data structure.
|
jpayne@68
|
461
|
jpayne@68
|
462 Create a Marshaller instance for each set of parameters, and use
|
jpayne@68
|
463 the "dumps" method to convert your data (represented as a tuple)
|
jpayne@68
|
464 to an XML-RPC params chunk. To write a fault response, pass a
|
jpayne@68
|
465 Fault instance instead. You may prefer to use the "dumps" module
|
jpayne@68
|
466 function for this purpose.
|
jpayne@68
|
467 """
|
jpayne@68
|
468
|
jpayne@68
|
469 # by the way, if you don't understand what's going on in here,
|
jpayne@68
|
470 # that's perfectly ok.
|
jpayne@68
|
471
|
jpayne@68
|
472 def __init__(self, encoding=None, allow_none=False):
|
jpayne@68
|
473 self.memo = {}
|
jpayne@68
|
474 self.data = None
|
jpayne@68
|
475 self.encoding = encoding
|
jpayne@68
|
476 self.allow_none = allow_none
|
jpayne@68
|
477
|
jpayne@68
|
478 dispatch = {}
|
jpayne@68
|
479
|
jpayne@68
|
480 def dumps(self, values):
|
jpayne@68
|
481 out = []
|
jpayne@68
|
482 write = out.append
|
jpayne@68
|
483 dump = self.__dump
|
jpayne@68
|
484 if isinstance(values, Fault):
|
jpayne@68
|
485 # fault instance
|
jpayne@68
|
486 write("<fault>\n")
|
jpayne@68
|
487 dump({'faultCode': values.faultCode,
|
jpayne@68
|
488 'faultString': values.faultString},
|
jpayne@68
|
489 write)
|
jpayne@68
|
490 write("</fault>\n")
|
jpayne@68
|
491 else:
|
jpayne@68
|
492 # parameter block
|
jpayne@68
|
493 # FIXME: the xml-rpc specification allows us to leave out
|
jpayne@68
|
494 # the entire <params> block if there are no parameters.
|
jpayne@68
|
495 # however, changing this may break older code (including
|
jpayne@68
|
496 # old versions of xmlrpclib.py), so this is better left as
|
jpayne@68
|
497 # is for now. See @XMLRPC3 for more information. /F
|
jpayne@68
|
498 write("<params>\n")
|
jpayne@68
|
499 for v in values:
|
jpayne@68
|
500 write("<param>\n")
|
jpayne@68
|
501 dump(v, write)
|
jpayne@68
|
502 write("</param>\n")
|
jpayne@68
|
503 write("</params>\n")
|
jpayne@68
|
504 result = "".join(out)
|
jpayne@68
|
505 return result
|
jpayne@68
|
506
|
jpayne@68
|
507 def __dump(self, value, write):
|
jpayne@68
|
508 try:
|
jpayne@68
|
509 f = self.dispatch[type(value)]
|
jpayne@68
|
510 except KeyError:
|
jpayne@68
|
511 # check if this object can be marshalled as a structure
|
jpayne@68
|
512 if not hasattr(value, '__dict__'):
|
jpayne@68
|
513 raise TypeError("cannot marshal %s objects" % type(value))
|
jpayne@68
|
514 # check if this class is a sub-class of a basic type,
|
jpayne@68
|
515 # because we don't know how to marshal these types
|
jpayne@68
|
516 # (e.g. a string sub-class)
|
jpayne@68
|
517 for type_ in type(value).__mro__:
|
jpayne@68
|
518 if type_ in self.dispatch.keys():
|
jpayne@68
|
519 raise TypeError("cannot marshal %s objects" % type(value))
|
jpayne@68
|
520 # XXX(twouters): using "_arbitrary_instance" as key as a quick-fix
|
jpayne@68
|
521 # for the p3yk merge, this should probably be fixed more neatly.
|
jpayne@68
|
522 f = self.dispatch["_arbitrary_instance"]
|
jpayne@68
|
523 f(self, value, write)
|
jpayne@68
|
524
|
jpayne@68
|
525 def dump_nil (self, value, write):
|
jpayne@68
|
526 if not self.allow_none:
|
jpayne@68
|
527 raise TypeError("cannot marshal None unless allow_none is enabled")
|
jpayne@68
|
528 write("<value><nil/></value>")
|
jpayne@68
|
529 dispatch[type(None)] = dump_nil
|
jpayne@68
|
530
|
jpayne@68
|
531 def dump_bool(self, value, write):
|
jpayne@68
|
532 write("<value><boolean>")
|
jpayne@68
|
533 write(value and "1" or "0")
|
jpayne@68
|
534 write("</boolean></value>\n")
|
jpayne@68
|
535 dispatch[bool] = dump_bool
|
jpayne@68
|
536
|
jpayne@68
|
537 def dump_long(self, value, write):
|
jpayne@68
|
538 if value > MAXINT or value < MININT:
|
jpayne@68
|
539 raise OverflowError("int exceeds XML-RPC limits")
|
jpayne@68
|
540 write("<value><int>")
|
jpayne@68
|
541 write(str(int(value)))
|
jpayne@68
|
542 write("</int></value>\n")
|
jpayne@68
|
543 dispatch[int] = dump_long
|
jpayne@68
|
544
|
jpayne@68
|
545 # backward compatible
|
jpayne@68
|
546 dump_int = dump_long
|
jpayne@68
|
547
|
jpayne@68
|
548 def dump_double(self, value, write):
|
jpayne@68
|
549 write("<value><double>")
|
jpayne@68
|
550 write(repr(value))
|
jpayne@68
|
551 write("</double></value>\n")
|
jpayne@68
|
552 dispatch[float] = dump_double
|
jpayne@68
|
553
|
jpayne@68
|
554 def dump_unicode(self, value, write, escape=escape):
|
jpayne@68
|
555 write("<value><string>")
|
jpayne@68
|
556 write(escape(value))
|
jpayne@68
|
557 write("</string></value>\n")
|
jpayne@68
|
558 dispatch[str] = dump_unicode
|
jpayne@68
|
559
|
jpayne@68
|
560 def dump_bytes(self, value, write):
|
jpayne@68
|
561 write("<value><base64>\n")
|
jpayne@68
|
562 encoded = base64.encodebytes(value)
|
jpayne@68
|
563 write(encoded.decode('ascii'))
|
jpayne@68
|
564 write("</base64></value>\n")
|
jpayne@68
|
565 dispatch[bytes] = dump_bytes
|
jpayne@68
|
566 dispatch[bytearray] = dump_bytes
|
jpayne@68
|
567
|
jpayne@68
|
568 def dump_array(self, value, write):
|
jpayne@68
|
569 i = id(value)
|
jpayne@68
|
570 if i in self.memo:
|
jpayne@68
|
571 raise TypeError("cannot marshal recursive sequences")
|
jpayne@68
|
572 self.memo[i] = None
|
jpayne@68
|
573 dump = self.__dump
|
jpayne@68
|
574 write("<value><array><data>\n")
|
jpayne@68
|
575 for v in value:
|
jpayne@68
|
576 dump(v, write)
|
jpayne@68
|
577 write("</data></array></value>\n")
|
jpayne@68
|
578 del self.memo[i]
|
jpayne@68
|
579 dispatch[tuple] = dump_array
|
jpayne@68
|
580 dispatch[list] = dump_array
|
jpayne@68
|
581
|
jpayne@68
|
582 def dump_struct(self, value, write, escape=escape):
|
jpayne@68
|
583 i = id(value)
|
jpayne@68
|
584 if i in self.memo:
|
jpayne@68
|
585 raise TypeError("cannot marshal recursive dictionaries")
|
jpayne@68
|
586 self.memo[i] = None
|
jpayne@68
|
587 dump = self.__dump
|
jpayne@68
|
588 write("<value><struct>\n")
|
jpayne@68
|
589 for k, v in value.items():
|
jpayne@68
|
590 write("<member>\n")
|
jpayne@68
|
591 if not isinstance(k, str):
|
jpayne@68
|
592 raise TypeError("dictionary key must be string")
|
jpayne@68
|
593 write("<name>%s</name>\n" % escape(k))
|
jpayne@68
|
594 dump(v, write)
|
jpayne@68
|
595 write("</member>\n")
|
jpayne@68
|
596 write("</struct></value>\n")
|
jpayne@68
|
597 del self.memo[i]
|
jpayne@68
|
598 dispatch[dict] = dump_struct
|
jpayne@68
|
599
|
jpayne@68
|
600 def dump_datetime(self, value, write):
|
jpayne@68
|
601 write("<value><dateTime.iso8601>")
|
jpayne@68
|
602 write(_strftime(value))
|
jpayne@68
|
603 write("</dateTime.iso8601></value>\n")
|
jpayne@68
|
604 dispatch[datetime] = dump_datetime
|
jpayne@68
|
605
|
jpayne@68
|
606 def dump_instance(self, value, write):
|
jpayne@68
|
607 # check for special wrappers
|
jpayne@68
|
608 if value.__class__ in WRAPPERS:
|
jpayne@68
|
609 self.write = write
|
jpayne@68
|
610 value.encode(self)
|
jpayne@68
|
611 del self.write
|
jpayne@68
|
612 else:
|
jpayne@68
|
613 # store instance attributes as a struct (really?)
|
jpayne@68
|
614 self.dump_struct(value.__dict__, write)
|
jpayne@68
|
615 dispatch[DateTime] = dump_instance
|
jpayne@68
|
616 dispatch[Binary] = dump_instance
|
jpayne@68
|
617 # XXX(twouters): using "_arbitrary_instance" as key as a quick-fix
|
jpayne@68
|
618 # for the p3yk merge, this should probably be fixed more neatly.
|
jpayne@68
|
619 dispatch["_arbitrary_instance"] = dump_instance
|
jpayne@68
|
620
|
jpayne@68
|
621 ##
|
jpayne@68
|
622 # XML-RPC unmarshaller.
|
jpayne@68
|
623 #
|
jpayne@68
|
624 # @see loads
|
jpayne@68
|
625
|
jpayne@68
|
626 class Unmarshaller:
|
jpayne@68
|
627 """Unmarshal an XML-RPC response, based on incoming XML event
|
jpayne@68
|
628 messages (start, data, end). Call close() to get the resulting
|
jpayne@68
|
629 data structure.
|
jpayne@68
|
630
|
jpayne@68
|
631 Note that this reader is fairly tolerant, and gladly accepts bogus
|
jpayne@68
|
632 XML-RPC data without complaining (but not bogus XML).
|
jpayne@68
|
633 """
|
jpayne@68
|
634
|
jpayne@68
|
635 # and again, if you don't understand what's going on in here,
|
jpayne@68
|
636 # that's perfectly ok.
|
jpayne@68
|
637
|
jpayne@68
|
638 def __init__(self, use_datetime=False, use_builtin_types=False):
|
jpayne@68
|
639 self._type = None
|
jpayne@68
|
640 self._stack = []
|
jpayne@68
|
641 self._marks = []
|
jpayne@68
|
642 self._data = []
|
jpayne@68
|
643 self._value = False
|
jpayne@68
|
644 self._methodname = None
|
jpayne@68
|
645 self._encoding = "utf-8"
|
jpayne@68
|
646 self.append = self._stack.append
|
jpayne@68
|
647 self._use_datetime = use_builtin_types or use_datetime
|
jpayne@68
|
648 self._use_bytes = use_builtin_types
|
jpayne@68
|
649
|
jpayne@68
|
650 def close(self):
|
jpayne@68
|
651 # return response tuple and target method
|
jpayne@68
|
652 if self._type is None or self._marks:
|
jpayne@68
|
653 raise ResponseError()
|
jpayne@68
|
654 if self._type == "fault":
|
jpayne@68
|
655 raise Fault(**self._stack[0])
|
jpayne@68
|
656 return tuple(self._stack)
|
jpayne@68
|
657
|
jpayne@68
|
658 def getmethodname(self):
|
jpayne@68
|
659 return self._methodname
|
jpayne@68
|
660
|
jpayne@68
|
661 #
|
jpayne@68
|
662 # event handlers
|
jpayne@68
|
663
|
jpayne@68
|
664 def xml(self, encoding, standalone):
|
jpayne@68
|
665 self._encoding = encoding
|
jpayne@68
|
666 # FIXME: assert standalone == 1 ???
|
jpayne@68
|
667
|
jpayne@68
|
668 def start(self, tag, attrs):
|
jpayne@68
|
669 # prepare to handle this element
|
jpayne@68
|
670 if ':' in tag:
|
jpayne@68
|
671 tag = tag.split(':')[-1]
|
jpayne@68
|
672 if tag == "array" or tag == "struct":
|
jpayne@68
|
673 self._marks.append(len(self._stack))
|
jpayne@68
|
674 self._data = []
|
jpayne@68
|
675 if self._value and tag not in self.dispatch:
|
jpayne@68
|
676 raise ResponseError("unknown tag %r" % tag)
|
jpayne@68
|
677 self._value = (tag == "value")
|
jpayne@68
|
678
|
jpayne@68
|
679 def data(self, text):
|
jpayne@68
|
680 self._data.append(text)
|
jpayne@68
|
681
|
jpayne@68
|
682 def end(self, tag):
|
jpayne@68
|
683 # call the appropriate end tag handler
|
jpayne@68
|
684 try:
|
jpayne@68
|
685 f = self.dispatch[tag]
|
jpayne@68
|
686 except KeyError:
|
jpayne@68
|
687 if ':' not in tag:
|
jpayne@68
|
688 return # unknown tag ?
|
jpayne@68
|
689 try:
|
jpayne@68
|
690 f = self.dispatch[tag.split(':')[-1]]
|
jpayne@68
|
691 except KeyError:
|
jpayne@68
|
692 return # unknown tag ?
|
jpayne@68
|
693 return f(self, "".join(self._data))
|
jpayne@68
|
694
|
jpayne@68
|
695 #
|
jpayne@68
|
696 # accelerator support
|
jpayne@68
|
697
|
jpayne@68
|
698 def end_dispatch(self, tag, data):
|
jpayne@68
|
699 # dispatch data
|
jpayne@68
|
700 try:
|
jpayne@68
|
701 f = self.dispatch[tag]
|
jpayne@68
|
702 except KeyError:
|
jpayne@68
|
703 if ':' not in tag:
|
jpayne@68
|
704 return # unknown tag ?
|
jpayne@68
|
705 try:
|
jpayne@68
|
706 f = self.dispatch[tag.split(':')[-1]]
|
jpayne@68
|
707 except KeyError:
|
jpayne@68
|
708 return # unknown tag ?
|
jpayne@68
|
709 return f(self, data)
|
jpayne@68
|
710
|
jpayne@68
|
711 #
|
jpayne@68
|
712 # element decoders
|
jpayne@68
|
713
|
jpayne@68
|
714 dispatch = {}
|
jpayne@68
|
715
|
jpayne@68
|
716 def end_nil (self, data):
|
jpayne@68
|
717 self.append(None)
|
jpayne@68
|
718 self._value = 0
|
jpayne@68
|
719 dispatch["nil"] = end_nil
|
jpayne@68
|
720
|
jpayne@68
|
721 def end_boolean(self, data):
|
jpayne@68
|
722 if data == "0":
|
jpayne@68
|
723 self.append(False)
|
jpayne@68
|
724 elif data == "1":
|
jpayne@68
|
725 self.append(True)
|
jpayne@68
|
726 else:
|
jpayne@68
|
727 raise TypeError("bad boolean value")
|
jpayne@68
|
728 self._value = 0
|
jpayne@68
|
729 dispatch["boolean"] = end_boolean
|
jpayne@68
|
730
|
jpayne@68
|
731 def end_int(self, data):
|
jpayne@68
|
732 self.append(int(data))
|
jpayne@68
|
733 self._value = 0
|
jpayne@68
|
734 dispatch["i1"] = end_int
|
jpayne@68
|
735 dispatch["i2"] = end_int
|
jpayne@68
|
736 dispatch["i4"] = end_int
|
jpayne@68
|
737 dispatch["i8"] = end_int
|
jpayne@68
|
738 dispatch["int"] = end_int
|
jpayne@68
|
739 dispatch["biginteger"] = end_int
|
jpayne@68
|
740
|
jpayne@68
|
741 def end_double(self, data):
|
jpayne@68
|
742 self.append(float(data))
|
jpayne@68
|
743 self._value = 0
|
jpayne@68
|
744 dispatch["double"] = end_double
|
jpayne@68
|
745 dispatch["float"] = end_double
|
jpayne@68
|
746
|
jpayne@68
|
747 def end_bigdecimal(self, data):
|
jpayne@68
|
748 self.append(Decimal(data))
|
jpayne@68
|
749 self._value = 0
|
jpayne@68
|
750 dispatch["bigdecimal"] = end_bigdecimal
|
jpayne@68
|
751
|
jpayne@68
|
752 def end_string(self, data):
|
jpayne@68
|
753 if self._encoding:
|
jpayne@68
|
754 data = data.decode(self._encoding)
|
jpayne@68
|
755 self.append(data)
|
jpayne@68
|
756 self._value = 0
|
jpayne@68
|
757 dispatch["string"] = end_string
|
jpayne@68
|
758 dispatch["name"] = end_string # struct keys are always strings
|
jpayne@68
|
759
|
jpayne@68
|
760 def end_array(self, data):
|
jpayne@68
|
761 mark = self._marks.pop()
|
jpayne@68
|
762 # map arrays to Python lists
|
jpayne@68
|
763 self._stack[mark:] = [self._stack[mark:]]
|
jpayne@68
|
764 self._value = 0
|
jpayne@68
|
765 dispatch["array"] = end_array
|
jpayne@68
|
766
|
jpayne@68
|
767 def end_struct(self, data):
|
jpayne@68
|
768 mark = self._marks.pop()
|
jpayne@68
|
769 # map structs to Python dictionaries
|
jpayne@68
|
770 dict = {}
|
jpayne@68
|
771 items = self._stack[mark:]
|
jpayne@68
|
772 for i in range(0, len(items), 2):
|
jpayne@68
|
773 dict[items[i]] = items[i+1]
|
jpayne@68
|
774 self._stack[mark:] = [dict]
|
jpayne@68
|
775 self._value = 0
|
jpayne@68
|
776 dispatch["struct"] = end_struct
|
jpayne@68
|
777
|
jpayne@68
|
778 def end_base64(self, data):
|
jpayne@68
|
779 value = Binary()
|
jpayne@68
|
780 value.decode(data.encode("ascii"))
|
jpayne@68
|
781 if self._use_bytes:
|
jpayne@68
|
782 value = value.data
|
jpayne@68
|
783 self.append(value)
|
jpayne@68
|
784 self._value = 0
|
jpayne@68
|
785 dispatch["base64"] = end_base64
|
jpayne@68
|
786
|
jpayne@68
|
787 def end_dateTime(self, data):
|
jpayne@68
|
788 value = DateTime()
|
jpayne@68
|
789 value.decode(data)
|
jpayne@68
|
790 if self._use_datetime:
|
jpayne@68
|
791 value = _datetime_type(data)
|
jpayne@68
|
792 self.append(value)
|
jpayne@68
|
793 dispatch["dateTime.iso8601"] = end_dateTime
|
jpayne@68
|
794
|
jpayne@68
|
795 def end_value(self, data):
|
jpayne@68
|
796 # if we stumble upon a value element with no internal
|
jpayne@68
|
797 # elements, treat it as a string element
|
jpayne@68
|
798 if self._value:
|
jpayne@68
|
799 self.end_string(data)
|
jpayne@68
|
800 dispatch["value"] = end_value
|
jpayne@68
|
801
|
jpayne@68
|
802 def end_params(self, data):
|
jpayne@68
|
803 self._type = "params"
|
jpayne@68
|
804 dispatch["params"] = end_params
|
jpayne@68
|
805
|
jpayne@68
|
806 def end_fault(self, data):
|
jpayne@68
|
807 self._type = "fault"
|
jpayne@68
|
808 dispatch["fault"] = end_fault
|
jpayne@68
|
809
|
jpayne@68
|
810 def end_methodName(self, data):
|
jpayne@68
|
811 if self._encoding:
|
jpayne@68
|
812 data = data.decode(self._encoding)
|
jpayne@68
|
813 self._methodname = data
|
jpayne@68
|
814 self._type = "methodName" # no params
|
jpayne@68
|
815 dispatch["methodName"] = end_methodName
|
jpayne@68
|
816
|
jpayne@68
|
817 ## Multicall support
|
jpayne@68
|
818 #
|
jpayne@68
|
819
|
jpayne@68
|
820 class _MultiCallMethod:
|
jpayne@68
|
821 # some lesser magic to store calls made to a MultiCall object
|
jpayne@68
|
822 # for batch execution
|
jpayne@68
|
823 def __init__(self, call_list, name):
|
jpayne@68
|
824 self.__call_list = call_list
|
jpayne@68
|
825 self.__name = name
|
jpayne@68
|
826 def __getattr__(self, name):
|
jpayne@68
|
827 return _MultiCallMethod(self.__call_list, "%s.%s" % (self.__name, name))
|
jpayne@68
|
828 def __call__(self, *args):
|
jpayne@68
|
829 self.__call_list.append((self.__name, args))
|
jpayne@68
|
830
|
jpayne@68
|
831 class MultiCallIterator:
|
jpayne@68
|
832 """Iterates over the results of a multicall. Exceptions are
|
jpayne@68
|
833 raised in response to xmlrpc faults."""
|
jpayne@68
|
834
|
jpayne@68
|
835 def __init__(self, results):
|
jpayne@68
|
836 self.results = results
|
jpayne@68
|
837
|
jpayne@68
|
838 def __getitem__(self, i):
|
jpayne@68
|
839 item = self.results[i]
|
jpayne@68
|
840 if type(item) == type({}):
|
jpayne@68
|
841 raise Fault(item['faultCode'], item['faultString'])
|
jpayne@68
|
842 elif type(item) == type([]):
|
jpayne@68
|
843 return item[0]
|
jpayne@68
|
844 else:
|
jpayne@68
|
845 raise ValueError("unexpected type in multicall result")
|
jpayne@68
|
846
|
jpayne@68
|
847 class MultiCall:
|
jpayne@68
|
848 """server -> an object used to boxcar method calls
|
jpayne@68
|
849
|
jpayne@68
|
850 server should be a ServerProxy object.
|
jpayne@68
|
851
|
jpayne@68
|
852 Methods can be added to the MultiCall using normal
|
jpayne@68
|
853 method call syntax e.g.:
|
jpayne@68
|
854
|
jpayne@68
|
855 multicall = MultiCall(server_proxy)
|
jpayne@68
|
856 multicall.add(2,3)
|
jpayne@68
|
857 multicall.get_address("Guido")
|
jpayne@68
|
858
|
jpayne@68
|
859 To execute the multicall, call the MultiCall object e.g.:
|
jpayne@68
|
860
|
jpayne@68
|
861 add_result, address = multicall()
|
jpayne@68
|
862 """
|
jpayne@68
|
863
|
jpayne@68
|
864 def __init__(self, server):
|
jpayne@68
|
865 self.__server = server
|
jpayne@68
|
866 self.__call_list = []
|
jpayne@68
|
867
|
jpayne@68
|
868 def __repr__(self):
|
jpayne@68
|
869 return "<%s at %#x>" % (self.__class__.__name__, id(self))
|
jpayne@68
|
870
|
jpayne@68
|
871 def __getattr__(self, name):
|
jpayne@68
|
872 return _MultiCallMethod(self.__call_list, name)
|
jpayne@68
|
873
|
jpayne@68
|
874 def __call__(self):
|
jpayne@68
|
875 marshalled_list = []
|
jpayne@68
|
876 for name, args in self.__call_list:
|
jpayne@68
|
877 marshalled_list.append({'methodName' : name, 'params' : args})
|
jpayne@68
|
878
|
jpayne@68
|
879 return MultiCallIterator(self.__server.system.multicall(marshalled_list))
|
jpayne@68
|
880
|
jpayne@68
|
881 # --------------------------------------------------------------------
|
jpayne@68
|
882 # convenience functions
|
jpayne@68
|
883
|
jpayne@68
|
884 FastMarshaller = FastParser = FastUnmarshaller = None
|
jpayne@68
|
885
|
jpayne@68
|
886 ##
|
jpayne@68
|
887 # Create a parser object, and connect it to an unmarshalling instance.
|
jpayne@68
|
888 # This function picks the fastest available XML parser.
|
jpayne@68
|
889 #
|
jpayne@68
|
890 # return A (parser, unmarshaller) tuple.
|
jpayne@68
|
891
|
jpayne@68
|
892 def getparser(use_datetime=False, use_builtin_types=False):
|
jpayne@68
|
893 """getparser() -> parser, unmarshaller
|
jpayne@68
|
894
|
jpayne@68
|
895 Create an instance of the fastest available parser, and attach it
|
jpayne@68
|
896 to an unmarshalling object. Return both objects.
|
jpayne@68
|
897 """
|
jpayne@68
|
898 if FastParser and FastUnmarshaller:
|
jpayne@68
|
899 if use_builtin_types:
|
jpayne@68
|
900 mkdatetime = _datetime_type
|
jpayne@68
|
901 mkbytes = base64.decodebytes
|
jpayne@68
|
902 elif use_datetime:
|
jpayne@68
|
903 mkdatetime = _datetime_type
|
jpayne@68
|
904 mkbytes = _binary
|
jpayne@68
|
905 else:
|
jpayne@68
|
906 mkdatetime = _datetime
|
jpayne@68
|
907 mkbytes = _binary
|
jpayne@68
|
908 target = FastUnmarshaller(True, False, mkbytes, mkdatetime, Fault)
|
jpayne@68
|
909 parser = FastParser(target)
|
jpayne@68
|
910 else:
|
jpayne@68
|
911 target = Unmarshaller(use_datetime=use_datetime, use_builtin_types=use_builtin_types)
|
jpayne@68
|
912 if FastParser:
|
jpayne@68
|
913 parser = FastParser(target)
|
jpayne@68
|
914 else:
|
jpayne@68
|
915 parser = ExpatParser(target)
|
jpayne@68
|
916 return parser, target
|
jpayne@68
|
917
|
jpayne@68
|
918 ##
|
jpayne@68
|
919 # Convert a Python tuple or a Fault instance to an XML-RPC packet.
|
jpayne@68
|
920 #
|
jpayne@68
|
921 # @def dumps(params, **options)
|
jpayne@68
|
922 # @param params A tuple or Fault instance.
|
jpayne@68
|
923 # @keyparam methodname If given, create a methodCall request for
|
jpayne@68
|
924 # this method name.
|
jpayne@68
|
925 # @keyparam methodresponse If given, create a methodResponse packet.
|
jpayne@68
|
926 # If used with a tuple, the tuple must be a singleton (that is,
|
jpayne@68
|
927 # it must contain exactly one element).
|
jpayne@68
|
928 # @keyparam encoding The packet encoding.
|
jpayne@68
|
929 # @return A string containing marshalled data.
|
jpayne@68
|
930
|
jpayne@68
|
931 def dumps(params, methodname=None, methodresponse=None, encoding=None,
|
jpayne@68
|
932 allow_none=False):
|
jpayne@68
|
933 """data [,options] -> marshalled data
|
jpayne@68
|
934
|
jpayne@68
|
935 Convert an argument tuple or a Fault instance to an XML-RPC
|
jpayne@68
|
936 request (or response, if the methodresponse option is used).
|
jpayne@68
|
937
|
jpayne@68
|
938 In addition to the data object, the following options can be given
|
jpayne@68
|
939 as keyword arguments:
|
jpayne@68
|
940
|
jpayne@68
|
941 methodname: the method name for a methodCall packet
|
jpayne@68
|
942
|
jpayne@68
|
943 methodresponse: true to create a methodResponse packet.
|
jpayne@68
|
944 If this option is used with a tuple, the tuple must be
|
jpayne@68
|
945 a singleton (i.e. it can contain only one element).
|
jpayne@68
|
946
|
jpayne@68
|
947 encoding: the packet encoding (default is UTF-8)
|
jpayne@68
|
948
|
jpayne@68
|
949 All byte strings in the data structure are assumed to use the
|
jpayne@68
|
950 packet encoding. Unicode strings are automatically converted,
|
jpayne@68
|
951 where necessary.
|
jpayne@68
|
952 """
|
jpayne@68
|
953
|
jpayne@68
|
954 assert isinstance(params, (tuple, Fault)), "argument must be tuple or Fault instance"
|
jpayne@68
|
955 if isinstance(params, Fault):
|
jpayne@68
|
956 methodresponse = 1
|
jpayne@68
|
957 elif methodresponse and isinstance(params, tuple):
|
jpayne@68
|
958 assert len(params) == 1, "response tuple must be a singleton"
|
jpayne@68
|
959
|
jpayne@68
|
960 if not encoding:
|
jpayne@68
|
961 encoding = "utf-8"
|
jpayne@68
|
962
|
jpayne@68
|
963 if FastMarshaller:
|
jpayne@68
|
964 m = FastMarshaller(encoding)
|
jpayne@68
|
965 else:
|
jpayne@68
|
966 m = Marshaller(encoding, allow_none)
|
jpayne@68
|
967
|
jpayne@68
|
968 data = m.dumps(params)
|
jpayne@68
|
969
|
jpayne@68
|
970 if encoding != "utf-8":
|
jpayne@68
|
971 xmlheader = "<?xml version='1.0' encoding='%s'?>\n" % str(encoding)
|
jpayne@68
|
972 else:
|
jpayne@68
|
973 xmlheader = "<?xml version='1.0'?>\n" # utf-8 is default
|
jpayne@68
|
974
|
jpayne@68
|
975 # standard XML-RPC wrappings
|
jpayne@68
|
976 if methodname:
|
jpayne@68
|
977 # a method call
|
jpayne@68
|
978 data = (
|
jpayne@68
|
979 xmlheader,
|
jpayne@68
|
980 "<methodCall>\n"
|
jpayne@68
|
981 "<methodName>", methodname, "</methodName>\n",
|
jpayne@68
|
982 data,
|
jpayne@68
|
983 "</methodCall>\n"
|
jpayne@68
|
984 )
|
jpayne@68
|
985 elif methodresponse:
|
jpayne@68
|
986 # a method response, or a fault structure
|
jpayne@68
|
987 data = (
|
jpayne@68
|
988 xmlheader,
|
jpayne@68
|
989 "<methodResponse>\n",
|
jpayne@68
|
990 data,
|
jpayne@68
|
991 "</methodResponse>\n"
|
jpayne@68
|
992 )
|
jpayne@68
|
993 else:
|
jpayne@68
|
994 return data # return as is
|
jpayne@68
|
995 return "".join(data)
|
jpayne@68
|
996
|
jpayne@68
|
997 ##
|
jpayne@68
|
998 # Convert an XML-RPC packet to a Python object. If the XML-RPC packet
|
jpayne@68
|
999 # represents a fault condition, this function raises a Fault exception.
|
jpayne@68
|
1000 #
|
jpayne@68
|
1001 # @param data An XML-RPC packet, given as an 8-bit string.
|
jpayne@68
|
1002 # @return A tuple containing the unpacked data, and the method name
|
jpayne@68
|
1003 # (None if not present).
|
jpayne@68
|
1004 # @see Fault
|
jpayne@68
|
1005
|
jpayne@68
|
1006 def loads(data, use_datetime=False, use_builtin_types=False):
|
jpayne@68
|
1007 """data -> unmarshalled data, method name
|
jpayne@68
|
1008
|
jpayne@68
|
1009 Convert an XML-RPC packet to unmarshalled data plus a method
|
jpayne@68
|
1010 name (None if not present).
|
jpayne@68
|
1011
|
jpayne@68
|
1012 If the XML-RPC packet represents a fault condition, this function
|
jpayne@68
|
1013 raises a Fault exception.
|
jpayne@68
|
1014 """
|
jpayne@68
|
1015 p, u = getparser(use_datetime=use_datetime, use_builtin_types=use_builtin_types)
|
jpayne@68
|
1016 p.feed(data)
|
jpayne@68
|
1017 p.close()
|
jpayne@68
|
1018 return u.close(), u.getmethodname()
|
jpayne@68
|
1019
|
jpayne@68
|
1020 ##
|
jpayne@68
|
1021 # Encode a string using the gzip content encoding such as specified by the
|
jpayne@68
|
1022 # Content-Encoding: gzip
|
jpayne@68
|
1023 # in the HTTP header, as described in RFC 1952
|
jpayne@68
|
1024 #
|
jpayne@68
|
1025 # @param data the unencoded data
|
jpayne@68
|
1026 # @return the encoded data
|
jpayne@68
|
1027
|
jpayne@68
|
1028 def gzip_encode(data):
|
jpayne@68
|
1029 """data -> gzip encoded data
|
jpayne@68
|
1030
|
jpayne@68
|
1031 Encode data using the gzip content encoding as described in RFC 1952
|
jpayne@68
|
1032 """
|
jpayne@68
|
1033 if not gzip:
|
jpayne@68
|
1034 raise NotImplementedError
|
jpayne@68
|
1035 f = BytesIO()
|
jpayne@68
|
1036 with gzip.GzipFile(mode="wb", fileobj=f, compresslevel=1) as gzf:
|
jpayne@68
|
1037 gzf.write(data)
|
jpayne@68
|
1038 return f.getvalue()
|
jpayne@68
|
1039
|
jpayne@68
|
1040 ##
|
jpayne@68
|
1041 # Decode a string using the gzip content encoding such as specified by the
|
jpayne@68
|
1042 # Content-Encoding: gzip
|
jpayne@68
|
1043 # in the HTTP header, as described in RFC 1952
|
jpayne@68
|
1044 #
|
jpayne@68
|
1045 # @param data The encoded data
|
jpayne@68
|
1046 # @keyparam max_decode Maximum bytes to decode (20 MiB default), use negative
|
jpayne@68
|
1047 # values for unlimited decoding
|
jpayne@68
|
1048 # @return the unencoded data
|
jpayne@68
|
1049 # @raises ValueError if data is not correctly coded.
|
jpayne@68
|
1050 # @raises ValueError if max gzipped payload length exceeded
|
jpayne@68
|
1051
|
jpayne@68
|
1052 def gzip_decode(data, max_decode=20971520):
|
jpayne@68
|
1053 """gzip encoded data -> unencoded data
|
jpayne@68
|
1054
|
jpayne@68
|
1055 Decode data using the gzip content encoding as described in RFC 1952
|
jpayne@68
|
1056 """
|
jpayne@68
|
1057 if not gzip:
|
jpayne@68
|
1058 raise NotImplementedError
|
jpayne@68
|
1059 with gzip.GzipFile(mode="rb", fileobj=BytesIO(data)) as gzf:
|
jpayne@68
|
1060 try:
|
jpayne@68
|
1061 if max_decode < 0: # no limit
|
jpayne@68
|
1062 decoded = gzf.read()
|
jpayne@68
|
1063 else:
|
jpayne@68
|
1064 decoded = gzf.read(max_decode + 1)
|
jpayne@68
|
1065 except OSError:
|
jpayne@68
|
1066 raise ValueError("invalid data")
|
jpayne@68
|
1067 if max_decode >= 0 and len(decoded) > max_decode:
|
jpayne@68
|
1068 raise ValueError("max gzipped payload length exceeded")
|
jpayne@68
|
1069 return decoded
|
jpayne@68
|
1070
|
jpayne@68
|
1071 ##
|
jpayne@68
|
1072 # Return a decoded file-like object for the gzip encoding
|
jpayne@68
|
1073 # as described in RFC 1952.
|
jpayne@68
|
1074 #
|
jpayne@68
|
1075 # @param response A stream supporting a read() method
|
jpayne@68
|
1076 # @return a file-like object that the decoded data can be read() from
|
jpayne@68
|
1077
|
jpayne@68
|
1078 class GzipDecodedResponse(gzip.GzipFile if gzip else object):
|
jpayne@68
|
1079 """a file-like object to decode a response encoded with the gzip
|
jpayne@68
|
1080 method, as described in RFC 1952.
|
jpayne@68
|
1081 """
|
jpayne@68
|
1082 def __init__(self, response):
|
jpayne@68
|
1083 #response doesn't support tell() and read(), required by
|
jpayne@68
|
1084 #GzipFile
|
jpayne@68
|
1085 if not gzip:
|
jpayne@68
|
1086 raise NotImplementedError
|
jpayne@68
|
1087 self.io = BytesIO(response.read())
|
jpayne@68
|
1088 gzip.GzipFile.__init__(self, mode="rb", fileobj=self.io)
|
jpayne@68
|
1089
|
jpayne@68
|
1090 def close(self):
|
jpayne@68
|
1091 try:
|
jpayne@68
|
1092 gzip.GzipFile.close(self)
|
jpayne@68
|
1093 finally:
|
jpayne@68
|
1094 self.io.close()
|
jpayne@68
|
1095
|
jpayne@68
|
1096
|
jpayne@68
|
1097 # --------------------------------------------------------------------
|
jpayne@68
|
1098 # request dispatcher
|
jpayne@68
|
1099
|
jpayne@68
|
1100 class _Method:
|
jpayne@68
|
1101 # some magic to bind an XML-RPC method to an RPC server.
|
jpayne@68
|
1102 # supports "nested" methods (e.g. examples.getStateName)
|
jpayne@68
|
1103 def __init__(self, send, name):
|
jpayne@68
|
1104 self.__send = send
|
jpayne@68
|
1105 self.__name = name
|
jpayne@68
|
1106 def __getattr__(self, name):
|
jpayne@68
|
1107 return _Method(self.__send, "%s.%s" % (self.__name, name))
|
jpayne@68
|
1108 def __call__(self, *args):
|
jpayne@68
|
1109 return self.__send(self.__name, args)
|
jpayne@68
|
1110
|
jpayne@68
|
1111 ##
|
jpayne@68
|
1112 # Standard transport class for XML-RPC over HTTP.
|
jpayne@68
|
1113 # <p>
|
jpayne@68
|
1114 # You can create custom transports by subclassing this method, and
|
jpayne@68
|
1115 # overriding selected methods.
|
jpayne@68
|
1116
|
jpayne@68
|
1117 class Transport:
|
jpayne@68
|
1118 """Handles an HTTP transaction to an XML-RPC server."""
|
jpayne@68
|
1119
|
jpayne@68
|
1120 # client identifier (may be overridden)
|
jpayne@68
|
1121 user_agent = "Python-xmlrpc/%s" % __version__
|
jpayne@68
|
1122
|
jpayne@68
|
1123 #if true, we'll request gzip encoding
|
jpayne@68
|
1124 accept_gzip_encoding = True
|
jpayne@68
|
1125
|
jpayne@68
|
1126 # if positive, encode request using gzip if it exceeds this threshold
|
jpayne@68
|
1127 # note that many servers will get confused, so only use it if you know
|
jpayne@68
|
1128 # that they can decode such a request
|
jpayne@68
|
1129 encode_threshold = None #None = don't encode
|
jpayne@68
|
1130
|
jpayne@68
|
1131 def __init__(self, use_datetime=False, use_builtin_types=False,
|
jpayne@68
|
1132 *, headers=()):
|
jpayne@68
|
1133 self._use_datetime = use_datetime
|
jpayne@68
|
1134 self._use_builtin_types = use_builtin_types
|
jpayne@68
|
1135 self._connection = (None, None)
|
jpayne@68
|
1136 self._headers = list(headers)
|
jpayne@68
|
1137 self._extra_headers = []
|
jpayne@68
|
1138
|
jpayne@68
|
1139 ##
|
jpayne@68
|
1140 # Send a complete request, and parse the response.
|
jpayne@68
|
1141 # Retry request if a cached connection has disconnected.
|
jpayne@68
|
1142 #
|
jpayne@68
|
1143 # @param host Target host.
|
jpayne@68
|
1144 # @param handler Target PRC handler.
|
jpayne@68
|
1145 # @param request_body XML-RPC request body.
|
jpayne@68
|
1146 # @param verbose Debugging flag.
|
jpayne@68
|
1147 # @return Parsed response.
|
jpayne@68
|
1148
|
jpayne@68
|
1149 def request(self, host, handler, request_body, verbose=False):
|
jpayne@68
|
1150 #retry request once if cached connection has gone cold
|
jpayne@68
|
1151 for i in (0, 1):
|
jpayne@68
|
1152 try:
|
jpayne@68
|
1153 return self.single_request(host, handler, request_body, verbose)
|
jpayne@68
|
1154 except http.client.RemoteDisconnected:
|
jpayne@68
|
1155 if i:
|
jpayne@68
|
1156 raise
|
jpayne@68
|
1157 except OSError as e:
|
jpayne@68
|
1158 if i or e.errno not in (errno.ECONNRESET, errno.ECONNABORTED,
|
jpayne@68
|
1159 errno.EPIPE):
|
jpayne@68
|
1160 raise
|
jpayne@68
|
1161
|
jpayne@68
|
1162 def single_request(self, host, handler, request_body, verbose=False):
|
jpayne@68
|
1163 # issue XML-RPC request
|
jpayne@68
|
1164 try:
|
jpayne@68
|
1165 http_conn = self.send_request(host, handler, request_body, verbose)
|
jpayne@68
|
1166 resp = http_conn.getresponse()
|
jpayne@68
|
1167 if resp.status == 200:
|
jpayne@68
|
1168 self.verbose = verbose
|
jpayne@68
|
1169 return self.parse_response(resp)
|
jpayne@68
|
1170
|
jpayne@68
|
1171 except Fault:
|
jpayne@68
|
1172 raise
|
jpayne@68
|
1173 except Exception:
|
jpayne@68
|
1174 #All unexpected errors leave connection in
|
jpayne@68
|
1175 # a strange state, so we clear it.
|
jpayne@68
|
1176 self.close()
|
jpayne@68
|
1177 raise
|
jpayne@68
|
1178
|
jpayne@68
|
1179 #We got an error response.
|
jpayne@68
|
1180 #Discard any response data and raise exception
|
jpayne@68
|
1181 if resp.getheader("content-length", ""):
|
jpayne@68
|
1182 resp.read()
|
jpayne@68
|
1183 raise ProtocolError(
|
jpayne@68
|
1184 host + handler,
|
jpayne@68
|
1185 resp.status, resp.reason,
|
jpayne@68
|
1186 dict(resp.getheaders())
|
jpayne@68
|
1187 )
|
jpayne@68
|
1188
|
jpayne@68
|
1189
|
jpayne@68
|
1190 ##
|
jpayne@68
|
1191 # Create parser.
|
jpayne@68
|
1192 #
|
jpayne@68
|
1193 # @return A 2-tuple containing a parser and an unmarshaller.
|
jpayne@68
|
1194
|
jpayne@68
|
1195 def getparser(self):
|
jpayne@68
|
1196 # get parser and unmarshaller
|
jpayne@68
|
1197 return getparser(use_datetime=self._use_datetime,
|
jpayne@68
|
1198 use_builtin_types=self._use_builtin_types)
|
jpayne@68
|
1199
|
jpayne@68
|
1200 ##
|
jpayne@68
|
1201 # Get authorization info from host parameter
|
jpayne@68
|
1202 # Host may be a string, or a (host, x509-dict) tuple; if a string,
|
jpayne@68
|
1203 # it is checked for a "user:pw@host" format, and a "Basic
|
jpayne@68
|
1204 # Authentication" header is added if appropriate.
|
jpayne@68
|
1205 #
|
jpayne@68
|
1206 # @param host Host descriptor (URL or (URL, x509 info) tuple).
|
jpayne@68
|
1207 # @return A 3-tuple containing (actual host, extra headers,
|
jpayne@68
|
1208 # x509 info). The header and x509 fields may be None.
|
jpayne@68
|
1209
|
jpayne@68
|
1210 def get_host_info(self, host):
|
jpayne@68
|
1211
|
jpayne@68
|
1212 x509 = {}
|
jpayne@68
|
1213 if isinstance(host, tuple):
|
jpayne@68
|
1214 host, x509 = host
|
jpayne@68
|
1215
|
jpayne@68
|
1216 auth, host = urllib.parse._splituser(host)
|
jpayne@68
|
1217
|
jpayne@68
|
1218 if auth:
|
jpayne@68
|
1219 auth = urllib.parse.unquote_to_bytes(auth)
|
jpayne@68
|
1220 auth = base64.encodebytes(auth).decode("utf-8")
|
jpayne@68
|
1221 auth = "".join(auth.split()) # get rid of whitespace
|
jpayne@68
|
1222 extra_headers = [
|
jpayne@68
|
1223 ("Authorization", "Basic " + auth)
|
jpayne@68
|
1224 ]
|
jpayne@68
|
1225 else:
|
jpayne@68
|
1226 extra_headers = []
|
jpayne@68
|
1227
|
jpayne@68
|
1228 return host, extra_headers, x509
|
jpayne@68
|
1229
|
jpayne@68
|
1230 ##
|
jpayne@68
|
1231 # Connect to server.
|
jpayne@68
|
1232 #
|
jpayne@68
|
1233 # @param host Target host.
|
jpayne@68
|
1234 # @return An HTTPConnection object
|
jpayne@68
|
1235
|
jpayne@68
|
1236 def make_connection(self, host):
|
jpayne@68
|
1237 #return an existing connection if possible. This allows
|
jpayne@68
|
1238 #HTTP/1.1 keep-alive.
|
jpayne@68
|
1239 if self._connection and host == self._connection[0]:
|
jpayne@68
|
1240 return self._connection[1]
|
jpayne@68
|
1241 # create a HTTP connection object from a host descriptor
|
jpayne@68
|
1242 chost, self._extra_headers, x509 = self.get_host_info(host)
|
jpayne@68
|
1243 self._connection = host, http.client.HTTPConnection(chost)
|
jpayne@68
|
1244 return self._connection[1]
|
jpayne@68
|
1245
|
jpayne@68
|
1246 ##
|
jpayne@68
|
1247 # Clear any cached connection object.
|
jpayne@68
|
1248 # Used in the event of socket errors.
|
jpayne@68
|
1249 #
|
jpayne@68
|
1250 def close(self):
|
jpayne@68
|
1251 host, connection = self._connection
|
jpayne@68
|
1252 if connection:
|
jpayne@68
|
1253 self._connection = (None, None)
|
jpayne@68
|
1254 connection.close()
|
jpayne@68
|
1255
|
jpayne@68
|
1256 ##
|
jpayne@68
|
1257 # Send HTTP request.
|
jpayne@68
|
1258 #
|
jpayne@68
|
1259 # @param host Host descriptor (URL or (URL, x509 info) tuple).
|
jpayne@68
|
1260 # @param handler Target RPC handler (a path relative to host)
|
jpayne@68
|
1261 # @param request_body The XML-RPC request body
|
jpayne@68
|
1262 # @param debug Enable debugging if debug is true.
|
jpayne@68
|
1263 # @return An HTTPConnection.
|
jpayne@68
|
1264
|
jpayne@68
|
1265 def send_request(self, host, handler, request_body, debug):
|
jpayne@68
|
1266 connection = self.make_connection(host)
|
jpayne@68
|
1267 headers = self._headers + self._extra_headers
|
jpayne@68
|
1268 if debug:
|
jpayne@68
|
1269 connection.set_debuglevel(1)
|
jpayne@68
|
1270 if self.accept_gzip_encoding and gzip:
|
jpayne@68
|
1271 connection.putrequest("POST", handler, skip_accept_encoding=True)
|
jpayne@68
|
1272 headers.append(("Accept-Encoding", "gzip"))
|
jpayne@68
|
1273 else:
|
jpayne@68
|
1274 connection.putrequest("POST", handler)
|
jpayne@68
|
1275 headers.append(("Content-Type", "text/xml"))
|
jpayne@68
|
1276 headers.append(("User-Agent", self.user_agent))
|
jpayne@68
|
1277 self.send_headers(connection, headers)
|
jpayne@68
|
1278 self.send_content(connection, request_body)
|
jpayne@68
|
1279 return connection
|
jpayne@68
|
1280
|
jpayne@68
|
1281 ##
|
jpayne@68
|
1282 # Send request headers.
|
jpayne@68
|
1283 # This function provides a useful hook for subclassing
|
jpayne@68
|
1284 #
|
jpayne@68
|
1285 # @param connection httpConnection.
|
jpayne@68
|
1286 # @param headers list of key,value pairs for HTTP headers
|
jpayne@68
|
1287
|
jpayne@68
|
1288 def send_headers(self, connection, headers):
|
jpayne@68
|
1289 for key, val in headers:
|
jpayne@68
|
1290 connection.putheader(key, val)
|
jpayne@68
|
1291
|
jpayne@68
|
1292 ##
|
jpayne@68
|
1293 # Send request body.
|
jpayne@68
|
1294 # This function provides a useful hook for subclassing
|
jpayne@68
|
1295 #
|
jpayne@68
|
1296 # @param connection httpConnection.
|
jpayne@68
|
1297 # @param request_body XML-RPC request body.
|
jpayne@68
|
1298
|
jpayne@68
|
1299 def send_content(self, connection, request_body):
|
jpayne@68
|
1300 #optionally encode the request
|
jpayne@68
|
1301 if (self.encode_threshold is not None and
|
jpayne@68
|
1302 self.encode_threshold < len(request_body) and
|
jpayne@68
|
1303 gzip):
|
jpayne@68
|
1304 connection.putheader("Content-Encoding", "gzip")
|
jpayne@68
|
1305 request_body = gzip_encode(request_body)
|
jpayne@68
|
1306
|
jpayne@68
|
1307 connection.putheader("Content-Length", str(len(request_body)))
|
jpayne@68
|
1308 connection.endheaders(request_body)
|
jpayne@68
|
1309
|
jpayne@68
|
1310 ##
|
jpayne@68
|
1311 # Parse response.
|
jpayne@68
|
1312 #
|
jpayne@68
|
1313 # @param file Stream.
|
jpayne@68
|
1314 # @return Response tuple and target method.
|
jpayne@68
|
1315
|
jpayne@68
|
1316 def parse_response(self, response):
|
jpayne@68
|
1317 # read response data from httpresponse, and parse it
|
jpayne@68
|
1318 # Check for new http response object, otherwise it is a file object.
|
jpayne@68
|
1319 if hasattr(response, 'getheader'):
|
jpayne@68
|
1320 if response.getheader("Content-Encoding", "") == "gzip":
|
jpayne@68
|
1321 stream = GzipDecodedResponse(response)
|
jpayne@68
|
1322 else:
|
jpayne@68
|
1323 stream = response
|
jpayne@68
|
1324 else:
|
jpayne@68
|
1325 stream = response
|
jpayne@68
|
1326
|
jpayne@68
|
1327 p, u = self.getparser()
|
jpayne@68
|
1328
|
jpayne@68
|
1329 while 1:
|
jpayne@68
|
1330 data = stream.read(1024)
|
jpayne@68
|
1331 if not data:
|
jpayne@68
|
1332 break
|
jpayne@68
|
1333 if self.verbose:
|
jpayne@68
|
1334 print("body:", repr(data))
|
jpayne@68
|
1335 p.feed(data)
|
jpayne@68
|
1336
|
jpayne@68
|
1337 if stream is not response:
|
jpayne@68
|
1338 stream.close()
|
jpayne@68
|
1339 p.close()
|
jpayne@68
|
1340
|
jpayne@68
|
1341 return u.close()
|
jpayne@68
|
1342
|
jpayne@68
|
1343 ##
|
jpayne@68
|
1344 # Standard transport class for XML-RPC over HTTPS.
|
jpayne@68
|
1345
|
jpayne@68
|
1346 class SafeTransport(Transport):
|
jpayne@68
|
1347 """Handles an HTTPS transaction to an XML-RPC server."""
|
jpayne@68
|
1348
|
jpayne@68
|
1349 def __init__(self, use_datetime=False, use_builtin_types=False,
|
jpayne@68
|
1350 *, headers=(), context=None):
|
jpayne@68
|
1351 super().__init__(use_datetime=use_datetime,
|
jpayne@68
|
1352 use_builtin_types=use_builtin_types,
|
jpayne@68
|
1353 headers=headers)
|
jpayne@68
|
1354 self.context = context
|
jpayne@68
|
1355
|
jpayne@68
|
1356 # FIXME: mostly untested
|
jpayne@68
|
1357
|
jpayne@68
|
1358 def make_connection(self, host):
|
jpayne@68
|
1359 if self._connection and host == self._connection[0]:
|
jpayne@68
|
1360 return self._connection[1]
|
jpayne@68
|
1361
|
jpayne@68
|
1362 if not hasattr(http.client, "HTTPSConnection"):
|
jpayne@68
|
1363 raise NotImplementedError(
|
jpayne@68
|
1364 "your version of http.client doesn't support HTTPS")
|
jpayne@68
|
1365 # create a HTTPS connection object from a host descriptor
|
jpayne@68
|
1366 # host may be a string, or a (host, x509-dict) tuple
|
jpayne@68
|
1367 chost, self._extra_headers, x509 = self.get_host_info(host)
|
jpayne@68
|
1368 self._connection = host, http.client.HTTPSConnection(chost,
|
jpayne@68
|
1369 None, context=self.context, **(x509 or {}))
|
jpayne@68
|
1370 return self._connection[1]
|
jpayne@68
|
1371
|
jpayne@68
|
1372 ##
|
jpayne@68
|
1373 # Standard server proxy. This class establishes a virtual connection
|
jpayne@68
|
1374 # to an XML-RPC server.
|
jpayne@68
|
1375 # <p>
|
jpayne@68
|
1376 # This class is available as ServerProxy and Server. New code should
|
jpayne@68
|
1377 # use ServerProxy, to avoid confusion.
|
jpayne@68
|
1378 #
|
jpayne@68
|
1379 # @def ServerProxy(uri, **options)
|
jpayne@68
|
1380 # @param uri The connection point on the server.
|
jpayne@68
|
1381 # @keyparam transport A transport factory, compatible with the
|
jpayne@68
|
1382 # standard transport class.
|
jpayne@68
|
1383 # @keyparam encoding The default encoding used for 8-bit strings
|
jpayne@68
|
1384 # (default is UTF-8).
|
jpayne@68
|
1385 # @keyparam verbose Use a true value to enable debugging output.
|
jpayne@68
|
1386 # (printed to standard output).
|
jpayne@68
|
1387 # @see Transport
|
jpayne@68
|
1388
|
jpayne@68
|
1389 class ServerProxy:
|
jpayne@68
|
1390 """uri [,options] -> a logical connection to an XML-RPC server
|
jpayne@68
|
1391
|
jpayne@68
|
1392 uri is the connection point on the server, given as
|
jpayne@68
|
1393 scheme://host/target.
|
jpayne@68
|
1394
|
jpayne@68
|
1395 The standard implementation always supports the "http" scheme. If
|
jpayne@68
|
1396 SSL socket support is available (Python 2.0), it also supports
|
jpayne@68
|
1397 "https".
|
jpayne@68
|
1398
|
jpayne@68
|
1399 If the target part and the slash preceding it are both omitted,
|
jpayne@68
|
1400 "/RPC2" is assumed.
|
jpayne@68
|
1401
|
jpayne@68
|
1402 The following options can be given as keyword arguments:
|
jpayne@68
|
1403
|
jpayne@68
|
1404 transport: a transport factory
|
jpayne@68
|
1405 encoding: the request encoding (default is UTF-8)
|
jpayne@68
|
1406
|
jpayne@68
|
1407 All 8-bit strings passed to the server proxy are assumed to use
|
jpayne@68
|
1408 the given encoding.
|
jpayne@68
|
1409 """
|
jpayne@68
|
1410
|
jpayne@68
|
1411 def __init__(self, uri, transport=None, encoding=None, verbose=False,
|
jpayne@68
|
1412 allow_none=False, use_datetime=False, use_builtin_types=False,
|
jpayne@68
|
1413 *, headers=(), context=None):
|
jpayne@68
|
1414 # establish a "logical" server connection
|
jpayne@68
|
1415
|
jpayne@68
|
1416 # get the url
|
jpayne@68
|
1417 type, uri = urllib.parse._splittype(uri)
|
jpayne@68
|
1418 if type not in ("http", "https"):
|
jpayne@68
|
1419 raise OSError("unsupported XML-RPC protocol")
|
jpayne@68
|
1420 self.__host, self.__handler = urllib.parse._splithost(uri)
|
jpayne@68
|
1421 if not self.__handler:
|
jpayne@68
|
1422 self.__handler = "/RPC2"
|
jpayne@68
|
1423
|
jpayne@68
|
1424 if transport is None:
|
jpayne@68
|
1425 if type == "https":
|
jpayne@68
|
1426 handler = SafeTransport
|
jpayne@68
|
1427 extra_kwargs = {"context": context}
|
jpayne@68
|
1428 else:
|
jpayne@68
|
1429 handler = Transport
|
jpayne@68
|
1430 extra_kwargs = {}
|
jpayne@68
|
1431 transport = handler(use_datetime=use_datetime,
|
jpayne@68
|
1432 use_builtin_types=use_builtin_types,
|
jpayne@68
|
1433 headers=headers,
|
jpayne@68
|
1434 **extra_kwargs)
|
jpayne@68
|
1435 self.__transport = transport
|
jpayne@68
|
1436
|
jpayne@68
|
1437 self.__encoding = encoding or 'utf-8'
|
jpayne@68
|
1438 self.__verbose = verbose
|
jpayne@68
|
1439 self.__allow_none = allow_none
|
jpayne@68
|
1440
|
jpayne@68
|
1441 def __close(self):
|
jpayne@68
|
1442 self.__transport.close()
|
jpayne@68
|
1443
|
jpayne@68
|
1444 def __request(self, methodname, params):
|
jpayne@68
|
1445 # call a method on the remote server
|
jpayne@68
|
1446
|
jpayne@68
|
1447 request = dumps(params, methodname, encoding=self.__encoding,
|
jpayne@68
|
1448 allow_none=self.__allow_none).encode(self.__encoding, 'xmlcharrefreplace')
|
jpayne@68
|
1449
|
jpayne@68
|
1450 response = self.__transport.request(
|
jpayne@68
|
1451 self.__host,
|
jpayne@68
|
1452 self.__handler,
|
jpayne@68
|
1453 request,
|
jpayne@68
|
1454 verbose=self.__verbose
|
jpayne@68
|
1455 )
|
jpayne@68
|
1456
|
jpayne@68
|
1457 if len(response) == 1:
|
jpayne@68
|
1458 response = response[0]
|
jpayne@68
|
1459
|
jpayne@68
|
1460 return response
|
jpayne@68
|
1461
|
jpayne@68
|
1462 def __repr__(self):
|
jpayne@68
|
1463 return (
|
jpayne@68
|
1464 "<%s for %s%s>" %
|
jpayne@68
|
1465 (self.__class__.__name__, self.__host, self.__handler)
|
jpayne@68
|
1466 )
|
jpayne@68
|
1467
|
jpayne@68
|
1468 def __getattr__(self, name):
|
jpayne@68
|
1469 # magic method dispatcher
|
jpayne@68
|
1470 return _Method(self.__request, name)
|
jpayne@68
|
1471
|
jpayne@68
|
1472 # note: to call a remote object with a non-standard name, use
|
jpayne@68
|
1473 # result getattr(server, "strange-python-name")(args)
|
jpayne@68
|
1474
|
jpayne@68
|
1475 def __call__(self, attr):
|
jpayne@68
|
1476 """A workaround to get special attributes on the ServerProxy
|
jpayne@68
|
1477 without interfering with the magic __getattr__
|
jpayne@68
|
1478 """
|
jpayne@68
|
1479 if attr == "close":
|
jpayne@68
|
1480 return self.__close
|
jpayne@68
|
1481 elif attr == "transport":
|
jpayne@68
|
1482 return self.__transport
|
jpayne@68
|
1483 raise AttributeError("Attribute %r not found" % (attr,))
|
jpayne@68
|
1484
|
jpayne@68
|
1485 def __enter__(self):
|
jpayne@68
|
1486 return self
|
jpayne@68
|
1487
|
jpayne@68
|
1488 def __exit__(self, *args):
|
jpayne@68
|
1489 self.__close()
|
jpayne@68
|
1490
|
jpayne@68
|
1491 # compatibility
|
jpayne@68
|
1492
|
jpayne@68
|
1493 Server = ServerProxy
|
jpayne@68
|
1494
|
jpayne@68
|
1495 # --------------------------------------------------------------------
|
jpayne@68
|
1496 # test code
|
jpayne@68
|
1497
|
jpayne@68
|
1498 if __name__ == "__main__":
|
jpayne@68
|
1499
|
jpayne@68
|
1500 # simple test program (from the XML-RPC specification)
|
jpayne@68
|
1501
|
jpayne@68
|
1502 # local server, available from Lib/xmlrpc/server.py
|
jpayne@68
|
1503 server = ServerProxy("http://localhost:8000")
|
jpayne@68
|
1504
|
jpayne@68
|
1505 try:
|
jpayne@68
|
1506 print(server.currentTime.getCurrentTime())
|
jpayne@68
|
1507 except Error as v:
|
jpayne@68
|
1508 print("ERROR", v)
|
jpayne@68
|
1509
|
jpayne@68
|
1510 multi = MultiCall(server)
|
jpayne@68
|
1511 multi.getData()
|
jpayne@68
|
1512 multi.pow(2,9)
|
jpayne@68
|
1513 multi.add(1,2)
|
jpayne@68
|
1514 try:
|
jpayne@68
|
1515 for response in multi():
|
jpayne@68
|
1516 print(response)
|
jpayne@68
|
1517 except Error as v:
|
jpayne@68
|
1518 print("ERROR", v)
|