upnp port forwarding
[p2pool.git] / nattraverso / pynupnp / soap.py
1 """
2 This module is a SOAP client using twisted's deferreds.
3 It uses the SOAPpy package.
4
5 @author: Raphael Slinckx
6 @copyright: Copyright 2005
7 @license: LGPL
8 @contact: U{raphael@slinckx.net<mailto:raphael@slinckx.net>}
9 @version: 0.1.0
10 """
11
12 __revision__ = "$id"
13
14 import SOAPpy, logging
15 from SOAPpy.Config import Config
16 from twisted.web import client, error
17
18 #General config
19 Config.typed = False
20
21 class SoapError(Exception):
22         """
23         This is a SOAP error message, not an HTTP error message.
24         
25         The content of this error is a SOAPpy structure representing the
26         SOAP error message.
27         """
28         pass
29
30 class SoapProxy:
31         """
32         Proxy for an url to which we send SOAP rpc calls.
33         """
34         def __init__(self, url, prefix):
35                 """
36                 Init the proxy, it will connect to the given url, using the
37                 given soap namespace.
38                 
39                 @param url: The url of the remote host to call
40                 @param prefix: The namespace prefix to use, eg.
41                         'urn:schemas-upnp-org:service:WANIPConnection:1'
42                 """
43                 logging.debug("Soap Proxy: '%s', prefix: '%s'", url, prefix)
44                 self._url = url
45                 self._prefix = prefix
46                 
47         def call(self, method, **kwargs):
48                 """
49                 Call the given remote method with the given arguments, as keywords.
50                 
51                 Returns a deferred, called with SOAPpy structure representing
52                 the soap response.
53                 
54                 @param method: The method name to call, eg. 'GetExternalIP'
55                 @param kwargs: The parameters of the call, as keywords
56                 @return: A deferred called with the external ip address of this host
57                 @rtype: L{twisted.internet.defer.Deferred}
58                 """
59                 payload = SOAPpy.buildSOAP(method=method, config=Config, namespace=self._prefix, kw=kwargs)
60                 # Here begins the nasty hack
61                 payload = payload.replace(
62                                         # Upnp wants s: instead of SOAP-ENV
63                                         'SOAP-ENV','s').replace(
64                                         # Doesn't seem to like these encoding stuff
65                                         'xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"', '').replace(
66                                         'SOAP-ENC:root="1"', '').replace(
67                                         # And it wants u: instead of ns1 namespace for arguments..
68                                         'ns1','u')
69
70                 logging.debug("SOAP Payload:\n%s", payload)
71                 
72                 return client.getPage(self._url, postdata=payload, method="POST",
73                         headers={'content-type': 'text/xml',    'SOAPACTION': '%s#%s' % (self._prefix, method)}
74         ).addCallbacks(self._got_page, self._got_error)
75
76         def _got_page(self, result):
77                 """
78                 The http POST command was successful, we parse the SOAP
79                 answer, and return it.
80                 
81                 @param result: the xml content
82                 """
83                 parsed = SOAPpy.parseSOAPRPC(result)
84                 
85                 logging.debug("SOAP Answer:\n%s", result)
86                 logging.debug("SOAP Parsed Answer: %r", parsed)
87                 
88                 return parsed
89                                 
90         def _got_error(self, res):
91                 """
92                 The HTTP POST command did not succeed, depending on the error type:
93                         - it's a SOAP error, we parse it and return a L{SoapError}.
94                         - it's another type of error (http, other), we raise it as is
95                 """
96                 logging.debug("SOAP Error:\n%s", res)
97                 
98                 if isinstance(res.value, error.Error):
99                         try:
100                                 logging.debug("SOAP Error content:\n%s", res.value.response)
101                                 raise SoapError(SOAPpy.parseSOAPRPC(res.value.response)["detail"])
102                         except:
103                                 raise
104                 raise Exception(res.value)