Differences

This shows you the differences between two versions of the page.

Link to this comparison view

xmlrpccookies [2012/01/06 17:41] (current)
jim created
Line 1: Line 1:
 +====== Cookies and XML-RPC ======
  
 +You're using the ''​xmlrpclib''​ stuff to access Bugzilla over XML-RPC. But there'​s a problem. Bugzilla requires you to log in first, and then sets a login cookie which you must return on all subsequent operations.
 +
 +The solution is to create a ''​Transport''​ that collects cookies set by the server and returns them on all subsequent operations.
 +
 +But ''​xmlrpclib.Transport''​ does not have any interface hooks that let you do that. Yes, you could write your own ''​Transport''​ class, but that will spend most of its time duplicating the functionality in ''​xmlrpclib.Transport'',​ which isn't good. Oh, and to capture the cookies you need to examine the response from the server, and there'​s no way to do that across versions 2.4-2.7 of Python without replacing the entire ''​request()''​ method. Which is in turn tricky because of the internal changes between those Python versions.
 +
 +Oh, and you also want to have HTTP and HTTPS transports, but without repeating yourself.
 +
 +Here's my solution. It uses multiple inheritance to generate transport classes without repeating the ''​request()''​ implementation,​ which is in turn carefully crafted to work across Pythons 2.4-2.7 inclusive.
 +
 +If only ''​xmlrpclib.Transport''​ had had some better hooks, this could have been a lot easier.
 +
 +<code python>
 +class cookietransportrequest:​
 +    """​A Transport request method that retains cookies over its lifetime.
 +
 +    The regular xmlrpclib transports ignore cookies. Which causes
 +    a bit of a problem when you need a cookie-based login, as with
 +    the Bugzilla XMLRPC interface.
 +
 +    So this is a helper for defining a Transport which looks for
 +    cookies being set in responses and saves them to add to all future
 +    requests.
 +    """​
 +
 +    # Inspiration drawn from
 +    # http://​blog.godson.in/​2010/​09/​how-to-make-python-xmlrpclib-client.html
 +    # http://​www.itkovian.net/​base/​transport-class-for-pythons-xml-rpc-lib/​
 +    #
 +    # Note this must be an old-style class so that __init__ handling works
 +    # correctly with the old-style Transport class. If you make this class
 +    # a new-style class, Transport.__init__() won't be called.
 +
 +    cookies = []
 +    def send_cookies(self,​ connection):​
 +        if self.cookies:​
 +            for cookie in self.cookies:​
 +                connection.putheader("​Cookie",​ cookie)
 +
 +    def request(self,​ host, handler, request_body,​ verbose=0):
 +        self.verbose = verbose
 +
 +        # issue XML-RPC request
 +        h = self.make_connection(host)
 +        if verbose:
 +            h.set_debuglevel(1)
 +
 +        self.send_request(h,​ handler, request_body)
 +        self.send_host(h,​ host)
 +        self.send_cookies(h)
 +        self.send_user_agent(h)
 +        self.send_content(h,​ request_body)
 +
 +        # Deal with differences between Python 2.4-2.6 and 2.7.
 +        # In the former h is a HTTP(S). In the latter it's a
 +        # HTTP(S)Connection. Luckily, the 2.4-2.6 implementation of
 +        # HTTP(S) has an underlying HTTP(S)Connection,​ so extract
 +        # that and use it.
 +        try:
 +            response = h.getresponse()
 +        except AttributeError:​
 +            response = h._conn.getresponse()
 +
 +        # Add any cookie definitions to our list.
 +        for header in response.msg.getallmatchingheaders("​Set-Cookie"​):​
 +            val = header.split(":​ ", 1)[1]
 +            cookie = val.split(";",​ 1)[0]
 +            self.cookies.append(cookie)
 +
 +        if response.status != 200:
 +            raise xmlrpclib.ProtocolError(host + handler, response.status,​
 +                                          response.reason,​ response.msg.headers)
 +
 +        payload = response.read()
 +        parser, unmarshaller = self.getparser()
 +        parser.feed(payload)
 +        parser.close()
 +
 +        return unmarshaller.close()
 +
 +class cookietransport(cookietransportrequest,​ xmlrpclib.Transport):​
 +    pass
 +
 +class cookiesafetransport(cookietransportrequest,​ xmlrpclib.SafeTransport):​
 +    pass
 +</​code>​
 +
 +Now choose the appropriate transport:
 +
 +<code python>
 +    proxy = xmlrpclib.ServerProxy(url,​ transport(url))
 +    proxy.User.login(dict(login=user,​ password=passwd))
 +
 +    def transport(self,​ uri):
 +        """​Return an appropriate Transport for the URI.
 +
 +        If the URI type is https, return a CookieSafeTransport.
 +        If the type is http, return a CookieTransport.
 +        """​
 +        if urlparse.urlparse(uri,​ "​http"​)[0] == "​https":​
 +            return cookiesafetransport()
 +        else:
 +            return cookietransport()
 +</​code>​
 
xmlrpccookies.txt ยท Last modified: 2012/01/06 17:41 by jim
chimeric.de = chi`s home Creative Commons License Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0