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