mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	
							parent
							
								
									552e7a7e2f
								
							
						
					
					
						commit
						e007860b8b
					
				
					 7 changed files with 406 additions and 70 deletions
				
			
		|  | @ -133,6 +133,15 @@ alone XML-RPC servers. | ||||||
| 
 | 
 | ||||||
|    .. versionadded:: 2.5 |    .. versionadded:: 2.5 | ||||||
| 
 | 
 | ||||||
|  | .. attribute:: SimpleXMLRPCRequestHandler.encode_threshold | ||||||
|  | 
 | ||||||
|  |    If this attribute is not ``None``, responses larger than this value | ||||||
|  |    will be encoded using the *gzip* transfer encoding, if permitted by | ||||||
|  |    the client.  The default is ``1400`` which corresponds roughly | ||||||
|  |    to a single TCP packet. | ||||||
|  | 
 | ||||||
|  |    .. versionadded:: 2.7 | ||||||
|  | 
 | ||||||
| .. _simplexmlrpcserver-example: | .. _simplexmlrpcserver-example: | ||||||
| 
 | 
 | ||||||
| SimpleXMLRPCServer Example | SimpleXMLRPCServer Example | ||||||
|  |  | ||||||
|  | @ -309,11 +309,13 @@ def handle_one_request(self): | ||||||
|         commands such as GET and POST. |         commands such as GET and POST. | ||||||
| 
 | 
 | ||||||
|         """ |         """ | ||||||
|  |         try: | ||||||
|             self.raw_requestline = self.rfile.readline() |             self.raw_requestline = self.rfile.readline() | ||||||
|             if not self.raw_requestline: |             if not self.raw_requestline: | ||||||
|                 self.close_connection = 1 |                 self.close_connection = 1 | ||||||
|                 return |                 return | ||||||
|         if not self.parse_request(): # An error code has been sent, just exit |             if not self.parse_request(): | ||||||
|  |                 # An error code has been sent, just exit | ||||||
|                 return |                 return | ||||||
|             mname = 'do_' + self.command |             mname = 'do_' + self.command | ||||||
|             if not hasattr(self, mname): |             if not hasattr(self, mname): | ||||||
|  | @ -321,6 +323,12 @@ def handle_one_request(self): | ||||||
|                 return |                 return | ||||||
|             method = getattr(self, mname) |             method = getattr(self, mname) | ||||||
|             method() |             method() | ||||||
|  |             self.wfile.flush() #actually send the response if not already done. | ||||||
|  |         except socket.timeout, e: | ||||||
|  |             #a read or a write timed out.  Discard this connection | ||||||
|  |             self.log_error("Request timed out: %r", e) | ||||||
|  |             self.close_connection = 1 | ||||||
|  |             return | ||||||
| 
 | 
 | ||||||
|     def handle(self): |     def handle(self): | ||||||
|         """Handle multiple requests if necessary.""" |         """Handle multiple requests if necessary.""" | ||||||
|  |  | ||||||
|  | @ -240,10 +240,6 @@ def do_GET(self): | ||||||
|         self.end_headers() |         self.end_headers() | ||||||
|         self.wfile.write(response) |         self.wfile.write(response) | ||||||
| 
 | 
 | ||||||
|         # shut down the connection |  | ||||||
|         self.wfile.flush() |  | ||||||
|         self.connection.shutdown(1) |  | ||||||
| 
 |  | ||||||
| class DocXMLRPCServer(  SimpleXMLRPCServer, | class DocXMLRPCServer(  SimpleXMLRPCServer, | ||||||
|                         XMLRPCDocGenerator): |                         XMLRPCDocGenerator): | ||||||
|     """XML-RPC and HTML documentation server. |     """XML-RPC and HTML documentation server. | ||||||
|  |  | ||||||
|  | @ -106,6 +106,7 @@ def export_add(self, x, y): | ||||||
| import sys | import sys | ||||||
| import os | import os | ||||||
| import traceback | import traceback | ||||||
|  | import re | ||||||
| try: | try: | ||||||
|     import fcntl |     import fcntl | ||||||
| except ImportError: | except ImportError: | ||||||
|  | @ -430,6 +431,31 @@ class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): | ||||||
|     # paths not on this list will result in a 404 error. |     # paths not on this list will result in a 404 error. | ||||||
|     rpc_paths = ('/', '/RPC2') |     rpc_paths = ('/', '/RPC2') | ||||||
| 
 | 
 | ||||||
|  |     #if not None, encode responses larger than this, if possible | ||||||
|  |     encode_threshold = 1400 #a common MTU | ||||||
|  | 
 | ||||||
|  |     #Override form StreamRequestHandler: full buffering of output | ||||||
|  |     #and no Nagle. | ||||||
|  |     wbufsize = -1 | ||||||
|  |     disable_nagle_algorithm = True | ||||||
|  | 
 | ||||||
|  |     # a re to match a gzip Accept-Encoding | ||||||
|  |     aepattern = re.compile(r""" | ||||||
|  |                             \s* ([^\s;]+) \s*            #content-coding | ||||||
|  |                             (;\s* q \s*=\s* ([0-9\.]+))? #q | ||||||
|  |                             """, re.VERBOSE | re.IGNORECASE) | ||||||
|  | 
 | ||||||
|  |     def accept_encodings(self): | ||||||
|  |         r = {} | ||||||
|  |         ae = self.headers.get("Accept-Encoding", "") | ||||||
|  |         for e in ae.split(","): | ||||||
|  |             match = self.aepattern.match(e) | ||||||
|  |             if match: | ||||||
|  |                 v = match.group(3) | ||||||
|  |                 v = float(v) if v else 1.0 | ||||||
|  |                 r[match.group(1)] = v | ||||||
|  |         return r | ||||||
|  | 
 | ||||||
|     def is_rpc_path_valid(self): |     def is_rpc_path_valid(self): | ||||||
|         if self.rpc_paths: |         if self.rpc_paths: | ||||||
|             return self.path in self.rpc_paths |             return self.path in self.rpc_paths | ||||||
|  | @ -463,6 +489,10 @@ def do_POST(self): | ||||||
|                 size_remaining -= len(L[-1]) |                 size_remaining -= len(L[-1]) | ||||||
|             data = ''.join(L) |             data = ''.join(L) | ||||||
| 
 | 
 | ||||||
|  |             data = self.decode_request_content(data) | ||||||
|  |             if data is None: | ||||||
|  |                 return #response has been sent | ||||||
|  | 
 | ||||||
|             # In previous versions of SimpleXMLRPCServer, _dispatch |             # In previous versions of SimpleXMLRPCServer, _dispatch | ||||||
|             # could be overridden in this class, instead of in |             # could be overridden in this class, instead of in | ||||||
|             # SimpleXMLRPCDispatcher. To maintain backwards compatibility, |             # SimpleXMLRPCDispatcher. To maintain backwards compatibility, | ||||||
|  | @ -481,18 +511,36 @@ def do_POST(self): | ||||||
|                 self.send_header("X-exception", str(e)) |                 self.send_header("X-exception", str(e)) | ||||||
|                 self.send_header("X-traceback", traceback.format_exc()) |                 self.send_header("X-traceback", traceback.format_exc()) | ||||||
| 
 | 
 | ||||||
|  |             self.send_header("Content-length", "0") | ||||||
|             self.end_headers() |             self.end_headers() | ||||||
|         else: |         else: | ||||||
|             # got a valid XML RPC response |             # got a valid XML RPC response | ||||||
|             self.send_response(200) |             self.send_response(200) | ||||||
|             self.send_header("Content-type", "text/xml") |             self.send_header("Content-type", "text/xml") | ||||||
|  |             if self.encode_threshold is not None: | ||||||
|  |                 if len(response) > self.encode_threshold: | ||||||
|  |                     q = self.accept_encodings().get("gzip", 0) | ||||||
|  |                     if q: | ||||||
|  |                         response = xmlrpclib.gzip_encode(response) | ||||||
|  |                         self.send_header("Content-Encoding", "gzip") | ||||||
|             self.send_header("Content-length", str(len(response))) |             self.send_header("Content-length", str(len(response))) | ||||||
|             self.end_headers() |             self.end_headers() | ||||||
|             self.wfile.write(response) |             self.wfile.write(response) | ||||||
| 
 | 
 | ||||||
|             # shut down the connection |     def decode_request_content(self, data): | ||||||
|             self.wfile.flush() |         #support gzip encoding of request | ||||||
|             self.connection.shutdown(1) |         encoding = self.headers.get("content-encoding", "identity").lower() | ||||||
|  |         if encoding == "identity": | ||||||
|  |             return data | ||||||
|  |         if encoding == "gzip": | ||||||
|  |             try: | ||||||
|  |                 return xmlrpclib.gzip_decode(data) | ||||||
|  |             except ValueError: | ||||||
|  |                 self.send_response(400, "error decoding gzip content") | ||||||
|  |         else: | ||||||
|  |             self.send_response(501, "encoding %r not supported" % encoding) | ||||||
|  |         self.send_header("Content-length", "0") | ||||||
|  |         self.end_headers() | ||||||
| 
 | 
 | ||||||
|     def report_404 (self): |     def report_404 (self): | ||||||
|             # Report a 404 error |             # Report a 404 error | ||||||
|  | @ -502,9 +550,6 @@ def report_404 (self): | ||||||
|         self.send_header("Content-length", str(len(response))) |         self.send_header("Content-length", str(len(response))) | ||||||
|         self.end_headers() |         self.end_headers() | ||||||
|         self.wfile.write(response) |         self.wfile.write(response) | ||||||
|         # shut down the connection |  | ||||||
|         self.wfile.flush() |  | ||||||
|         self.connection.shutdown(1) |  | ||||||
| 
 | 
 | ||||||
|     def log_request(self, code='-', size='-'): |     def log_request(self, code='-', size='-'): | ||||||
|         """Selectively log an accepted request.""" |         """Selectively log an accepted request.""" | ||||||
|  |  | ||||||
|  | @ -445,6 +445,7 @@ def get_request(self): | ||||||
| 
 | 
 | ||||||
|     def close_request(self, request): |     def close_request(self, request): | ||||||
|         """Called to clean up an individual request.""" |         """Called to clean up an individual request.""" | ||||||
|  |         request.shutdown(socket.SHUT_WR) | ||||||
|         request.close() |         request.close() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -610,12 +611,11 @@ def __init__(self, request, client_address, server): | ||||||
|         self.request = request |         self.request = request | ||||||
|         self.client_address = client_address |         self.client_address = client_address | ||||||
|         self.server = server |         self.server = server | ||||||
|         try: |  | ||||||
|         self.setup() |         self.setup() | ||||||
|  |         try: | ||||||
|             self.handle() |             self.handle() | ||||||
|             self.finish() |  | ||||||
|         finally: |         finally: | ||||||
|             sys.exc_traceback = None    # Help garbage collection |             self.finish() | ||||||
| 
 | 
 | ||||||
|     def setup(self): |     def setup(self): | ||||||
|         pass |         pass | ||||||
|  | @ -649,12 +649,17 @@ class StreamRequestHandler(BaseRequestHandler): | ||||||
|     rbufsize = -1 |     rbufsize = -1 | ||||||
|     wbufsize = 0 |     wbufsize = 0 | ||||||
| 
 | 
 | ||||||
|  |     # A timeout to apply to the request socket, if not None. | ||||||
|  |     timeout = None | ||||||
|  | 
 | ||||||
|     # Disable nagle algoritm for this socket, if True. |     # Disable nagle algoritm for this socket, if True. | ||||||
|     # Use only when wbufsize != 0, to avoid small packets. |     # Use only when wbufsize != 0, to avoid small packets. | ||||||
|     disable_nagle_algorithm = False |     disable_nagle_algorithm = False | ||||||
| 
 | 
 | ||||||
|     def setup(self): |     def setup(self): | ||||||
|         self.connection = self.request |         self.connection = self.request | ||||||
|  |         if self.timeout is not None: | ||||||
|  |             self.connection.settimeout(self.timeout) | ||||||
|         if self.disable_nagle_algorithm: |         if self.disable_nagle_algorithm: | ||||||
|             self.connection.setsockopt(socket.IPPROTO_TCP, |             self.connection.setsockopt(socket.IPPROTO_TCP, | ||||||
|                                        socket.TCP_NODELAY, True) |                                        socket.TCP_NODELAY, True) | ||||||
|  |  | ||||||
|  | @ -273,7 +273,7 @@ def test_decode(self): | ||||||
| # The evt is set twice.  First when the server is ready to serve. | # The evt is set twice.  First when the server is ready to serve. | ||||||
| # Second when the server has been shutdown.  The user must clear | # Second when the server has been shutdown.  The user must clear | ||||||
| # the event after it has been set the first time to catch the second set. | # the event after it has been set the first time to catch the second set. | ||||||
| def http_server(evt, numrequests): | def http_server(evt, numrequests, requestHandler=None): | ||||||
|     class TestInstanceClass: |     class TestInstanceClass: | ||||||
|         def div(self, x, y): |         def div(self, x, y): | ||||||
|             return x // y |             return x // y | ||||||
|  | @ -294,7 +294,9 @@ def get_request(self): | ||||||
|             s.setblocking(True) |             s.setblocking(True) | ||||||
|             return s, port |             return s, port | ||||||
| 
 | 
 | ||||||
|     serv = MyXMLRPCServer(("localhost", 0), |     if not requestHandler: | ||||||
|  |         requestHandler = SimpleXMLRPCServer.SimpleXMLRPCRequestHandler | ||||||
|  |     serv = MyXMLRPCServer(("localhost", 0), requestHandler, | ||||||
|                           logRequests=False, bind_and_activate=False) |                           logRequests=False, bind_and_activate=False) | ||||||
|     try: |     try: | ||||||
|         serv.socket.settimeout(3) |         serv.socket.settimeout(3) | ||||||
|  | @ -348,34 +350,36 @@ def is_unavailable_exception(e): | ||||||
| 
 | 
 | ||||||
|     return False |     return False | ||||||
| 
 | 
 | ||||||
| # NOTE: The tests in SimpleServerTestCase will ignore failures caused by | class BaseServerTestCase(unittest.TestCase): | ||||||
| # "temporarily unavailable" exceptions raised in SimpleXMLRPCServer.  This |     requestHandler = None | ||||||
| # condition occurs infrequently on some platforms, frequently on others, and |  | ||||||
| # is apparently caused by using SimpleXMLRPCServer with a non-blocking socket. |  | ||||||
| # If the server class is updated at some point in the future to handle this |  | ||||||
| # situation more gracefully, these tests should be modified appropriately. |  | ||||||
| 
 |  | ||||||
| class SimpleServerTestCase(unittest.TestCase): |  | ||||||
|     def setUp(self): |     def setUp(self): | ||||||
|         # enable traceback reporting |         # enable traceback reporting | ||||||
|         SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = True |         SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = True | ||||||
| 
 | 
 | ||||||
|         self.evt = threading.Event() |         self.evt = threading.Event() | ||||||
|         # start server thread to handle requests |         # start server thread to handle requests | ||||||
|         serv_args = (self.evt, 1) |         serv_args = (self.evt, 1, self.requestHandler) | ||||||
|         threading.Thread(target=http_server, args=serv_args).start() |         threading.Thread(target=http_server, args=serv_args).start() | ||||||
| 
 | 
 | ||||||
|         # wait for the server to be ready |         # wait for the server to be ready | ||||||
|         self.evt.wait() |         self.evt.wait(10) | ||||||
|         self.evt.clear() |         self.evt.clear() | ||||||
| 
 | 
 | ||||||
|     def tearDown(self): |     def tearDown(self): | ||||||
|         # wait on the server thread to terminate |         # wait on the server thread to terminate | ||||||
|         self.evt.wait() |         self.evt.wait(10) | ||||||
| 
 | 
 | ||||||
|         # disable traceback reporting |         # disable traceback reporting | ||||||
|         SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = False |         SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = False | ||||||
| 
 | 
 | ||||||
|  | # NOTE: The tests in SimpleServerTestCase will ignore failures caused by | ||||||
|  | # "temporarily unavailable" exceptions raised in SimpleXMLRPCServer.  This | ||||||
|  | # condition occurs infrequently on some platforms, frequently on others, and | ||||||
|  | # is apparently caused by using SimpleXMLRPCServer with a non-blocking socket | ||||||
|  | # If the server class is updated at some point in the future to handle this | ||||||
|  | # situation more gracefully, these tests should be modified appropriately. | ||||||
|  | 
 | ||||||
|  | class SimpleServerTestCase(BaseServerTestCase): | ||||||
|     def test_simple1(self): |     def test_simple1(self): | ||||||
|         try: |         try: | ||||||
|             p = xmlrpclib.ServerProxy(URL) |             p = xmlrpclib.ServerProxy(URL) | ||||||
|  | @ -512,6 +516,110 @@ def test_dotted_attribute(self): | ||||||
|         # This avoids waiting for the socket timeout. |         # This avoids waiting for the socket timeout. | ||||||
|         self.test_simple1() |         self.test_simple1() | ||||||
| 
 | 
 | ||||||
|  | #A test case that verifies that a server using the HTTP/1.1 keep-alive mechanism | ||||||
|  | #does indeed serve subsequent requests on the same connection | ||||||
|  | class KeepaliveServerTestCase(BaseServerTestCase): | ||||||
|  |     #a request handler that supports keep-alive and logs requests into a | ||||||
|  |     #class variable | ||||||
|  |     class RequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): | ||||||
|  |         parentClass = SimpleXMLRPCServer.SimpleXMLRPCRequestHandler | ||||||
|  |         protocol_version = 'HTTP/1.1' | ||||||
|  |         myRequests = [] | ||||||
|  |         def handle(self): | ||||||
|  |             self.myRequests.append([]) | ||||||
|  |             return self.parentClass.handle(self) | ||||||
|  |         def handle_one_request(self): | ||||||
|  |             result = self.parentClass.handle_one_request(self) | ||||||
|  |             self.myRequests[-1].append(self.raw_requestline) | ||||||
|  |             return result | ||||||
|  | 
 | ||||||
|  |     requestHandler = RequestHandler | ||||||
|  |     def setUp(self): | ||||||
|  |         #clear request log | ||||||
|  |         self.RequestHandler.myRequests = [] | ||||||
|  |         return BaseServerTestCase.setUp(self) | ||||||
|  | 
 | ||||||
|  |     def test_two(self): | ||||||
|  |         p = xmlrpclib.ServerProxy(URL) | ||||||
|  |         self.assertEqual(p.pow(6,8), 6**8) | ||||||
|  |         self.assertEqual(p.pow(6,8), 6**8) | ||||||
|  |         self.assertEqual(len(self.RequestHandler.myRequests), 1) | ||||||
|  |         #we may or may not catch the final "append" with the empty line | ||||||
|  |         self.assertTrue(len(self.RequestHandler.myRequests[-1]) >= 2) | ||||||
|  | 
 | ||||||
|  | #A test case that verifies that gzip encoding works in both directions | ||||||
|  | #(for a request and the response) | ||||||
|  | class GzipServerTestCase(BaseServerTestCase): | ||||||
|  |     #a request handler that supports keep-alive and logs requests into a | ||||||
|  |     #class variable | ||||||
|  |     class RequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): | ||||||
|  |         parentClass = SimpleXMLRPCServer.SimpleXMLRPCRequestHandler | ||||||
|  |         protocol_version = 'HTTP/1.1' | ||||||
|  | 
 | ||||||
|  |         def do_POST(self): | ||||||
|  |             #store content of last request in class | ||||||
|  |             self.__class__.content_length = int(self.headers["content-length"]) | ||||||
|  |             return self.parentClass.do_POST(self) | ||||||
|  |     requestHandler = RequestHandler | ||||||
|  | 
 | ||||||
|  |     class Transport(xmlrpclib.Transport): | ||||||
|  |         #custom transport, stores the response length for our perusal | ||||||
|  |         fake_gzip = False | ||||||
|  |         def parse_response(self, response): | ||||||
|  |             self.response_length=int(response.getheader("content-length", 0)) | ||||||
|  |             return xmlrpclib.Transport.parse_response(self, response) | ||||||
|  | 
 | ||||||
|  |         def send_content(self, connection, body): | ||||||
|  |             if self.fake_gzip: | ||||||
|  |                 #add a lone gzip header to induce decode error remotely | ||||||
|  |                 connection.putheader("Content-Encoding", "gzip") | ||||||
|  |             return xmlrpclib.Transport.send_content(self, connection, body) | ||||||
|  | 
 | ||||||
|  |     def test_gzip_request(self): | ||||||
|  |         t = self.Transport() | ||||||
|  |         t.encode_threshold = None | ||||||
|  |         p = xmlrpclib.ServerProxy(URL, transport=t) | ||||||
|  |         self.assertEqual(p.pow(6,8), 6**8) | ||||||
|  |         a = self.RequestHandler.content_length | ||||||
|  |         t.encode_threshold = 0 #turn on request encoding | ||||||
|  |         self.assertEqual(p.pow(6,8), 6**8) | ||||||
|  |         b = self.RequestHandler.content_length | ||||||
|  |         self.assertTrue(a>b) | ||||||
|  | 
 | ||||||
|  |     def test_bad_gzip_request(self): | ||||||
|  |         t = self.Transport() | ||||||
|  |         t.encode_threshold = None | ||||||
|  |         t.fake_gzip = True | ||||||
|  |         p = xmlrpclib.ServerProxy(URL, transport=t) | ||||||
|  |         cm = self.assertRaisesRegexp(xmlrpclib.ProtocolError, | ||||||
|  |                                      re.compile(r"\b400\b")) | ||||||
|  |         with cm: | ||||||
|  |             p.pow(6, 8) | ||||||
|  | 
 | ||||||
|  |     def test_gsip_response(self): | ||||||
|  |         t = self.Transport() | ||||||
|  |         p = xmlrpclib.ServerProxy(URL, transport=t) | ||||||
|  |         old = self.requestHandler.encode_threshold | ||||||
|  |         self.requestHandler.encode_threshold = None #no encoding | ||||||
|  |         self.assertEqual(p.pow(6,8), 6**8) | ||||||
|  |         a = t.response_length | ||||||
|  |         self.requestHandler.encode_threshold = 0 #always encode | ||||||
|  |         self.assertEqual(p.pow(6,8), 6**8) | ||||||
|  |         b = t.response_length | ||||||
|  |         self.requestHandler.encode_threshold = old | ||||||
|  |         self.assertTrue(a>b) | ||||||
|  | 
 | ||||||
|  | #Test special attributes of the ServerProxy object | ||||||
|  | class ServerProxyTestCase(unittest.TestCase): | ||||||
|  |     def test_close(self): | ||||||
|  |         p = xmlrpclib.ServerProxy(URL) | ||||||
|  |         self.assertEqual(p('close')(), None) | ||||||
|  | 
 | ||||||
|  |     def test_transport(self): | ||||||
|  |         t = xmlrpclib.Transport() | ||||||
|  |         p = xmlrpclib.ServerProxy(URL, transport=t) | ||||||
|  |         self.assertEqual(p('transport'), t) | ||||||
|  | 
 | ||||||
| # This is a contrived way to make a failure occur on the server side | # This is a contrived way to make a failure occur on the server side | ||||||
| # in order to test the _send_traceback_header flag on the server | # in order to test the _send_traceback_header flag on the server | ||||||
| class FailingMessageClass(mimetools.Message): | class FailingMessageClass(mimetools.Message): | ||||||
|  | @ -693,6 +801,9 @@ def getvalue(self): | ||||||
|     def makefile(self, x='r', y=-1): |     def makefile(self, x='r', y=-1): | ||||||
|         raise RuntimeError |         raise RuntimeError | ||||||
| 
 | 
 | ||||||
|  |     def close(self): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
| class FakeTransport(xmlrpclib.Transport): | class FakeTransport(xmlrpclib.Transport): | ||||||
|     """A Transport instance that records instead of sending a request. |     """A Transport instance that records instead of sending a request. | ||||||
| 
 | 
 | ||||||
|  | @ -703,7 +814,7 @@ class FakeTransport(xmlrpclib.Transport): | ||||||
| 
 | 
 | ||||||
|     def make_connection(self, host): |     def make_connection(self, host): | ||||||
|         conn = xmlrpclib.Transport.make_connection(self, host) |         conn = xmlrpclib.Transport.make_connection(self, host) | ||||||
|         conn._conn.sock = self.fake_socket = FakeSocket() |         conn.sock = self.fake_socket = FakeSocket() | ||||||
|         return conn |         return conn | ||||||
| 
 | 
 | ||||||
| class TransportSubclassTestCase(unittest.TestCase): | class TransportSubclassTestCase(unittest.TestCase): | ||||||
|  | @ -763,6 +874,9 @@ def test_main(): | ||||||
|     xmlrpc_tests = [XMLRPCTestCase, HelperTestCase, DateTimeTestCase, |     xmlrpc_tests = [XMLRPCTestCase, HelperTestCase, DateTimeTestCase, | ||||||
|          BinaryTestCase, FaultTestCase, TransportSubclassTestCase] |          BinaryTestCase, FaultTestCase, TransportSubclassTestCase] | ||||||
|     xmlrpc_tests.append(SimpleServerTestCase) |     xmlrpc_tests.append(SimpleServerTestCase) | ||||||
|  |     xmlrpc_tests.append(KeepaliveServerTestCase) | ||||||
|  |     xmlrpc_tests.append(GzipServerTestCase) | ||||||
|  |     xmlrpc_tests.append(ServerProxyTestCase) | ||||||
|     xmlrpc_tests.append(FailingServerTestCase) |     xmlrpc_tests.append(FailingServerTestCase) | ||||||
|     xmlrpc_tests.append(CGIHandlerTestCase) |     xmlrpc_tests.append(CGIHandlerTestCase) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										209
									
								
								Lib/xmlrpclib.py
									
										
									
									
									
								
							
							
						
						
									
										209
									
								
								Lib/xmlrpclib.py
									
										
									
									
									
								
							|  | @ -139,6 +139,10 @@ | ||||||
| import re, string, time, operator | import re, string, time, operator | ||||||
| 
 | 
 | ||||||
| from types import * | from types import * | ||||||
|  | import gzip | ||||||
|  | import socket | ||||||
|  | import errno | ||||||
|  | import httplib | ||||||
| 
 | 
 | ||||||
| # -------------------------------------------------------------------- | # -------------------------------------------------------------------- | ||||||
| # Internal stuff | # Internal stuff | ||||||
|  | @ -1129,6 +1133,72 @@ def loads(data, use_datetime=0): | ||||||
|     p.close() |     p.close() | ||||||
|     return u.close(), u.getmethodname() |     return u.close(), u.getmethodname() | ||||||
| 
 | 
 | ||||||
|  | ## | ||||||
|  | # Encode a string using the gzip content encoding such as specified by the | ||||||
|  | # Content-Encoding: gzip | ||||||
|  | # in the HTTP header, as described in RFC 1952 | ||||||
|  | # | ||||||
|  | # @param data the unencoded data | ||||||
|  | # @return the encoded data | ||||||
|  | 
 | ||||||
|  | def gzip_encode(data): | ||||||
|  |     """data -> gzip encoded data | ||||||
|  | 
 | ||||||
|  |     Encode data using the gzip content encoding as described in RFC 1952 | ||||||
|  |     """ | ||||||
|  |     f = StringIO.StringIO() | ||||||
|  |     gzf = gzip.GzipFile(mode="wb", fileobj=f, compresslevel=1) | ||||||
|  |     gzf.write(data) | ||||||
|  |     gzf.close() | ||||||
|  |     encoded = f.getvalue() | ||||||
|  |     f.close() | ||||||
|  |     return encoded | ||||||
|  | 
 | ||||||
|  | ## | ||||||
|  | # Decode a string using the gzip content encoding such as specified by the | ||||||
|  | # Content-Encoding: gzip | ||||||
|  | # in the HTTP header, as described in RFC 1952 | ||||||
|  | # | ||||||
|  | # @param data The encoded data | ||||||
|  | # @return the unencoded data | ||||||
|  | # @raises ValueError if data is not correctly coded. | ||||||
|  | 
 | ||||||
|  | def gzip_decode(data): | ||||||
|  |     """gzip encoded data -> unencoded data | ||||||
|  | 
 | ||||||
|  |     Decode data using the gzip content encoding as described in RFC 1952 | ||||||
|  |     """ | ||||||
|  |     f = StringIO.StringIO(data) | ||||||
|  |     gzf = gzip.GzipFile(mode="rb", fileobj=f) | ||||||
|  |     try: | ||||||
|  |         decoded = gzf.read() | ||||||
|  |     except IOError: | ||||||
|  |         raise ValueError("invalid data") | ||||||
|  |     f.close() | ||||||
|  |     gzf.close() | ||||||
|  |     return decoded | ||||||
|  | 
 | ||||||
|  | ## | ||||||
|  | # Return a decoded file-like object for the gzip encoding | ||||||
|  | # as described in RFC 1952. | ||||||
|  | # | ||||||
|  | # @param response A stream supporting a read() method | ||||||
|  | # @return a file-like object that the decoded data can be read() from | ||||||
|  | 
 | ||||||
|  | class GzipDecodedResponse(gzip.GzipFile): | ||||||
|  |     """a file-like object to decode a response encoded with the gzip | ||||||
|  |     method, as described in RFC 1952. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, response): | ||||||
|  |         #response doesn't support tell() and read(), required by | ||||||
|  |         #GzipFile | ||||||
|  |         self.stringio = StringIO.StringIO(response.read()) | ||||||
|  |         gzip.GzipFile.__init__(self, mode="rb", fileobj=self.stringio) | ||||||
|  | 
 | ||||||
|  |     def close(self): | ||||||
|  |         gzip.GzipFile.close(self) | ||||||
|  |         self.stringio.close() | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| # -------------------------------------------------------------------- | # -------------------------------------------------------------------- | ||||||
| # request dispatcher | # request dispatcher | ||||||
|  | @ -1156,8 +1226,39 @@ class Transport: | ||||||
|     # client identifier (may be overridden) |     # client identifier (may be overridden) | ||||||
|     user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__ |     user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__ | ||||||
| 
 | 
 | ||||||
|  |     #if true, we'll request gzip encoding | ||||||
|  |     accept_gzip_encoding = True | ||||||
|  | 
 | ||||||
|  |     # if positive, encode request using gzip if it exceeds this threshold | ||||||
|  |     # note that many server will get confused, so only use it if you know | ||||||
|  |     # that they can decode such a request | ||||||
|  |     encode_threshold = None #None = don't encode | ||||||
|  | 
 | ||||||
|     def __init__(self, use_datetime=0): |     def __init__(self, use_datetime=0): | ||||||
|         self._use_datetime = use_datetime |         self._use_datetime = use_datetime | ||||||
|  |         self._connection = (None, None) | ||||||
|  |         self._extra_headers = [] | ||||||
|  |     ## | ||||||
|  |     # Send a complete request, and parse the response. | ||||||
|  |     # Retry request if a cached connection has disconnected. | ||||||
|  |     # | ||||||
|  |     # @param host Target host. | ||||||
|  |     # @param handler Target PRC handler. | ||||||
|  |     # @param request_body XML-RPC request body. | ||||||
|  |     # @param verbose Debugging flag. | ||||||
|  |     # @return Parsed response. | ||||||
|  | 
 | ||||||
|  |     def request(self, host, handler, request_body, verbose=0): | ||||||
|  |         #retry request once if cached connection has gone cold | ||||||
|  |         for i in (0, 1): | ||||||
|  |             try: | ||||||
|  |                 return self.single_request(host, handler, request_body, verbose) | ||||||
|  |             except (socket.error, httplib.HTTPException), e: | ||||||
|  |                 retry = (errno.ECONNRESET, | ||||||
|  |                          errno.ECONNABORTED, | ||||||
|  |                          httplib.BadStatusLine) #close after we sent request | ||||||
|  |                 if i or e[0] not in retry: | ||||||
|  |                     raise | ||||||
| 
 | 
 | ||||||
|     ## |     ## | ||||||
|     # Send a complete request, and parse the response. |     # Send a complete request, and parse the response. | ||||||
|  | @ -1168,31 +1269,40 @@ def __init__(self, use_datetime=0): | ||||||
|     # @param verbose Debugging flag. |     # @param verbose Debugging flag. | ||||||
|     # @return Parsed response. |     # @return Parsed response. | ||||||
| 
 | 
 | ||||||
|     def request(self, host, handler, request_body, verbose=0): |     def single_request(self, host, handler, request_body, verbose=0): | ||||||
|         # issue XML-RPC request |         # issue XML-RPC request | ||||||
| 
 | 
 | ||||||
|         h = self.make_connection(host) |         h = self.make_connection(host) | ||||||
|         if verbose: |         if verbose: | ||||||
|             h.set_debuglevel(1) |             h.set_debuglevel(1) | ||||||
| 
 | 
 | ||||||
|  |         try: | ||||||
|             self.send_request(h, handler, request_body) |             self.send_request(h, handler, request_body) | ||||||
|             self.send_host(h, host) |             self.send_host(h, host) | ||||||
|             self.send_user_agent(h) |             self.send_user_agent(h) | ||||||
|             self.send_content(h, request_body) |             self.send_content(h, request_body) | ||||||
| 
 | 
 | ||||||
|         errcode, errmsg, headers = h.getreply(buffering=True) |             response = h.getresponse(buffering=True) | ||||||
|  |             if response.status == 200: | ||||||
|  |                 self.verbose = verbose | ||||||
|  |                 return self.parse_response(response) | ||||||
|  |         except Fault: | ||||||
|  |             raise | ||||||
|  |         except Exception: | ||||||
|  |             # All unexpected errors leave connection in | ||||||
|  |             # a strange state, so we clear it. | ||||||
|  |             self.close() | ||||||
|  |             raise | ||||||
| 
 | 
 | ||||||
|         if errcode != 200: |         #discard any response data and raise exception | ||||||
|  |         if (response.getheader("content-length", 0)): | ||||||
|  |             response.read() | ||||||
|         raise ProtocolError( |         raise ProtocolError( | ||||||
|             host + handler, |             host + handler, | ||||||
|                 errcode, errmsg, |             response.status, response.reason, | ||||||
|                 headers |             response.msg, | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|         self.verbose = verbose |  | ||||||
| 
 |  | ||||||
|         return self.parse_response(h.getfile()) |  | ||||||
| 
 |  | ||||||
|     ## |     ## | ||||||
|     # Create parser. |     # Create parser. | ||||||
|     # |     # | ||||||
|  | @ -1240,10 +1350,25 @@ def get_host_info(self, host): | ||||||
|     # @return A connection handle. |     # @return A connection handle. | ||||||
| 
 | 
 | ||||||
|     def make_connection(self, host): |     def make_connection(self, host): | ||||||
|  |         #return an existing connection if possible.  This allows | ||||||
|  |         #HTTP/1.1 keep-alive. | ||||||
|  |         if self._connection and host == self._connection[0]: | ||||||
|  |             return self._connection[1] | ||||||
|  | 
 | ||||||
|         # create a HTTP connection object from a host descriptor |         # create a HTTP connection object from a host descriptor | ||||||
|         import httplib |         chost, self._extra_headers, x509 = self.get_host_info(host) | ||||||
|         host, extra_headers, x509 = self.get_host_info(host) |         #store the host argument along with the connection object | ||||||
|         return httplib.HTTP(host) |         self._connection = host, httplib.HTTPConnection(chost) | ||||||
|  |         return self._connection[1] | ||||||
|  | 
 | ||||||
|  |     ## | ||||||
|  |     # Clear any cached connection object. | ||||||
|  |     # Used in the event of socket errors. | ||||||
|  |     # | ||||||
|  |     def close(self): | ||||||
|  |         if self._connection[1]: | ||||||
|  |             self._connection[1].close() | ||||||
|  |             self._connection = (None, None) | ||||||
| 
 | 
 | ||||||
|     ## |     ## | ||||||
|     # Send request header. |     # Send request header. | ||||||
|  | @ -1253,6 +1378,10 @@ def make_connection(self, host): | ||||||
|     # @param request_body XML-RPC body. |     # @param request_body XML-RPC body. | ||||||
| 
 | 
 | ||||||
|     def send_request(self, connection, handler, request_body): |     def send_request(self, connection, handler, request_body): | ||||||
|  |         if (self.accept_gzip_encoding): | ||||||
|  |             connection.putrequest("POST", handler, skip_accept_encoding=True) | ||||||
|  |             connection.putheader("Accept-Encoding", "gzip") | ||||||
|  |         else: | ||||||
|             connection.putrequest("POST", handler) |             connection.putrequest("POST", handler) | ||||||
| 
 | 
 | ||||||
|     ## |     ## | ||||||
|  | @ -1260,10 +1389,13 @@ def send_request(self, connection, handler, request_body): | ||||||
|     # |     # | ||||||
|     # @param connection Connection handle. |     # @param connection Connection handle. | ||||||
|     # @param host Host name. |     # @param host Host name. | ||||||
|  |     # | ||||||
|  |     # Note: This function doesn't actually add the "Host" | ||||||
|  |     # header anymore, it is done as part of the connection.putrequest() in | ||||||
|  |     # send_request() above. | ||||||
| 
 | 
 | ||||||
|     def send_host(self, connection, host): |     def send_host(self, connection, host): | ||||||
|         host, extra_headers, x509 = self.get_host_info(host) |         extra_headers = self._extra_headers | ||||||
|         connection.putheader("Host", host) |  | ||||||
|         if extra_headers: |         if extra_headers: | ||||||
|             if isinstance(extra_headers, DictType): |             if isinstance(extra_headers, DictType): | ||||||
|                 extra_headers = extra_headers.items() |                 extra_headers = extra_headers.items() | ||||||
|  | @ -1286,6 +1418,13 @@ def send_user_agent(self, connection): | ||||||
| 
 | 
 | ||||||
|     def send_content(self, connection, request_body): |     def send_content(self, connection, request_body): | ||||||
|         connection.putheader("Content-Type", "text/xml") |         connection.putheader("Content-Type", "text/xml") | ||||||
|  | 
 | ||||||
|  |         #optionally encode the request | ||||||
|  |         if (self.encode_threshold is not None and | ||||||
|  |             self.encode_threshold < len(request_body)): | ||||||
|  |             connection.putheader("Content-Encoding", "gzip") | ||||||
|  |             request_body = gzip_encode(request_body) | ||||||
|  | 
 | ||||||
|         connection.putheader("Content-Length", str(len(request_body))) |         connection.putheader("Content-Length", str(len(request_body))) | ||||||
|         connection.endheaders(request_body) |         connection.endheaders(request_body) | ||||||
| 
 | 
 | ||||||
|  | @ -1295,20 +1434,25 @@ def send_content(self, connection, request_body): | ||||||
|     # @param file Stream. |     # @param file Stream. | ||||||
|     # @return Response tuple and target method. |     # @return Response tuple and target method. | ||||||
| 
 | 
 | ||||||
|     def parse_response(self, file): |     def parse_response(self, response): | ||||||
|         # read response from input file/socket, and parse it |         # read response data from httpresponse, and parse it | ||||||
|  |         if response.getheader("Content-Encoding", "") == "gzip": | ||||||
|  |             stream = GzipDecodedResponse(response) | ||||||
|  |         else: | ||||||
|  |             stream = response | ||||||
| 
 | 
 | ||||||
|         p, u = self.getparser() |         p, u = self.getparser() | ||||||
| 
 | 
 | ||||||
|         while 1: |         while 1: | ||||||
|             response = file.read(1024) |             data = stream.read(1024) | ||||||
|             if not response: |             if not data: | ||||||
|                 break |                 break | ||||||
|             if self.verbose: |             if self.verbose: | ||||||
|                 print "body:", repr(response) |                 print "body:", repr(data) | ||||||
|             p.feed(response) |             p.feed(data) | ||||||
| 
 | 
 | ||||||
|         file.close() |         if stream is not response: | ||||||
|  |             stream.close() | ||||||
|         p.close() |         p.close() | ||||||
| 
 | 
 | ||||||
|         return u.close() |         return u.close() | ||||||
|  | @ -1322,18 +1466,20 @@ class SafeTransport(Transport): | ||||||
|     # FIXME: mostly untested |     # FIXME: mostly untested | ||||||
| 
 | 
 | ||||||
|     def make_connection(self, host): |     def make_connection(self, host): | ||||||
|  |         if self._connection and host == self._connection[0]: | ||||||
|  |             return self._connection[1] | ||||||
|         # create a HTTPS connection object from a host descriptor |         # create a HTTPS connection object from a host descriptor | ||||||
|         # host may be a string, or a (host, x509-dict) tuple |         # host may be a string, or a (host, x509-dict) tuple | ||||||
|         import httplib |  | ||||||
|         host, extra_headers, x509 = self.get_host_info(host) |  | ||||||
|         try: |         try: | ||||||
|             HTTPS = httplib.HTTPS |             HTTPS = httplib.HTTPSConnection | ||||||
|         except AttributeError: |         except AttributeError: | ||||||
|             raise NotImplementedError( |             raise NotImplementedError( | ||||||
|                 "your version of httplib doesn't support HTTPS" |                 "your version of httplib doesn't support HTTPS" | ||||||
|                 ) |                 ) | ||||||
|         else: |         else: | ||||||
|             return HTTPS(host, None, **(x509 or {})) |             chost, self._extra_headers, x509 = self.get_host_info(host) | ||||||
|  |             self._connection = host, HTTPSConnection(chost, None, **(x509 or {})) | ||||||
|  |             return self._connection[1] | ||||||
| 
 | 
 | ||||||
| ## | ## | ||||||
| # Standard server proxy.  This class establishes a virtual connection | # Standard server proxy.  This class establishes a virtual connection | ||||||
|  | @ -1398,6 +1544,9 @@ def __init__(self, uri, transport=None, encoding=None, verbose=0, | ||||||
|         self.__verbose = verbose |         self.__verbose = verbose | ||||||
|         self.__allow_none = allow_none |         self.__allow_none = allow_none | ||||||
| 
 | 
 | ||||||
|  |     def __close(self): | ||||||
|  |         self.__transport.close() | ||||||
|  | 
 | ||||||
|     def __request(self, methodname, params): |     def __request(self, methodname, params): | ||||||
|         # call a method on the remote server |         # call a method on the remote server | ||||||
| 
 | 
 | ||||||
|  | @ -1431,6 +1580,16 @@ def __getattr__(self, name): | ||||||
|     # note: to call a remote object with an non-standard name, use |     # note: to call a remote object with an non-standard name, use | ||||||
|     # result getattr(server, "strange-python-name")(args) |     # result getattr(server, "strange-python-name")(args) | ||||||
| 
 | 
 | ||||||
|  |     def __call__(self, attr): | ||||||
|  |         """A workaround to get special attributes on the ServerProxy | ||||||
|  |            without interfering with the magic __getattr__ | ||||||
|  |         """ | ||||||
|  |         if attr == "close": | ||||||
|  |             return self.__close | ||||||
|  |         elif attr == "transport": | ||||||
|  |             return self.__transport | ||||||
|  |         raise AttributeError("Attribute %r not found" % (attr,)) | ||||||
|  | 
 | ||||||
| # compatibility | # compatibility | ||||||
| 
 | 
 | ||||||
| Server = ServerProxy | Server = ServerProxy | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Kristján Valur Jónsson
						Kristján Valur Jónsson