Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | # |
2 | # XML-RPC CLIENT LIBRARY | |
3 | # $Id: xmlrpclib.py,v 1.36.2.1 2005/02/11 17:59:58 fdrake Exp $ | |
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 | # things to look into some day: | |
90 | ||
91 | # TODO: sort out True/False/boolean issues for Python 2.3 | |
92 | ||
93 | """ | |
94 | An XML-RPC client interface for Python. | |
95 | ||
96 | The marshalling and response parser code can also be used to | |
97 | implement XML-RPC servers. | |
98 | ||
99 | Exported exceptions: | |
100 | ||
101 | Error Base class for client errors | |
102 | ProtocolError Indicates an HTTP protocol error | |
103 | ResponseError Indicates a broken response package | |
104 | Fault Indicates an XML-RPC fault package | |
105 | ||
106 | Exported classes: | |
107 | ||
108 | ServerProxy Represents a logical connection to an XML-RPC server | |
109 | ||
110 | MultiCall Executor of boxcared xmlrpc requests | |
111 | Boolean boolean wrapper to generate a "boolean" XML-RPC value | |
112 | DateTime dateTime wrapper for an ISO 8601 string or time tuple or | |
113 | localtime integer value to generate a "dateTime.iso8601" | |
114 | XML-RPC value | |
115 | Binary binary data wrapper | |
116 | ||
117 | SlowParser Slow but safe standard parser (based on xmllib) | |
118 | Marshaller Generate an XML-RPC params chunk from a Python data structure | |
119 | Unmarshaller Unmarshal an XML-RPC response from incoming XML event message | |
120 | Transport Handles an HTTP transaction to an XML-RPC server | |
121 | SafeTransport Handles an HTTPS transaction to an XML-RPC server | |
122 | ||
123 | Exported constants: | |
124 | ||
125 | True | |
126 | False | |
127 | ||
128 | Exported functions: | |
129 | ||
130 | boolean Convert any Python value to an XML-RPC boolean | |
131 | getparser Create instance of the fastest available parser & attach | |
132 | to an unmarshalling object | |
133 | dumps Convert an argument tuple or a Fault instance to an XML-RPC | |
134 | request (or response, if the methodresponse option is used). | |
135 | loads Convert an XML-RPC packet to unmarshalled data plus a method | |
136 | name (None if not present). | |
137 | """ | |
138 | ||
139 | import re, string, time, operator | |
140 | ||
141 | from types import * | |
142 | ||
143 | # -------------------------------------------------------------------- | |
144 | # Internal stuff | |
145 | ||
146 | try: | |
147 | unicode | |
148 | except NameError: | |
149 | unicode = None # unicode support not available | |
150 | ||
151 | try: | |
152 | _bool_is_builtin = False.__class__.__name__ == "bool" | |
153 | except NameError: | |
154 | _bool_is_builtin = 0 | |
155 | ||
156 | def _decode(data, encoding, is8bit=re.compile("[\x80-\xff]").search): | |
157 | # decode non-ascii string (if possible) | |
158 | if unicode and encoding and is8bit(data): | |
159 | data = unicode(data, encoding) | |
160 | return data | |
161 | ||
162 | def escape(s, replace=string.replace): | |
163 | s = replace(s, "&", "&") | |
164 | s = replace(s, "<", "<") | |
165 | return replace(s, ">", ">",) | |
166 | ||
167 | if unicode: | |
168 | def _stringify(string): | |
169 | # convert to 7-bit ascii if possible | |
170 | try: | |
171 | return string.encode("ascii") | |
172 | except UnicodeError: | |
173 | return string | |
174 | else: | |
175 | def _stringify(string): | |
176 | return string | |
177 | ||
178 | __version__ = "1.0.1" | |
179 | ||
180 | # xmlrpc integer limits | |
181 | MAXINT = 2L**31-1 | |
182 | MININT = -2L**31 | |
183 | ||
184 | # -------------------------------------------------------------------- | |
185 | # Error constants (from Dan Libby's specification at | |
186 | # http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php) | |
187 | ||
188 | # Ranges of errors | |
189 | PARSE_ERROR = -32700 | |
190 | SERVER_ERROR = -32600 | |
191 | APPLICATION_ERROR = -32500 | |
192 | SYSTEM_ERROR = -32400 | |
193 | TRANSPORT_ERROR = -32300 | |
194 | ||
195 | # Specific errors | |
196 | NOT_WELLFORMED_ERROR = -32700 | |
197 | UNSUPPORTED_ENCODING = -32701 | |
198 | INVALID_ENCODING_CHAR = -32702 | |
199 | INVALID_XMLRPC = -32600 | |
200 | METHOD_NOT_FOUND = -32601 | |
201 | INVALID_METHOD_PARAMS = -32602 | |
202 | INTERNAL_ERROR = -32603 | |
203 | ||
204 | # -------------------------------------------------------------------- | |
205 | # Exceptions | |
206 | ||
207 | ## | |
208 | # Base class for all kinds of client-side errors. | |
209 | ||
210 | class Error(Exception): | |
211 | """Base class for client errors.""" | |
212 | def __str__(self): | |
213 | return repr(self) | |
214 | ||
215 | ## | |
216 | # Indicates an HTTP-level protocol error. This is raised by the HTTP | |
217 | # transport layer, if the server returns an error code other than 200 | |
218 | # (OK). | |
219 | # | |
220 | # @param url The target URL. | |
221 | # @param errcode The HTTP error code. | |
222 | # @param errmsg The HTTP error message. | |
223 | # @param headers The HTTP header dictionary. | |
224 | ||
225 | class ProtocolError(Error): | |
226 | """Indicates an HTTP protocol error.""" | |
227 | def __init__(self, url, errcode, errmsg, headers): | |
228 | Error.__init__(self) | |
229 | self.url = url | |
230 | self.errcode = errcode | |
231 | self.errmsg = errmsg | |
232 | self.headers = headers | |
233 | def __repr__(self): | |
234 | return ( | |
235 | "<ProtocolError for %s: %s %s>" % | |
236 | (self.url, self.errcode, self.errmsg) | |
237 | ) | |
238 | ||
239 | ## | |
240 | # Indicates a broken XML-RPC response package. This exception is | |
241 | # raised by the unmarshalling layer, if the XML-RPC response is | |
242 | # malformed. | |
243 | ||
244 | class ResponseError(Error): | |
245 | """Indicates a broken response package.""" | |
246 | pass | |
247 | ||
248 | ## | |
249 | # Indicates an XML-RPC fault response package. This exception is | |
250 | # raised by the unmarshalling layer, if the XML-RPC response contains | |
251 | # a fault string. This exception can also used as a class, to | |
252 | # generate a fault XML-RPC message. | |
253 | # | |
254 | # @param faultCode The XML-RPC fault code. | |
255 | # @param faultString The XML-RPC fault string. | |
256 | ||
257 | class Fault(Error): | |
258 | """Indicates an XML-RPC fault package.""" | |
259 | def __init__(self, faultCode, faultString, **extra): | |
260 | Error.__init__(self) | |
261 | self.faultCode = faultCode | |
262 | self.faultString = faultString | |
263 | def __repr__(self): | |
264 | return ( | |
265 | "<Fault %s: %s>" % | |
266 | (self.faultCode, repr(self.faultString)) | |
267 | ) | |
268 | ||
269 | # -------------------------------------------------------------------- | |
270 | # Special values | |
271 | ||
272 | ## | |
273 | # Wrapper for XML-RPC boolean values. Use the xmlrpclib.True and | |
274 | # xmlrpclib.False constants, or the xmlrpclib.boolean() function, to | |
275 | # generate boolean XML-RPC values. | |
276 | # | |
277 | # @param value A boolean value. Any true value is interpreted as True, | |
278 | # all other values are interpreted as False. | |
279 | ||
280 | if _bool_is_builtin: | |
281 | boolean = Boolean = bool | |
282 | # to avoid breaking code which references xmlrpclib.{True,False} | |
283 | True, False = True, False | |
284 | else: | |
285 | class Boolean: | |
286 | """Boolean-value wrapper. | |
287 | ||
288 | Use True or False to generate a "boolean" XML-RPC value. | |
289 | """ | |
290 | ||
291 | def __init__(self, value = 0): | |
292 | self.value = operator.truth(value) | |
293 | ||
294 | def encode(self, out): | |
295 | out.write("<value><boolean>%d</boolean></value>\n" % self.value) | |
296 | ||
297 | def __cmp__(self, other): | |
298 | if isinstance(other, Boolean): | |
299 | other = other.value | |
300 | return cmp(self.value, other) | |
301 | ||
302 | def __repr__(self): | |
303 | if self.value: | |
304 | return "<Boolean True at %x>" % id(self) | |
305 | else: | |
306 | return "<Boolean False at %x>" % id(self) | |
307 | ||
308 | def __int__(self): | |
309 | return self.value | |
310 | ||
311 | def __nonzero__(self): | |
312 | return self.value | |
313 | ||
314 | True, False = Boolean(1), Boolean(0) | |
315 | ||
316 | ## | |
317 | # Map true or false value to XML-RPC boolean values. | |
318 | # | |
319 | # @def boolean(value) | |
320 | # @param value A boolean value. Any true value is mapped to True, | |
321 | # all other values are mapped to False. | |
322 | # @return xmlrpclib.True or xmlrpclib.False. | |
323 | # @see Boolean | |
324 | # @see True | |
325 | # @see False | |
326 | ||
327 | def boolean(value, _truefalse=(False, True)): | |
328 | """Convert any Python value to XML-RPC 'boolean'.""" | |
329 | return _truefalse[operator.truth(value)] | |
330 | ||
331 | ## | |
332 | # Wrapper for XML-RPC DateTime values. This converts a time value to | |
333 | # the format used by XML-RPC. | |
334 | # <p> | |
335 | # The value can be given as a string in the format | |
336 | # "yyyymmddThh:mm:ss", as a 9-item time tuple (as returned by | |
337 | # time.localtime()), or an integer value (as returned by time.time()). | |
338 | # The wrapper uses time.localtime() to convert an integer to a time | |
339 | # tuple. | |
340 | # | |
341 | # @param value The time, given as an ISO 8601 string, a time | |
342 | # tuple, or a integer time value. | |
343 | ||
344 | class DateTime: | |
345 | """DateTime wrapper for an ISO 8601 string or time tuple or | |
346 | localtime integer value to generate 'dateTime.iso8601' XML-RPC | |
347 | value. | |
348 | """ | |
349 | ||
350 | def __init__(self, value=0): | |
351 | if not isinstance(value, StringType): | |
352 | if not isinstance(value, (TupleType, time.struct_time)): | |
353 | if value == 0: | |
354 | value = time.time() | |
355 | value = time.localtime(value) | |
356 | value = time.strftime("%Y%m%dT%H:%M:%S", value) | |
357 | self.value = value | |
358 | ||
359 | def __cmp__(self, other): | |
360 | if isinstance(other, DateTime): | |
361 | other = other.value | |
362 | return cmp(self.value, other) | |
363 | ||
364 | ## | |
365 | # Get date/time value. | |
366 | # | |
367 | # @return Date/time value, as an ISO 8601 string. | |
368 | ||
369 | def __str__(self): | |
370 | return self.value | |
371 | ||
372 | def __repr__(self): | |
373 | return "<DateTime %s at %x>" % (repr(self.value), id(self)) | |
374 | ||
375 | def decode(self, data): | |
376 | self.value = string.strip(data) | |
377 | ||
378 | def encode(self, out): | |
379 | out.write("<value><dateTime.iso8601>") | |
380 | out.write(self.value) | |
381 | out.write("</dateTime.iso8601></value>\n") | |
382 | ||
383 | def _datetime(data): | |
384 | # decode xml element contents into a DateTime structure. | |
385 | value = DateTime() | |
386 | value.decode(data) | |
387 | return value | |
388 | ||
389 | ## | |
390 | # Wrapper for binary data. This can be used to transport any kind | |
391 | # of binary data over XML-RPC, using BASE64 encoding. | |
392 | # | |
393 | # @param data An 8-bit string containing arbitrary data. | |
394 | ||
395 | import base64 | |
396 | try: | |
397 | import cStringIO as StringIO | |
398 | except ImportError: | |
399 | import StringIO | |
400 | ||
401 | class Binary: | |
402 | """Wrapper for binary data.""" | |
403 | ||
404 | def __init__(self, data=None): | |
405 | self.data = data | |
406 | ||
407 | ## | |
408 | # Get buffer contents. | |
409 | # | |
410 | # @return Buffer contents, as an 8-bit string. | |
411 | ||
412 | def __str__(self): | |
413 | return self.data or "" | |
414 | ||
415 | def __cmp__(self, other): | |
416 | if isinstance(other, Binary): | |
417 | other = other.data | |
418 | return cmp(self.data, other) | |
419 | ||
420 | def decode(self, data): | |
421 | self.data = base64.decodestring(data) | |
422 | ||
423 | def encode(self, out): | |
424 | out.write("<value><base64>\n") | |
425 | base64.encode(StringIO.StringIO(self.data), out) | |
426 | out.write("</base64></value>\n") | |
427 | ||
428 | def _binary(data): | |
429 | # decode xml element contents into a Binary structure | |
430 | value = Binary() | |
431 | value.decode(data) | |
432 | return value | |
433 | ||
434 | WRAPPERS = (DateTime, Binary) | |
435 | if not _bool_is_builtin: | |
436 | WRAPPERS = WRAPPERS + (Boolean,) | |
437 | ||
438 | # -------------------------------------------------------------------- | |
439 | # XML parsers | |
440 | ||
441 | try: | |
442 | # optional xmlrpclib accelerator | |
443 | import _xmlrpclib | |
444 | FastParser = _xmlrpclib.Parser | |
445 | FastUnmarshaller = _xmlrpclib.Unmarshaller | |
446 | except (AttributeError, ImportError): | |
447 | FastParser = FastUnmarshaller = None | |
448 | ||
449 | try: | |
450 | import _xmlrpclib | |
451 | FastMarshaller = _xmlrpclib.Marshaller | |
452 | except (AttributeError, ImportError): | |
453 | FastMarshaller = None | |
454 | ||
455 | # | |
456 | # the SGMLOP parser is about 15x faster than Python's builtin | |
457 | # XML parser. SGMLOP sources can be downloaded from: | |
458 | # | |
459 | # http://www.pythonware.com/products/xml/sgmlop.htm | |
460 | # | |
461 | ||
462 | try: | |
463 | import sgmlop | |
464 | if not hasattr(sgmlop, "XMLParser"): | |
465 | raise ImportError | |
466 | except ImportError: | |
467 | SgmlopParser = None # sgmlop accelerator not available | |
468 | else: | |
469 | class SgmlopParser: | |
470 | def __init__(self, target): | |
471 | ||
472 | # setup callbacks | |
473 | self.finish_starttag = target.start | |
474 | self.finish_endtag = target.end | |
475 | self.handle_data = target.data | |
476 | self.handle_xml = target.xml | |
477 | ||
478 | # activate parser | |
479 | self.parser = sgmlop.XMLParser() | |
480 | self.parser.register(self) | |
481 | self.feed = self.parser.feed | |
482 | self.entity = { | |
483 | "amp": "&", "gt": ">", "lt": "<", | |
484 | "apos": "'", "quot": '"' | |
485 | } | |
486 | ||
487 | def close(self): | |
488 | try: | |
489 | self.parser.close() | |
490 | finally: | |
491 | self.parser = self.feed = None # nuke circular reference | |
492 | ||
493 | def handle_proc(self, tag, attr): | |
494 | m = re.search("encoding\s*=\s*['\"]([^\"']+)[\"']", attr) | |
495 | if m: | |
496 | self.handle_xml(m.group(1), 1) | |
497 | ||
498 | def handle_entityref(self, entity): | |
499 | # <string> entity | |
500 | try: | |
501 | self.handle_data(self.entity[entity]) | |
502 | except KeyError: | |
503 | self.handle_data("&%s;" % entity) | |
504 | ||
505 | try: | |
506 | from xml.parsers import expat | |
507 | if not hasattr(expat, "ParserCreate"): | |
508 | raise ImportError | |
509 | except ImportError: | |
510 | ExpatParser = None # expat not available | |
511 | else: | |
512 | class ExpatParser: | |
513 | # fast expat parser for Python 2.0 and later. this is about | |
514 | # 50% slower than sgmlop, on roundtrip testing | |
515 | def __init__(self, target): | |
516 | self._parser = parser = expat.ParserCreate(None, None) | |
517 | self._target = target | |
518 | parser.StartElementHandler = target.start | |
519 | parser.EndElementHandler = target.end | |
520 | parser.CharacterDataHandler = target.data | |
521 | encoding = None | |
522 | if not parser.returns_unicode: | |
523 | encoding = "utf-8" | |
524 | target.xml(encoding, None) | |
525 | ||
526 | def feed(self, data): | |
527 | self._parser.Parse(data, 0) | |
528 | ||
529 | def close(self): | |
530 | self._parser.Parse("", 1) # end of data | |
531 | del self._target, self._parser # get rid of circular references | |
532 | ||
533 | class SlowParser: | |
534 | """Default XML parser (based on xmllib.XMLParser).""" | |
535 | # this is about 10 times slower than sgmlop, on roundtrip | |
536 | # testing. | |
537 | def __init__(self, target): | |
538 | import xmllib # lazy subclassing (!) | |
539 | if xmllib.XMLParser not in SlowParser.__bases__: | |
540 | SlowParser.__bases__ = (xmllib.XMLParser,) | |
541 | self.handle_xml = target.xml | |
542 | self.unknown_starttag = target.start | |
543 | self.handle_data = target.data | |
544 | self.handle_cdata = target.data | |
545 | self.unknown_endtag = target.end | |
546 | try: | |
547 | xmllib.XMLParser.__init__(self, accept_utf8=1) | |
548 | except TypeError: | |
549 | xmllib.XMLParser.__init__(self) # pre-2.0 | |
550 | ||
551 | # -------------------------------------------------------------------- | |
552 | # XML-RPC marshalling and unmarshalling code | |
553 | ||
554 | ## | |
555 | # XML-RPC marshaller. | |
556 | # | |
557 | # @param encoding Default encoding for 8-bit strings. The default | |
558 | # value is None (interpreted as UTF-8). | |
559 | # @see dumps | |
560 | ||
561 | class Marshaller: | |
562 | """Generate an XML-RPC params chunk from a Python data structure. | |
563 | ||
564 | Create a Marshaller instance for each set of parameters, and use | |
565 | the "dumps" method to convert your data (represented as a tuple) | |
566 | to an XML-RPC params chunk. To write a fault response, pass a | |
567 | Fault instance instead. You may prefer to use the "dumps" module | |
568 | function for this purpose. | |
569 | """ | |
570 | ||
571 | # by the way, if you don't understand what's going on in here, | |
572 | # that's perfectly ok. | |
573 | ||
574 | def __init__(self, encoding=None, allow_none=0): | |
575 | self.memo = {} | |
576 | self.data = None | |
577 | self.encoding = encoding | |
578 | self.allow_none = allow_none | |
579 | ||
580 | dispatch = {} | |
581 | ||
582 | def dumps(self, values): | |
583 | out = [] | |
584 | write = out.append | |
585 | dump = self.__dump | |
586 | if isinstance(values, Fault): | |
587 | # fault instance | |
588 | write("<fault>\n") | |
589 | dump({'faultCode': values.faultCode, | |
590 | 'faultString': values.faultString}, | |
591 | write) | |
592 | write("</fault>\n") | |
593 | else: | |
594 | # parameter block | |
595 | # FIXME: the xml-rpc specification allows us to leave out | |
596 | # the entire <params> block if there are no parameters. | |
597 | # however, changing this may break older code (including | |
598 | # old versions of xmlrpclib.py), so this is better left as | |
599 | # is for now. See @XMLRPC3 for more information. /F | |
600 | write("<params>\n") | |
601 | for v in values: | |
602 | write("<param>\n") | |
603 | dump(v, write) | |
604 | write("</param>\n") | |
605 | write("</params>\n") | |
606 | result = string.join(out, "") | |
607 | return result | |
608 | ||
609 | def __dump(self, value, write): | |
610 | try: | |
611 | f = self.dispatch[type(value)] | |
612 | except KeyError: | |
613 | raise TypeError, "cannot marshal %s objects" % type(value) | |
614 | else: | |
615 | f(self, value, write) | |
616 | ||
617 | def dump_nil (self, value, write): | |
618 | if not self.allow_none: | |
619 | raise TypeError, "cannot marshal None unless allow_none is enabled" | |
620 | write("<value><nil/></value>") | |
621 | dispatch[NoneType] = dump_nil | |
622 | ||
623 | def dump_int(self, value, write): | |
624 | # in case ints are > 32 bits | |
625 | if value > MAXINT or value < MININT: | |
626 | raise OverflowError, "int exceeds XML-RPC limits" | |
627 | write("<value><int>") | |
628 | write(str(value)) | |
629 | write("</int></value>\n") | |
630 | dispatch[IntType] = dump_int | |
631 | ||
632 | if _bool_is_builtin: | |
633 | def dump_bool(self, value, write): | |
634 | write("<value><boolean>") | |
635 | write(value and "1" or "0") | |
636 | write("</boolean></value>\n") | |
637 | dispatch[bool] = dump_bool | |
638 | ||
639 | def dump_long(self, value, write): | |
640 | if value > MAXINT or value < MININT: | |
641 | raise OverflowError, "long int exceeds XML-RPC limits" | |
642 | write("<value><int>") | |
643 | write(str(int(value))) | |
644 | write("</int></value>\n") | |
645 | dispatch[LongType] = dump_long | |
646 | ||
647 | def dump_double(self, value, write): | |
648 | write("<value><double>") | |
649 | write(repr(value)) | |
650 | write("</double></value>\n") | |
651 | dispatch[FloatType] = dump_double | |
652 | ||
653 | def dump_string(self, value, write, escape=escape): | |
654 | write("<value><string>") | |
655 | write(escape(value)) | |
656 | write("</string></value>\n") | |
657 | dispatch[StringType] = dump_string | |
658 | ||
659 | if unicode: | |
660 | def dump_unicode(self, value, write, escape=escape): | |
661 | value = value.encode(self.encoding) | |
662 | write("<value><string>") | |
663 | write(escape(value)) | |
664 | write("</string></value>\n") | |
665 | dispatch[UnicodeType] = dump_unicode | |
666 | ||
667 | def dump_array(self, value, write): | |
668 | i = id(value) | |
669 | if self.memo.has_key(i): | |
670 | raise TypeError, "cannot marshal recursive sequences" | |
671 | self.memo[i] = None | |
672 | dump = self.__dump | |
673 | write("<value><array><data>\n") | |
674 | for v in value: | |
675 | dump(v, write) | |
676 | write("</data></array></value>\n") | |
677 | del self.memo[i] | |
678 | dispatch[TupleType] = dump_array | |
679 | dispatch[ListType] = dump_array | |
680 | ||
681 | def dump_struct(self, value, write, escape=escape): | |
682 | i = id(value) | |
683 | if self.memo.has_key(i): | |
684 | raise TypeError, "cannot marshal recursive dictionaries" | |
685 | self.memo[i] = None | |
686 | dump = self.__dump | |
687 | write("<value><struct>\n") | |
688 | for k, v in value.items(): | |
689 | write("<member>\n") | |
690 | if type(k) is not StringType: | |
691 | if unicode and type(k) is UnicodeType: | |
692 | k = k.encode(self.encoding) | |
693 | else: | |
694 | raise TypeError, "dictionary key must be string" | |
695 | write("<name>%s</name>\n" % escape(k)) | |
696 | dump(v, write) | |
697 | write("</member>\n") | |
698 | write("</struct></value>\n") | |
699 | del self.memo[i] | |
700 | dispatch[DictType] = dump_struct | |
701 | ||
702 | def dump_instance(self, value, write): | |
703 | # check for special wrappers | |
704 | if value.__class__ in WRAPPERS: | |
705 | self.write = write | |
706 | value.encode(self) | |
707 | del self.write | |
708 | else: | |
709 | # store instance attributes as a struct (really?) | |
710 | self.dump_struct(value.__dict__, write) | |
711 | dispatch[InstanceType] = dump_instance | |
712 | ||
713 | ## | |
714 | # XML-RPC unmarshaller. | |
715 | # | |
716 | # @see loads | |
717 | ||
718 | class Unmarshaller: | |
719 | """Unmarshal an XML-RPC response, based on incoming XML event | |
720 | messages (start, data, end). Call close() to get the resulting | |
721 | data structure. | |
722 | ||
723 | Note that this reader is fairly tolerant, and gladly accepts bogus | |
724 | XML-RPC data without complaining (but not bogus XML). | |
725 | """ | |
726 | ||
727 | # and again, if you don't understand what's going on in here, | |
728 | # that's perfectly ok. | |
729 | ||
730 | def __init__(self): | |
731 | self._type = None | |
732 | self._stack = [] | |
733 | self._marks = [] | |
734 | self._data = [] | |
735 | self._methodname = None | |
736 | self._encoding = "utf-8" | |
737 | self.append = self._stack.append | |
738 | ||
739 | def close(self): | |
740 | # return response tuple and target method | |
741 | if self._type is None or self._marks: | |
742 | raise ResponseError() | |
743 | if self._type == "fault": | |
744 | raise Fault(**self._stack[0]) | |
745 | return tuple(self._stack) | |
746 | ||
747 | def getmethodname(self): | |
748 | return self._methodname | |
749 | ||
750 | # | |
751 | # event handlers | |
752 | ||
753 | def xml(self, encoding, standalone): | |
754 | self._encoding = encoding | |
755 | # FIXME: assert standalone == 1 ??? | |
756 | ||
757 | def start(self, tag, attrs): | |
758 | # prepare to handle this element | |
759 | if tag == "array" or tag == "struct": | |
760 | self._marks.append(len(self._stack)) | |
761 | self._data = [] | |
762 | self._value = (tag == "value") | |
763 | ||
764 | def data(self, text): | |
765 | self._data.append(text) | |
766 | ||
767 | def end(self, tag, join=string.join): | |
768 | # call the appropriate end tag handler | |
769 | try: | |
770 | f = self.dispatch[tag] | |
771 | except KeyError: | |
772 | pass # unknown tag ? | |
773 | else: | |
774 | return f(self, join(self._data, "")) | |
775 | ||
776 | # | |
777 | # accelerator support | |
778 | ||
779 | def end_dispatch(self, tag, data): | |
780 | # dispatch data | |
781 | try: | |
782 | f = self.dispatch[tag] | |
783 | except KeyError: | |
784 | pass # unknown tag ? | |
785 | else: | |
786 | return f(self, data) | |
787 | ||
788 | # | |
789 | # element decoders | |
790 | ||
791 | dispatch = {} | |
792 | ||
793 | def end_nil (self, data): | |
794 | self.append(None) | |
795 | self._value = 0 | |
796 | dispatch["nil"] = end_nil | |
797 | ||
798 | def end_boolean(self, data): | |
799 | if data == "0": | |
800 | self.append(False) | |
801 | elif data == "1": | |
802 | self.append(True) | |
803 | else: | |
804 | raise TypeError, "bad boolean value" | |
805 | self._value = 0 | |
806 | dispatch["boolean"] = end_boolean | |
807 | ||
808 | def end_int(self, data): | |
809 | self.append(int(data)) | |
810 | self._value = 0 | |
811 | dispatch["i4"] = end_int | |
812 | dispatch["int"] = end_int | |
813 | ||
814 | def end_double(self, data): | |
815 | self.append(float(data)) | |
816 | self._value = 0 | |
817 | dispatch["double"] = end_double | |
818 | ||
819 | def end_string(self, data): | |
820 | if self._encoding: | |
821 | data = _decode(data, self._encoding) | |
822 | self.append(_stringify(data)) | |
823 | self._value = 0 | |
824 | dispatch["string"] = end_string | |
825 | dispatch["name"] = end_string # struct keys are always strings | |
826 | ||
827 | def end_array(self, data): | |
828 | mark = self._marks.pop() | |
829 | # map arrays to Python lists | |
830 | self._stack[mark:] = [self._stack[mark:]] | |
831 | self._value = 0 | |
832 | dispatch["array"] = end_array | |
833 | ||
834 | def end_struct(self, data): | |
835 | mark = self._marks.pop() | |
836 | # map structs to Python dictionaries | |
837 | dict = {} | |
838 | items = self._stack[mark:] | |
839 | for i in range(0, len(items), 2): | |
840 | dict[_stringify(items[i])] = items[i+1] | |
841 | self._stack[mark:] = [dict] | |
842 | self._value = 0 | |
843 | dispatch["struct"] = end_struct | |
844 | ||
845 | def end_base64(self, data): | |
846 | value = Binary() | |
847 | value.decode(data) | |
848 | self.append(value) | |
849 | self._value = 0 | |
850 | dispatch["base64"] = end_base64 | |
851 | ||
852 | def end_dateTime(self, data): | |
853 | value = DateTime() | |
854 | value.decode(data) | |
855 | self.append(value) | |
856 | dispatch["dateTime.iso8601"] = end_dateTime | |
857 | ||
858 | def end_value(self, data): | |
859 | # if we stumble upon a value element with no internal | |
860 | # elements, treat it as a string element | |
861 | if self._value: | |
862 | self.end_string(data) | |
863 | dispatch["value"] = end_value | |
864 | ||
865 | def end_params(self, data): | |
866 | self._type = "params" | |
867 | dispatch["params"] = end_params | |
868 | ||
869 | def end_fault(self, data): | |
870 | self._type = "fault" | |
871 | dispatch["fault"] = end_fault | |
872 | ||
873 | def end_methodName(self, data): | |
874 | if self._encoding: | |
875 | data = _decode(data, self._encoding) | |
876 | self._methodname = data | |
877 | self._type = "methodName" # no params | |
878 | dispatch["methodName"] = end_methodName | |
879 | ||
880 | ## Multicall support | |
881 | # | |
882 | ||
883 | class _MultiCallMethod: | |
884 | # some lesser magic to store calls made to a MultiCall object | |
885 | # for batch execution | |
886 | def __init__(self, call_list, name): | |
887 | self.__call_list = call_list | |
888 | self.__name = name | |
889 | def __getattr__(self, name): | |
890 | return _MultiCallMethod(self.__call_list, "%s.%s" % (self.__name, name)) | |
891 | def __call__(self, *args): | |
892 | self.__call_list.append((self.__name, args)) | |
893 | ||
894 | class MultiCallIterator: | |
895 | """Iterates over the results of a multicall. Exceptions are | |
896 | thrown in response to xmlrpc faults.""" | |
897 | ||
898 | def __init__(self, results): | |
899 | self.results = results | |
900 | ||
901 | def __getitem__(self, i): | |
902 | item = self.results[i] | |
903 | if type(item) == type({}): | |
904 | raise Fault(item['faultCode'], item['faultString']) | |
905 | elif type(item) == type([]): | |
906 | return item[0] | |
907 | else: | |
908 | raise ValueError,\ | |
909 | "unexpected type in multicall result" | |
910 | ||
911 | class MultiCall: | |
912 | """server -> a object used to boxcar method calls | |
913 | ||
914 | server should be a ServerProxy object. | |
915 | ||
916 | Methods can be added to the MultiCall using normal | |
917 | method call syntax e.g.: | |
918 | ||
919 | multicall = MultiCall(server_proxy) | |
920 | multicall.add(2,3) | |
921 | multicall.get_address("Guido") | |
922 | ||
923 | To execute the multicall, call the MultiCall object e.g.: | |
924 | ||
925 | add_result, address = multicall() | |
926 | """ | |
927 | ||
928 | def __init__(self, server): | |
929 | self.__server = server | |
930 | self.__call_list = [] | |
931 | ||
932 | def __repr__(self): | |
933 | return "<MultiCall at %x>" % id(self) | |
934 | ||
935 | __str__ = __repr__ | |
936 | ||
937 | def __getattr__(self, name): | |
938 | return _MultiCallMethod(self.__call_list, name) | |
939 | ||
940 | def __call__(self): | |
941 | marshalled_list = [] | |
942 | for name, args in self.__call_list: | |
943 | marshalled_list.append({'methodName' : name, 'params' : args}) | |
944 | ||
945 | return MultiCallIterator(self.__server.system.multicall(marshalled_list)) | |
946 | ||
947 | # -------------------------------------------------------------------- | |
948 | # convenience functions | |
949 | ||
950 | ## | |
951 | # Create a parser object, and connect it to an unmarshalling instance. | |
952 | # This function picks the fastest available XML parser. | |
953 | # | |
954 | # return A (parser, unmarshaller) tuple. | |
955 | ||
956 | def getparser(): | |
957 | """getparser() -> parser, unmarshaller | |
958 | ||
959 | Create an instance of the fastest available parser, and attach it | |
960 | to an unmarshalling object. Return both objects. | |
961 | """ | |
962 | if FastParser and FastUnmarshaller: | |
963 | target = FastUnmarshaller(True, False, _binary, _datetime, Fault) | |
964 | parser = FastParser(target) | |
965 | else: | |
966 | target = Unmarshaller() | |
967 | if FastParser: | |
968 | parser = FastParser(target) | |
969 | elif SgmlopParser: | |
970 | parser = SgmlopParser(target) | |
971 | elif ExpatParser: | |
972 | parser = ExpatParser(target) | |
973 | else: | |
974 | parser = SlowParser(target) | |
975 | return parser, target | |
976 | ||
977 | ## | |
978 | # Convert a Python tuple or a Fault instance to an XML-RPC packet. | |
979 | # | |
980 | # @def dumps(params, **options) | |
981 | # @param params A tuple or Fault instance. | |
982 | # @keyparam methodname If given, create a methodCall request for | |
983 | # this method name. | |
984 | # @keyparam methodresponse If given, create a methodResponse packet. | |
985 | # If used with a tuple, the tuple must be a singleton (that is, | |
986 | # it must contain exactly one element). | |
987 | # @keyparam encoding The packet encoding. | |
988 | # @return A string containing marshalled data. | |
989 | ||
990 | def dumps(params, methodname=None, methodresponse=None, encoding=None, | |
991 | allow_none=0): | |
992 | """data [,options] -> marshalled data | |
993 | ||
994 | Convert an argument tuple or a Fault instance to an XML-RPC | |
995 | request (or response, if the methodresponse option is used). | |
996 | ||
997 | In addition to the data object, the following options can be given | |
998 | as keyword arguments: | |
999 | ||
1000 | methodname: the method name for a methodCall packet | |
1001 | ||
1002 | methodresponse: true to create a methodResponse packet. | |
1003 | If this option is used with a tuple, the tuple must be | |
1004 | a singleton (i.e. it can contain only one element). | |
1005 | ||
1006 | encoding: the packet encoding (default is UTF-8) | |
1007 | ||
1008 | All 8-bit strings in the data structure are assumed to use the | |
1009 | packet encoding. Unicode strings are automatically converted, | |
1010 | where necessary. | |
1011 | """ | |
1012 | ||
1013 | assert isinstance(params, TupleType) or isinstance(params, Fault),\ | |
1014 | "argument must be tuple or Fault instance" | |
1015 | ||
1016 | if isinstance(params, Fault): | |
1017 | methodresponse = 1 | |
1018 | elif methodresponse and isinstance(params, TupleType): | |
1019 | assert len(params) == 1, "response tuple must be a singleton" | |
1020 | ||
1021 | if not encoding: | |
1022 | encoding = "utf-8" | |
1023 | ||
1024 | if FastMarshaller: | |
1025 | m = FastMarshaller(encoding) | |
1026 | else: | |
1027 | m = Marshaller(encoding, allow_none) | |
1028 | ||
1029 | data = m.dumps(params) | |
1030 | ||
1031 | if encoding != "utf-8": | |
1032 | xmlheader = "<?xml version='1.0' encoding='%s'?>\n" % str(encoding) | |
1033 | else: | |
1034 | xmlheader = "<?xml version='1.0'?>\n" # utf-8 is default | |
1035 | ||
1036 | # standard XML-RPC wrappings | |
1037 | if methodname: | |
1038 | # a method call | |
1039 | if not isinstance(methodname, StringType): | |
1040 | methodname = methodname.encode(encoding) | |
1041 | data = ( | |
1042 | xmlheader, | |
1043 | "<methodCall>\n" | |
1044 | "<methodName>", methodname, "</methodName>\n", | |
1045 | data, | |
1046 | "</methodCall>\n" | |
1047 | ) | |
1048 | elif methodresponse: | |
1049 | # a method response, or a fault structure | |
1050 | data = ( | |
1051 | xmlheader, | |
1052 | "<methodResponse>\n", | |
1053 | data, | |
1054 | "</methodResponse>\n" | |
1055 | ) | |
1056 | else: | |
1057 | return data # return as is | |
1058 | return string.join(data, "") | |
1059 | ||
1060 | ## | |
1061 | # Convert an XML-RPC packet to a Python object. If the XML-RPC packet | |
1062 | # represents a fault condition, this function raises a Fault exception. | |
1063 | # | |
1064 | # @param data An XML-RPC packet, given as an 8-bit string. | |
1065 | # @return A tuple containing the unpacked data, and the method name | |
1066 | # (None if not present). | |
1067 | # @see Fault | |
1068 | ||
1069 | def loads(data): | |
1070 | """data -> unmarshalled data, method name | |
1071 | ||
1072 | Convert an XML-RPC packet to unmarshalled data plus a method | |
1073 | name (None if not present). | |
1074 | ||
1075 | If the XML-RPC packet represents a fault condition, this function | |
1076 | raises a Fault exception. | |
1077 | """ | |
1078 | p, u = getparser() | |
1079 | p.feed(data) | |
1080 | p.close() | |
1081 | return u.close(), u.getmethodname() | |
1082 | ||
1083 | ||
1084 | # -------------------------------------------------------------------- | |
1085 | # request dispatcher | |
1086 | ||
1087 | class _Method: | |
1088 | # some magic to bind an XML-RPC method to an RPC server. | |
1089 | # supports "nested" methods (e.g. examples.getStateName) | |
1090 | def __init__(self, send, name): | |
1091 | self.__send = send | |
1092 | self.__name = name | |
1093 | def __getattr__(self, name): | |
1094 | return _Method(self.__send, "%s.%s" % (self.__name, name)) | |
1095 | def __call__(self, *args): | |
1096 | return self.__send(self.__name, args) | |
1097 | ||
1098 | ## | |
1099 | # Standard transport class for XML-RPC over HTTP. | |
1100 | # <p> | |
1101 | # You can create custom transports by subclassing this method, and | |
1102 | # overriding selected methods. | |
1103 | ||
1104 | class Transport: | |
1105 | """Handles an HTTP transaction to an XML-RPC server.""" | |
1106 | ||
1107 | # client identifier (may be overridden) | |
1108 | user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__ | |
1109 | ||
1110 | ## | |
1111 | # Send a complete request, and parse the response. | |
1112 | # | |
1113 | # @param host Target host. | |
1114 | # @param handler Target PRC handler. | |
1115 | # @param request_body XML-RPC request body. | |
1116 | # @param verbose Debugging flag. | |
1117 | # @return Parsed response. | |
1118 | ||
1119 | def request(self, host, handler, request_body, verbose=0): | |
1120 | # issue XML-RPC request | |
1121 | ||
1122 | h = self.make_connection(host) | |
1123 | if verbose: | |
1124 | h.set_debuglevel(1) | |
1125 | ||
1126 | self.send_request(h, handler, request_body) | |
1127 | self.send_host(h, host) | |
1128 | self.send_user_agent(h) | |
1129 | self.send_content(h, request_body) | |
1130 | ||
1131 | errcode, errmsg, headers = h.getreply() | |
1132 | ||
1133 | if errcode != 200: | |
1134 | raise ProtocolError( | |
1135 | host + handler, | |
1136 | errcode, errmsg, | |
1137 | headers | |
1138 | ) | |
1139 | ||
1140 | self.verbose = verbose | |
1141 | ||
1142 | try: | |
1143 | sock = h._conn.sock | |
1144 | except AttributeError: | |
1145 | sock = None | |
1146 | ||
1147 | return self._parse_response(h.getfile(), sock) | |
1148 | ||
1149 | ## | |
1150 | # Create parser. | |
1151 | # | |
1152 | # @return A 2-tuple containing a parser and a unmarshaller. | |
1153 | ||
1154 | def getparser(self): | |
1155 | # get parser and unmarshaller | |
1156 | return getparser() | |
1157 | ||
1158 | ## | |
1159 | # Get authorization info from host parameter | |
1160 | # Host may be a string, or a (host, x509-dict) tuple; if a string, | |
1161 | # it is checked for a "user:pw@host" format, and a "Basic | |
1162 | # Authentication" header is added if appropriate. | |
1163 | # | |
1164 | # @param host Host descriptor (URL or (URL, x509 info) tuple). | |
1165 | # @return A 3-tuple containing (actual host, extra headers, | |
1166 | # x509 info). The header and x509 fields may be None. | |
1167 | ||
1168 | def get_host_info(self, host): | |
1169 | ||
1170 | x509 = {} | |
1171 | if isinstance(host, TupleType): | |
1172 | host, x509 = host | |
1173 | ||
1174 | import urllib | |
1175 | auth, host = urllib.splituser(host) | |
1176 | ||
1177 | if auth: | |
1178 | import base64 | |
1179 | auth = base64.encodestring(urllib.unquote(auth)) | |
1180 | auth = string.join(string.split(auth), "") # get rid of whitespace | |
1181 | extra_headers = [ | |
1182 | ("Authorization", "Basic " + auth) | |
1183 | ] | |
1184 | else: | |
1185 | extra_headers = None | |
1186 | ||
1187 | return host, extra_headers, x509 | |
1188 | ||
1189 | ## | |
1190 | # Connect to server. | |
1191 | # | |
1192 | # @param host Target host. | |
1193 | # @return A connection handle. | |
1194 | ||
1195 | def make_connection(self, host): | |
1196 | # create a HTTP connection object from a host descriptor | |
1197 | import httplib | |
1198 | host, extra_headers, x509 = self.get_host_info(host) | |
1199 | return httplib.HTTP(host) | |
1200 | ||
1201 | ## | |
1202 | # Send request header. | |
1203 | # | |
1204 | # @param connection Connection handle. | |
1205 | # @param handler Target RPC handler. | |
1206 | # @param request_body XML-RPC body. | |
1207 | ||
1208 | def send_request(self, connection, handler, request_body): | |
1209 | connection.putrequest("POST", handler) | |
1210 | ||
1211 | ## | |
1212 | # Send host name. | |
1213 | # | |
1214 | # @param connection Connection handle. | |
1215 | # @param host Host name. | |
1216 | ||
1217 | def send_host(self, connection, host): | |
1218 | host, extra_headers, x509 = self.get_host_info(host) | |
1219 | connection.putheader("Host", host) | |
1220 | if extra_headers: | |
1221 | if isinstance(extra_headers, DictType): | |
1222 | extra_headers = extra_headers.items() | |
1223 | for key, value in extra_headers: | |
1224 | connection.putheader(key, value) | |
1225 | ||
1226 | ## | |
1227 | # Send user-agent identifier. | |
1228 | # | |
1229 | # @param connection Connection handle. | |
1230 | ||
1231 | def send_user_agent(self, connection): | |
1232 | connection.putheader("User-Agent", self.user_agent) | |
1233 | ||
1234 | ## | |
1235 | # Send request body. | |
1236 | # | |
1237 | # @param connection Connection handle. | |
1238 | # @param request_body XML-RPC request body. | |
1239 | ||
1240 | def send_content(self, connection, request_body): | |
1241 | connection.putheader("Content-Type", "text/xml") | |
1242 | connection.putheader("Content-Length", str(len(request_body))) | |
1243 | connection.endheaders() | |
1244 | if request_body: | |
1245 | connection.send(request_body) | |
1246 | ||
1247 | ## | |
1248 | # Parse response. | |
1249 | # | |
1250 | # @param file Stream. | |
1251 | # @return Response tuple and target method. | |
1252 | ||
1253 | def parse_response(self, file): | |
1254 | # compatibility interface | |
1255 | return self._parse_response(file, None) | |
1256 | ||
1257 | ## | |
1258 | # Parse response (alternate interface). This is similar to the | |
1259 | # parse_response method, but also provides direct access to the | |
1260 | # underlying socket object (where available). | |
1261 | # | |
1262 | # @param file Stream. | |
1263 | # @param sock Socket handle (or None, if the socket object | |
1264 | # could not be accessed). | |
1265 | # @return Response tuple and target method. | |
1266 | ||
1267 | def _parse_response(self, file, sock): | |
1268 | # read response from input file/socket, and parse it | |
1269 | ||
1270 | p, u = self.getparser() | |
1271 | ||
1272 | while 1: | |
1273 | if sock: | |
1274 | response = sock.recv(1024) | |
1275 | else: | |
1276 | response = file.read(1024) | |
1277 | if not response: | |
1278 | break | |
1279 | if self.verbose: | |
1280 | print "body:", repr(response) | |
1281 | p.feed(response) | |
1282 | ||
1283 | file.close() | |
1284 | p.close() | |
1285 | ||
1286 | return u.close() | |
1287 | ||
1288 | ## | |
1289 | # Standard transport class for XML-RPC over HTTPS. | |
1290 | ||
1291 | class SafeTransport(Transport): | |
1292 | """Handles an HTTPS transaction to an XML-RPC server.""" | |
1293 | ||
1294 | # FIXME: mostly untested | |
1295 | ||
1296 | def make_connection(self, host): | |
1297 | # create a HTTPS connection object from a host descriptor | |
1298 | # host may be a string, or a (host, x509-dict) tuple | |
1299 | import httplib | |
1300 | host, extra_headers, x509 = self.get_host_info(host) | |
1301 | try: | |
1302 | HTTPS = httplib.HTTPS | |
1303 | except AttributeError: | |
1304 | raise NotImplementedError( | |
1305 | "your version of httplib doesn't support HTTPS" | |
1306 | ) | |
1307 | else: | |
1308 | return HTTPS(host, None, **(x509 or {})) | |
1309 | ||
1310 | ## | |
1311 | # Standard server proxy. This class establishes a virtual connection | |
1312 | # to an XML-RPC server. | |
1313 | # <p> | |
1314 | # This class is available as ServerProxy and Server. New code should | |
1315 | # use ServerProxy, to avoid confusion. | |
1316 | # | |
1317 | # @def ServerProxy(uri, **options) | |
1318 | # @param uri The connection point on the server. | |
1319 | # @keyparam transport A transport factory, compatible with the | |
1320 | # standard transport class. | |
1321 | # @keyparam encoding The default encoding used for 8-bit strings | |
1322 | # (default is UTF-8). | |
1323 | # @keyparam verbose Use a true value to enable debugging output. | |
1324 | # (printed to standard output). | |
1325 | # @see Transport | |
1326 | ||
1327 | class ServerProxy: | |
1328 | """uri [,options] -> a logical connection to an XML-RPC server | |
1329 | ||
1330 | uri is the connection point on the server, given as | |
1331 | scheme://host/target. | |
1332 | ||
1333 | The standard implementation always supports the "http" scheme. If | |
1334 | SSL socket support is available (Python 2.0), it also supports | |
1335 | "https". | |
1336 | ||
1337 | If the target part and the slash preceding it are both omitted, | |
1338 | "/RPC2" is assumed. | |
1339 | ||
1340 | The following options can be given as keyword arguments: | |
1341 | ||
1342 | transport: a transport factory | |
1343 | encoding: the request encoding (default is UTF-8) | |
1344 | ||
1345 | All 8-bit strings passed to the server proxy are assumed to use | |
1346 | the given encoding. | |
1347 | """ | |
1348 | ||
1349 | def __init__(self, uri, transport=None, encoding=None, verbose=0, | |
1350 | allow_none=0): | |
1351 | # establish a "logical" server connection | |
1352 | ||
1353 | # get the url | |
1354 | import urllib | |
1355 | type, uri = urllib.splittype(uri) | |
1356 | if type not in ("http", "https"): | |
1357 | raise IOError, "unsupported XML-RPC protocol" | |
1358 | self.__host, self.__handler = urllib.splithost(uri) | |
1359 | if not self.__handler: | |
1360 | self.__handler = "/RPC2" | |
1361 | ||
1362 | if transport is None: | |
1363 | if type == "https": | |
1364 | transport = SafeTransport() | |
1365 | else: | |
1366 | transport = Transport() | |
1367 | self.__transport = transport | |
1368 | ||
1369 | self.__encoding = encoding | |
1370 | self.__verbose = verbose | |
1371 | self.__allow_none = allow_none | |
1372 | ||
1373 | def __request(self, methodname, params): | |
1374 | # call a method on the remote server | |
1375 | ||
1376 | request = dumps(params, methodname, encoding=self.__encoding, | |
1377 | allow_none=self.__allow_none) | |
1378 | ||
1379 | response = self.__transport.request( | |
1380 | self.__host, | |
1381 | self.__handler, | |
1382 | request, | |
1383 | verbose=self.__verbose | |
1384 | ) | |
1385 | ||
1386 | if len(response) == 1: | |
1387 | response = response[0] | |
1388 | ||
1389 | return response | |
1390 | ||
1391 | def __repr__(self): | |
1392 | return ( | |
1393 | "<ServerProxy for %s%s>" % | |
1394 | (self.__host, self.__handler) | |
1395 | ) | |
1396 | ||
1397 | __str__ = __repr__ | |
1398 | ||
1399 | def __getattr__(self, name): | |
1400 | # magic method dispatcher | |
1401 | return _Method(self.__request, name) | |
1402 | ||
1403 | # note: to call a remote object with an non-standard name, use | |
1404 | # result getattr(server, "strange-python-name")(args) | |
1405 | ||
1406 | # compatibility | |
1407 | ||
1408 | Server = ServerProxy | |
1409 | ||
1410 | # -------------------------------------------------------------------- | |
1411 | # test code | |
1412 | ||
1413 | if __name__ == "__main__": | |
1414 | ||
1415 | # simple test program (from the XML-RPC specification) | |
1416 | ||
1417 | # server = ServerProxy("http://localhost:8000") # local server | |
1418 | server = ServerProxy("http://time.xmlrpc.com/RPC2") | |
1419 | ||
1420 | print server | |
1421 | ||
1422 | try: | |
1423 | print server.currentTime.getCurrentTime() | |
1424 | except Error, v: | |
1425 | print "ERROR", v | |
1426 | ||
1427 | multi = MultiCall(server) | |
1428 | multi.currentTime.getCurrentTime() | |
1429 | multi.currentTime.getCurrentTime() | |
1430 | try: | |
1431 | for response in multi(): | |
1432 | print response | |
1433 | except Error, v: | |
1434 | print "ERROR", v |