# HG changeset patch # User Thierry Florac # Date 1341383139 -7200 # Node ID 5fdc01c95cade364568950d3744947bb56c84d3a # Parent 6404eb9f238d77b21f0af3a17a12560a28e4272d Added Python 2.7 compatibility code and timeout to XML-RPC protocol helpers diff -r 6404eb9f238d -r 5fdc01c95cad docs/HISTORY.txt --- 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 ------ diff -r 6404eb9f238d -r 5fdc01c95cad src/ztfy/utils/protocol/xmlrpc.py --- 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 # 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)