OLD | NEW |
1 """Simple XML-RPC Server. | 1 """Simple XML-RPC Server. |
2 | 2 |
3 This module can be used to create simple XML-RPC servers | 3 This module can be used to create simple XML-RPC servers |
4 by creating a server and either installing functions, a | 4 by creating a server and either installing functions, a |
5 class instance, or by extending the SimpleXMLRPCServer | 5 class instance, or by extending the SimpleXMLRPCServer |
6 class. | 6 class. |
7 | 7 |
8 It can also be used to handle XML-RPC requests in a CGI | 8 It can also be used to handle XML-RPC requests in a CGI |
9 environment using CGIXMLRPCRequestHandler. | 9 environment using CGIXMLRPCRequestHandler. |
10 | 10 |
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
99 # Written by Brian Quinlan (brian@sweetapp.com). | 99 # Written by Brian Quinlan (brian@sweetapp.com). |
100 # Based on code written by Fredrik Lundh. | 100 # Based on code written by Fredrik Lundh. |
101 | 101 |
102 import xmlrpclib | 102 import xmlrpclib |
103 from xmlrpclib import Fault | 103 from xmlrpclib import Fault |
104 import SocketServer | 104 import SocketServer |
105 import BaseHTTPServer | 105 import BaseHTTPServer |
106 import sys | 106 import sys |
107 import os | 107 import os |
108 import traceback | 108 import traceback |
| 109 import re |
109 try: | 110 try: |
110 import fcntl | 111 import fcntl |
111 except ImportError: | 112 except ImportError: |
112 fcntl = None | 113 fcntl = None |
113 | 114 |
114 def resolve_dotted_attribute(obj, attr, allow_dotted_names=True): | 115 def resolve_dotted_attribute(obj, attr, allow_dotted_names=True): |
115 """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d | 116 """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d |
116 | 117 |
117 Resolves a dotted attribute name to an object. Raises | 118 Resolves a dotted attribute name to an object. Raises |
118 an AttributeError if any attribute in the chain starts with a '_'. | 119 an AttributeError if any attribute in the chain starts with a '_'. |
(...skipping 304 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
423 """Simple XML-RPC request handler class. | 424 """Simple XML-RPC request handler class. |
424 | 425 |
425 Handles all HTTP POST requests and attempts to decode them as | 426 Handles all HTTP POST requests and attempts to decode them as |
426 XML-RPC requests. | 427 XML-RPC requests. |
427 """ | 428 """ |
428 | 429 |
429 # Class attribute listing the accessible path components; | 430 # Class attribute listing the accessible path components; |
430 # paths not on this list will result in a 404 error. | 431 # paths not on this list will result in a 404 error. |
431 rpc_paths = ('/', '/RPC2') | 432 rpc_paths = ('/', '/RPC2') |
432 | 433 |
| 434 #if not None, encode responses larger than this, if possible |
| 435 encode_threshold = 1400 #a common MTU |
| 436 |
| 437 #Override form StreamRequestHandler: full buffering of output |
| 438 #and no Nagle. |
| 439 wbufsize = -1 |
| 440 disable_nagle_algorithm = True |
| 441 |
| 442 # a re to match a gzip Accept-Encoding |
| 443 aepattern = re.compile(r""" |
| 444 \s* ([^\s;]+) \s* #content-coding |
| 445 (;\s* q \s*=\s* ([0-9\.]+))? #q |
| 446 """, re.VERBOSE | re.IGNORECASE) |
| 447 |
| 448 def accept_encodings(self): |
| 449 r = {} |
| 450 ae = self.headers.get("Accept-Encoding", "") |
| 451 for e in ae.split(","): |
| 452 match = self.aepattern.match(e) |
| 453 if match: |
| 454 v = match.group(3) |
| 455 v = float(v) if v else 1.0 |
| 456 r[match.group(1)] = v |
| 457 return r |
| 458 |
433 def is_rpc_path_valid(self): | 459 def is_rpc_path_valid(self): |
434 if self.rpc_paths: | 460 if self.rpc_paths: |
435 return self.path in self.rpc_paths | 461 return self.path in self.rpc_paths |
436 else: | 462 else: |
437 # If .rpc_paths is empty, just assume all paths are legal | 463 # If .rpc_paths is empty, just assume all paths are legal |
438 return True | 464 return True |
439 | 465 |
440 def do_POST(self): | 466 def do_POST(self): |
441 """Handles the HTTP POST request. | 467 """Handles the HTTP POST request. |
442 | 468 |
(...skipping 13 matching lines...) Expand all Loading... |
456 # begin to have problems (bug #792570). | 482 # begin to have problems (bug #792570). |
457 max_chunk_size = 10*1024*1024 | 483 max_chunk_size = 10*1024*1024 |
458 size_remaining = int(self.headers["content-length"]) | 484 size_remaining = int(self.headers["content-length"]) |
459 L = [] | 485 L = [] |
460 while size_remaining: | 486 while size_remaining: |
461 chunk_size = min(size_remaining, max_chunk_size) | 487 chunk_size = min(size_remaining, max_chunk_size) |
462 L.append(self.rfile.read(chunk_size)) | 488 L.append(self.rfile.read(chunk_size)) |
463 size_remaining -= len(L[-1]) | 489 size_remaining -= len(L[-1]) |
464 data = ''.join(L) | 490 data = ''.join(L) |
465 | 491 |
| 492 data = self.decode_request_content(data) |
| 493 if data is None: |
| 494 return #response has been sent |
| 495 |
466 # In previous versions of SimpleXMLRPCServer, _dispatch | 496 # In previous versions of SimpleXMLRPCServer, _dispatch |
467 # could be overridden in this class, instead of in | 497 # could be overridden in this class, instead of in |
468 # SimpleXMLRPCDispatcher. To maintain backwards compatibility, | 498 # SimpleXMLRPCDispatcher. To maintain backwards compatibility, |
469 # check to see if a subclass implements _dispatch and dispatch | 499 # check to see if a subclass implements _dispatch and dispatch |
470 # using that method if present. | 500 # using that method if present. |
471 response = self.server._marshaled_dispatch( | 501 response = self.server._marshaled_dispatch( |
472 data, getattr(self, '_dispatch', None) | 502 data, getattr(self, '_dispatch', None) |
473 ) | 503 ) |
474 except Exception, e: # This should only happen if the module is buggy | 504 except Exception, e: # This should only happen if the module is buggy |
475 # internal error, report as HTTP server error | 505 # internal error, report as HTTP server error |
476 self.send_response(500) | 506 self.send_response(500) |
477 | 507 |
478 # Send information about the exception if requested | 508 # Send information about the exception if requested |
479 if hasattr(self.server, '_send_traceback_header') and \ | 509 if hasattr(self.server, '_send_traceback_header') and \ |
480 self.server._send_traceback_header: | 510 self.server._send_traceback_header: |
481 self.send_header("X-exception", str(e)) | 511 self.send_header("X-exception", str(e)) |
482 self.send_header("X-traceback", traceback.format_exc()) | 512 self.send_header("X-traceback", traceback.format_exc()) |
483 | 513 |
| 514 self.send_header("Content-length", "0") |
484 self.end_headers() | 515 self.end_headers() |
485 else: | 516 else: |
486 # got a valid XML RPC response | 517 # got a valid XML RPC response |
487 self.send_response(200) | 518 self.send_response(200) |
488 self.send_header("Content-type", "text/xml") | 519 self.send_header("Content-type", "text/xml") |
| 520 if self.encode_threshold is not None: |
| 521 if len(response) > self.encode_threshold: |
| 522 q = self.accept_encodings().get("gzip", 0) |
| 523 if q: |
| 524 response = xmlrpclib.gzip_encode(response) |
| 525 self.send_header("Content-Encoding", "gzip") |
489 self.send_header("Content-length", str(len(response))) | 526 self.send_header("Content-length", str(len(response))) |
490 self.end_headers() | 527 self.end_headers() |
491 self.wfile.write(response) | 528 self.wfile.write(response) |
492 | 529 |
493 # shut down the connection | 530 def decode_request_content(self, data): |
494 self.wfile.flush() | 531 #support gzip encoding of request |
495 self.connection.shutdown(1) | 532 encoding = self.headers.get("content-encoding", "identity").lower() |
| 533 if encoding == "identity": |
| 534 return data |
| 535 if encoding == "gzip": |
| 536 try: |
| 537 return xmlrpclib.gzip_decode(data) |
| 538 except ValueError: |
| 539 self.send_response(400, "error decoding gzip content") |
| 540 else: |
| 541 self.send_response(501, "encoding %r not supported" % encoding) |
| 542 self.send_header("Content-length", "0") |
| 543 self.end_headers() |
496 | 544 |
497 def report_404 (self): | 545 def report_404 (self): |
498 # Report a 404 error | 546 # Report a 404 error |
499 self.send_response(404) | 547 self.send_response(404) |
500 response = 'No such page' | 548 response = 'No such page' |
501 self.send_header("Content-type", "text/plain") | 549 self.send_header("Content-type", "text/plain") |
502 self.send_header("Content-length", str(len(response))) | 550 self.send_header("Content-length", str(len(response))) |
503 self.end_headers() | 551 self.end_headers() |
504 self.wfile.write(response) | 552 self.wfile.write(response) |
505 # shut down the connection | |
506 self.wfile.flush() | |
507 self.connection.shutdown(1) | |
508 | 553 |
509 def log_request(self, code='-', size='-'): | 554 def log_request(self, code='-', size='-'): |
510 """Selectively log an accepted request.""" | 555 """Selectively log an accepted request.""" |
511 | 556 |
512 if self.server.logRequests: | 557 if self.server.logRequests: |
513 BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size) | 558 BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size) |
514 | 559 |
515 class SimpleXMLRPCServer(SocketServer.TCPServer, | 560 class SimpleXMLRPCServer(SocketServer.TCPServer, |
516 SimpleXMLRPCDispatcher): | 561 SimpleXMLRPCDispatcher): |
517 """Simple XML-RPC server. | 562 """Simple XML-RPC server. |
518 | 563 |
519 Simple XML-RPC server that allows functions and a single instance | 564 Simple XML-RPC server that allows functions and a single instance |
520 to be installed to handle requests. The default implementation | 565 to be installed to handle requests. The default implementation |
521 attempts to dispatch XML-RPC calls to the functions or instance | 566 attempts to dispatch XML-RPC calls to the functions or instance |
522 installed in the server. Override the _dispatch method inhereted | 567 installed in the server. Override the _dispatch method inhereted |
523 from SimpleXMLRPCDispatcher to change this behavior. | 568 from SimpleXMLRPCDispatcher to change this behavior. |
524 """ | 569 """ |
525 | 570 |
526 allow_reuse_address = True | 571 allow_reuse_address = True |
527 | 572 |
| 573 |
528 # Warning: this is for debugging purposes only! Never set this to True in | 574 # Warning: this is for debugging purposes only! Never set this to True in |
529 # production code, as will be sending out sensitive information (exception | 575 # production code, as will be sending out sensitive information (exception |
530 # and stack trace details) when exceptions are raised inside | 576 # and stack trace details) when exceptions are raised inside |
531 # SimpleXMLRPCRequestHandler.do_POST | 577 # SimpleXMLRPCRequestHandler.do_POST |
532 _send_traceback_header = False | 578 _send_traceback_header = False |
533 | 579 |
534 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler, | 580 def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler, |
535 logRequests=True, allow_none=False, encoding=None, bind_and_act
ivate=True): | 581 logRequests=True, allow_none=False, encoding=None, bind_and_act
ivate=True): |
536 self.logRequests = logRequests | 582 self.logRequests = logRequests |
537 | 583 |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
606 request_text = sys.stdin.read(length) | 652 request_text = sys.stdin.read(length) |
607 | 653 |
608 self.handle_xmlrpc(request_text) | 654 self.handle_xmlrpc(request_text) |
609 | 655 |
610 if __name__ == '__main__': | 656 if __name__ == '__main__': |
611 print 'Running XML-RPC server on port 8000' | 657 print 'Running XML-RPC server on port 8000' |
612 server = SimpleXMLRPCServer(("localhost", 8000)) | 658 server = SimpleXMLRPCServer(("localhost", 8000)) |
613 server.register_function(pow) | 659 server.register_function(pow) |
614 server.register_function(lambda x,y: x+y, 'add') | 660 server.register_function(lambda x,y: x+y, 'add') |
615 server.serve_forever() | 661 server.serve_forever() |
OLD | NEW |