Added Python 2.7 compatibility code and timeout to XML-RPC protocol helpers ZTK-1.1
authorThierry Florac <tflorac@ulthar.net>
Wed, 04 Jul 2012 08:25:39 +0200
branchZTK-1.1
changeset 160 5fdc01c95cad
parent 159 6404eb9f238d
child 161 33b95dfd6142
Added Python 2.7 compatibility code and timeout to XML-RPC protocol helpers
docs/HISTORY.txt
src/ztfy/utils/protocol/xmlrpc.py
--- a/docs/HISTORY.txt	Wed Jul 04 08:23:44 2012 +0200
+++ b/docs/HISTORY.txt	Wed Jul 04 08:25:39 2012 +0200
@@ -4,6 +4,8 @@
 0.3.13
 ------
  - added "ztfy.utils.container" utility module
+ - added Python 2.7 compatibility code and timeout parameter to XML-RPC
+   protocol helper
 
 0.3.12
 ------
--- a/src/ztfy/utils/protocol/xmlrpc.py	Wed Jul 04 08:23:44 2012 +0200
+++ b/src/ztfy/utils/protocol/xmlrpc.py	Wed Jul 04 08:25:39 2012 +0200
@@ -4,6 +4,9 @@
 # Copyright (c) 2008 Thierry Florac <tflorac AT ulthar.net>
 # All Rights Reserved.
 #
+# Python 2.6/2.7 compatibility code copied from EULExistDB
+# (https://github.com/emory-libraries/eulexistdb)
+#
 # This software is subject to the provisions of the Zope Public License,
 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
@@ -17,6 +20,8 @@
 # import standard packages
 import base64
 import cookielib
+import httplib
+import socket
 import urllib2
 import xmlrpclib
 
@@ -29,14 +34,63 @@
 # import local packages
 
 
+class TimeoutHTTP(httplib.HTTP):
+    def __init__(self, host='', port=None, strict=None, timeout=None):
+        if port == 0:
+            port = None
+        self._setup(self._connection_class(host, port, strict, timeout))
+
+class TimeoutHTTPS(httplib.HTTPS):
+    def __init__(self, host='', port=None, strict=None, timeout=None):
+        if port == 0:
+            port = None
+        self._setup(self._connection_class(host, port, strict, timeout))
+
+
 class XMLRPCCookieAuthTransport(xmlrpclib.Transport):
     """An XML-RPC transport handling authentication via cookies"""
 
-    def __init__(self, user_agent, credentials=(), cookies=None):
+    _http_connection = httplib.HTTPConnection
+    _http_connection_compat = TimeoutHTTP
+
+    def __init__(self, user_agent, credentials=(), cookies=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
         xmlrpclib.Transport.__init__(self)
         self.user_agent = user_agent
         self.credentials = credentials
         self.cookies = cookies
+        self.timeout = timeout
+        if self._connection_required_compat():
+            self.make_connection = self._make_connection_compat
+
+    def _connection_required_compat(self):
+        # Compatibility code copied from EULExistDB (https://github.com/emory-libraries/eulexistdb)
+        # UGLY HACK ALERT. Python 2.7 xmlrpclib caches connection objects in
+        # self._connection (and sets self._connection in __init__). Python
+        # 2.6 and earlier has no such cache. Thus, if self._connection
+        # exists, we're running the newer-style, and if it doesn't then
+        # we're running older-style and thus need compatibility mode.
+        try:
+            self._connection
+            return False
+        except AttributeError:
+            return True
+
+    def make_connection(self, host):
+        # This is the make_connection that runs under Python 2.7 and newer.
+        # The code is pulled straight from 2.7 xmlrpclib, except replacing
+        # HTTPConnection with self._http_connection
+        if self._connection and host == self._connection[0]:
+            return self._connection[1]
+        chost, self._extra_headers, _x509 = self.get_host_info(host)
+        self._connection = host, self._http_connection(chost, timeout=self.timeout)
+        return self._connection[1]
+
+    def _make_connection_compat(self, host):
+        # This method runs as make_connection under Python 2.6 and older.
+        # __init__ detects which version we need and pastes this method
+        # directly into self.make_connection if necessary.
+        host, _extra_headers, _x509 = self.get_host_info(host)
+        return self._http_connection_compat(host, timeout=self.timeout)
 
     # override the send_host hook to also send authentication info
     def send_host(self, connection, host):
@@ -69,8 +123,8 @@
         self.send_request(connection, handler, request_body)
         self.send_host(connection, host)
         self.send_user_agent(connection)
+        self.send_content(connection, request_body)
         # get response
-        self.send_content(connection, request_body)
         errcode, errmsg, headers = connection.getreply()
         # extract cookies from response headers
         crequest = CookieRequest('http://%s/' % host)
@@ -86,13 +140,26 @@
         return self._parse_response(connection.getfile(), sock)
 
 
-def getClient(uri, credentials=(), verbose=False):
+class SecureXMLRPCCookieAuthTransport(XMLRPCCookieAuthTransport):
+    """Secure XML-RPC transport"""
+
+    _http_connection = httplib.HTTPSConnection
+    _http_connection_compat = TimeoutHTTPS
+
+
+def getClient(uri, credentials=(), verbose=False, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
     """Get an XML-RPC client which supports basic authentication"""
-    transport = XMLRPCCookieAuthTransport('Python XML-RPC Client/0.1 (ZTFY basic implementation)', credentials)
+    if uri.startswith('https:'):
+        transport = SecureXMLRPCCookieAuthTransport('Python XML-RPC Client/0.1 (ZTFY secure transport)', credentials, timeout=timeout)
+    else:
+        transport = XMLRPCCookieAuthTransport('Python XML-RPC Client/0.1 (ZTFY basic transport)', credentials, timeout=timeout)
     return xmlrpclib.Server(uri, transport=transport, verbose=verbose)
 
 
-def getClientWithCookies(uri, credentials=(), verbose=False):
+def getClientWithCookies(uri, credentials=(), verbose=False, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
     """Get an XML-RPC client which supports authentication throught cookies"""
-    transport = XMLRPCCookieAuthTransport('Python XML-RPC Client/0.1 (ZTFY cookie implementation)', credentials, cookielib.CookieJar())
+    if uri.startswith('https:'):
+        transport = SecureXMLRPCCookieAuthTransport('Python XML-RPC Client/0.1 (ZTFY secure cookie transport)', credentials, cookielib.CookieJar(), timeout)
+    else:
+        transport = XMLRPCCookieAuthTransport('Python XML-RPC Client/0.1 (ZTFY secure cookie transport)', credentials, cookielib.CookieJar(), timeout)
     return xmlrpclib.Server(uri, transport=transport, verbose=verbose)