""" HTTPTransport provides an urllib2 based communications channel for use in xmlrpclib. Created by Bill Bumgarner . Using HTTPTransport allows the XML-RPC library to take full advantage of the features perpetuated by urllib2, including HTTP proxy support and a number of different authentication schemes. If urllib2 does not provide a handler capable of meeting the developer's need, the developer can create a custom Handler without requiring any changes to the XML-RPC code. Example usage: def serverProxyForUrl(uri, transport=None, encoding=None, verbose=0): if not transport: transport = HTTPTransport.HTTPTransport(uri, proxyUrl, proxyUser, proxyPass) return xmlrpclib.ServerProxy(uri, transport, encoding, verbose) """ import xmlrpclib from xmlrpclib import ProtocolError from urllib import splittype, splithost import urllib2 import sys class _TransportConnection: pass def _fixHandlerArrayOrder(handlers): insertionPoint = 0 for handlerIndex in range(0, len(handlers)): aHandler = handlers[handlerIndex] if isinstance(aHandler, urllib2.ProxyHandler): del handlers[handlerIndex] handlers.insert(insertionPoint, aHandler) insertionPoint = insertionPoint + 1 def _fixUpHandlers(anOpener): ### Moves proxy handlers to the front of the handlers in anOpener # # This function preserves the order of multiple proxyhandlers, if present. # This appears to be wasted effort in that build_opener() chokes if there # is more than one instance of any given handler class in the arglist. _fixHandlerArrayOrder(anOpener.handlers) map(lambda x: _fixHandlerArrayOrder(x), anOpener.handle_open.values()) class HTTPTransport(xmlrpclib.Transport): """Handles an HTTP transaction to an XML-RPC server using urllib2 [eventually].""" def __init__(self, uri, proxyUrl=None, proxyUser=None, proxyPass=None): ### this is kind of nasty. We need the full URI for the host/handler we are connecting to # to properly use urllib2 to make the request. This does not mesh completely cleanly # with xmlrpclib's initialization of ServerProxy. self.uri = uri self.proxyUrl = proxyUrl self.proxyUser = proxyUser self.proxyPass = proxyPass def request(self, host, handler, request_body, verbose=0): # issue XML-RPC request h = self.make_connection(host) self.set_verbosity(h, verbose) self.send_request(h, handler, request_body) self.send_host(h, host) self.send_user_agent(h) self.send_content(h, request_body) errcode, errmsg, headers = self.get_reply(h) if errcode != 200: raise ProtocolError( host + handler, errcode, errmsg, headers ) self.verbose = verbose return self.parse_response(self.get_file(h)) def make_connection(self, host): return _TransportConnection() def set_verbosity(self, connection, verbose): connection.verbose = verbose def send_request(self, connection, handler, request_body): connection.request = urllib2.Request(self.uri, request_body) def send_host(self, connection, host): connection.request.add_header("Host", host) def send_user_agent(self, connection): # There is no way to override the 'user-agent' sent by the UrlOpener. # This will only cause a second User-agent header to be sent. # This is both different from the urllib2 documentation of add_header() # and would seem to be a bug. # # connection.request.add_header("User-agent", self.user_agent) pass def send_content(self, connection, request_body): connection.request.add_header("Content-Type", "text/xml") def get_reply(self, connection): proxyHandler = None if self.proxyUrl: if self.proxyUser: type, rest = splittype(self.proxyUrl) host, rest = splithost(rest) if self.proxyPass: user = "%s:%s" % (self.proxyUser, self.proxyPass) else: user = self.proxyUser uri = "%s://%s@%s%s" % (type, user, host, rest) else: uri = self.proxyUrl proxies = {'http':uri, 'https':uri} proxyHandler = urllib2.ProxyHandler(proxies) opener = urllib2.build_opener(proxyHandler) _fixUpHandlers(opener) try: connection.response = opener.open(connection.request) except urllib2.HTTPError, c: if connection.verbose > 0: print "Internal HTTP %s error: %s %s" % (connection.verbose, c.code, c.msg) if connection.verbose > 1: print "Body of error response:\n-----------------\n%s\n--------------------\n" % c.read() return c.code, c.msg, c.headers return 200, "OK", connection.response.headers def get_file(self, connection): return connection.response