simple_ssl_context() is now a helper exposed in module sfa.util.ssl
[sfa.git] / sfa / client / sfaserverproxy.py
1 # XMLRPC-specific code for SFA Client
2
3 from sfa.util.ssl import simple_ssl_context
4
5 import xmlrpc.client
6 import http.client
7
8 try:
9     from sfa.util.sfalogging import logger
10 except:
11     import logging
12     logger = logging.getLogger('sfaserverproxy')
13
14 ##
15 # ServerException, ExceptionUnmarshaller
16 #
17 # Used to convert server exception strings back to an exception.
18 #    from usenet, Raghuram Devarakonda
19
20
21 class ServerException(Exception):
22     pass
23
24
25 class ExceptionUnmarshaller(xmlrpc.client.Unmarshaller):
26
27     def close(self):
28         try:
29             return xmlrpc.client.Unmarshaller.close(self)
30         except xmlrpc.client.Fault as e:
31             raise ServerException(e.faultString)
32
33 ##
34 # XMLRPCTransport
35 #
36 # A transport for XMLRPC that works on top of HTTPS
37
38 # targetting only python-2.7 we can get rid of some older code
39
40
41 class XMLRPCTransport(xmlrpc.client.Transport):
42
43     def __init__(self, key_file=None, cert_file=None, timeout=None):
44         xmlrpc.client.Transport.__init__(self)
45         self.timeout = timeout
46         self.key_file = key_file
47         self.cert_file = cert_file
48
49     def make_connection(self, host):
50         # create a HTTPS connection object from a host descriptor
51         # host may be a string, or a (host, x509-dict) tuple
52         host, extra_headers, x509 = self.get_host_info(host)
53         conn = http.client.HTTPSConnection(
54             host, None, key_file=self.key_file,
55             cert_file=self.cert_file, context=simple_ssl_context())
56
57         # Some logic to deal with timeouts. It appears that some (or all) versions
58         # of python don't set the timeout after the socket is created. We'll do it
59         # ourselves by forcing the connection to connect, finding the socket, and
60         # calling settimeout() on it. (tested with python 2.6)
61         if self.timeout:
62             if hasattr(conn, 'set_timeout'):
63                 conn.set_timeout(self.timeout)
64
65             if hasattr(conn, "_conn"):
66                 # HTTPS is a wrapper around HTTPSConnection
67                 real_conn = conn._conn
68             else:
69                 real_conn = conn
70             conn.connect()
71             if hasattr(real_conn, "sock") and hasattr(real_conn.sock, "settimeout"):
72                 real_conn.sock.settimeout(float(self.timeout))
73
74         return conn
75
76     def getparser(self):
77         unmarshaller = ExceptionUnmarshaller()
78         parser = xmlrpc.client.ExpatParser(unmarshaller)
79         return parser, unmarshaller
80
81
82 class XMLRPCServerProxy(xmlrpc.client.ServerProxy):
83
84     def __init__(self, url, transport, allow_none=True, verbose=False):
85         # remember url for GetVersion
86         # xxx not sure this is still needed as SfaServerProxy has this too
87         self.url = url
88         xmlrpc.client.ServerProxy.__init__(
89             self, url, transport, allow_none=allow_none,
90             context=simple_ssl_context(), verbose=verbose)
91
92     def __getattr__(self, attr):
93         logger.debug("xml-rpc %s method:%s" % (self.url, attr))
94         return xmlrpc.client.ServerProxy.__getattr__(self, attr)
95
96 # the object on which we can send methods that get sent over xmlrpc
97
98
99 class SfaServerProxy:
100
101     def __init__(self, url, keyfile, certfile, verbose=False, timeout=None):
102         self.url = url
103         self.keyfile = keyfile
104         self.certfile = certfile
105         self.verbose = verbose
106         self.timeout = timeout
107         # an instance of xmlrpc.client.ServerProxy
108         transport = XMLRPCTransport(keyfile, certfile, timeout)
109         self.serverproxy = XMLRPCServerProxy(
110             url, transport, allow_none=True, verbose=verbose)
111
112     # this is python magic to return the code to run when
113     # SfaServerProxy receives a method call
114     # so essentially we send the same method with identical arguments
115     # to the server_proxy object
116     def __getattr__(self, name):
117         def func(*args, **kwds):
118             return getattr(self.serverproxy, name)(*args, **kwds)
119         return func