source: TI12-security/trunk/ndg_saml/ndg/soap/client.py @ 7560

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/ndg_saml/ndg/soap/client.py@7560
Revision 7560, 10.6 KB checked in by pjkersha, 9 years ago (diff)

Merged ndg.soap back into ndg_saml package - simpler to keep it together.

Line 
1"""SOAP client module for NDG SAML - for use with SAML SOAP binding
2
3NERC DataGrid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "27/07/09"
7__copyright__ = "(C) 2010 Science and Technology Facilities Council"
8__license__ = "http://www.apache.org/licenses/LICENSE-2.0"
9__contact__ = "Philip.Kershaw@stfc.ac.uk"
10__revision__ = '$Id: client.py 7131 2010-06-30 13:37:48Z pjkersha $'
11import logging
12log = logging.getLogger(__name__)
13
14from ndg.soap import SOAPEnvelopeBase
15
16
17class SOAPClientError(Exception):
18    """Base class for SOAP Client exceptions"""
19
20class SOAPParseError(SOAPClientError):
21    """Error parsing SOAP response"""
22   
23           
24class SOAPClientBase(object):
25    """Handle client request to a SOAP Service
26    @cvar RESPONSE_CONTENT_TYPES: expected content type to be returned in a
27    response from a service
28    @type RESPONSE_CONTENT_TYPES: string
29    """
30    RESPONSE_CONTENT_TYPES = ('text/xml', )
31   
32    def __init__(self):
33        self.__responseEnvelopeClass = None
34
35    def _getResponseEnvelopeClass(self):
36        return self.__responseEnvelopeClass
37
38    def _setResponseEnvelopeClass(self, value):
39        if not issubclass(value, SOAPEnvelopeBase):
40            raise TypeError("Setting SOAP envelope class: expecting %r, got "
41                            "%r" % (SOAPEnvelopeBase, type(value)))
42        self.__responseEnvelopeClass = value
43
44    responseEnvelopeClass = property(fget=_getResponseEnvelopeClass, 
45                                     fset=_setResponseEnvelopeClass, 
46                                     doc="Set the class for handling "
47                                         "the SOAP envelope responses")
48     
49    def send(self, soapRequest):
50        raise NotImplementedError()
51
52
53class _SoapIOBase(object):
54    """Base class for request and response classes"""
55   
56    def __init__(self):
57        self.__envelope = None
58
59    def _getEnvelope(self):
60        return self.__envelope
61
62    def _setEnvelope(self, value):
63        if not isinstance(value, SOAPEnvelopeBase):
64            raise TypeError('Setting SOAP envelope object: expecting %r; got '
65                            '%r' % (SOAPEnvelopeBase, type(value)))
66                           
67        self.__envelope = value
68
69    envelope = property(fget=_getEnvelope, 
70                        fset=_setEnvelope, 
71                        doc="SOAP Envelope object used in request/response")
72
73       
74class SOAPRequestBase(object):
75    """Interface for SOAP requests"""
76    def __init__(self):
77        self.__url = None
78        self.__envelope = None
79
80    def _getUrl(self):
81        return self.__url
82
83    def _setUrl(self, value):
84        if not isinstance(value, basestring):
85            raise TypeError('Setting request URL: expecting %r; got '
86                            '%r' % (basestring, type(value)))
87        self.__url = value
88
89    url = property(fget=_getUrl, fset=_setUrl, doc="URL of SOAP endpoint")
90
91   
92class SOAPResponseBase(_SoapIOBase):
93    """Interface for SOAP responses"""
94
95import httplib
96import urllib2
97from urllib import addinfourl
98 
99class UrlLib2SOAPClientError(SOAPClientError):
100    """Specialisation to enable the urllib2 response to be included in the
101    exception instance as context information for the caller
102    """
103    URLLIB2RESPONSE_TYPE = addinfourl
104   
105    def __init__(self, *arg, **kw):
106        Exception.__init__(self, *arg, **kw)
107        self.__urllib2Response = None
108
109    def _getUrllib2Response(self):
110        return self.__urllib2Response
111
112    def _setUrllib2Response(self, value):
113        if not isinstance(value, UrlLib2SOAPClientError.URLLIB2RESPONSE_TYPE):
114            raise TypeError('Expecting %r type for "urllib2Response"; got %r' %
115                            (UrlLib2SOAPClientError.URLLIB2RESPONSE_TYPE, 
116                             type(value)))
117        self.__urllib2Response = value
118
119    urllib2Response = property(_getUrllib2Response, 
120                               _setUrllib2Response, 
121                               doc="Urllib2Response")
122
123
124class SOAPResponseError(UrlLib2SOAPClientError):
125    """Raise for invalid SOAP response from server"""
126       
127class HTTPException(UrlLib2SOAPClientError):
128    """Server returned HTTP code error code"""
129
130class UrlLib2SOAPRequest(SOAPRequestBase): 
131    """Interface for UrlLib2 based SOAP Requests"""
132   
133   
134class UrlLib2SOAPResponse(SOAPResponseBase):
135    """Interface for UrlLib2 based SOAP Responses"""
136    def __init__(self):
137        self.__fileobject = None
138       
139    @property
140    def fileobject(self):
141        "urllib2 file object returned from request"
142        return self.__fileobject
143
144
145class CapitalizedKeysDict(dict):
146    """Extend dict type to make keys capitalized.  Keys must be string type"""
147    def __init__(self, *arg, **kw):
148        if len(arg) > 0:
149            arg = list(arg)
150           
151            if isinstance(arg[0], dict):
152                arg[0] = [(k.capitalize(), v) for k, v in arg[0].items()]
153            else:
154                arg[0] = [(k.capitalize(), v) for k, v in arg[0]] 
155               
156            arg = tuple(arg)
157       
158        kw = dict([(k.capitalize(), v) for k, v in kw.items()])
159       
160        super(CapitalizedKeysDict, self).__init__(*arg, **kw)
161       
162    def __setitem__(self, k, v):
163        if not isinstance(k, basestring):
164            raise TypeError('Key must be string type; got %r' % type(k))
165       
166        super(CapitalizedKeysDict, self).__setitem__(k.capitalize(), v)
167     
168    def copy(self):
169        """Explicit copy implementation to ensure CapitalizedKeysDict return
170        type"""
171        return CapitalizedKeysDict(self)
172   
173   
174class UrlLib2SOAPClient(SOAPClientBase):
175    """urllib2 based SOAP Client"""
176    DEFAULT_HTTP_HEADER = CapitalizedKeysDict({'Content-type': 'text/xml'})
177   
178    def __init__(self):
179        super(UrlLib2SOAPClient, self).__init__()
180        self.__openerDirector = urllib2.OpenerDirector()
181        self.__openerDirector.add_handler(urllib2.UnknownHandler())
182        self.__openerDirector.add_handler(urllib2.HTTPHandler())
183        self.__timeout = None
184        self.__httpHeader = UrlLib2SOAPClient.DEFAULT_HTTP_HEADER.copy()
185
186    @property
187    def httpHeader(self):
188        "Set HTTP header fields in this dict object"
189        return self.__httpHeader
190
191    def _getSOAPAction(self):
192        return self.__httpHeader.get('Soapaction')
193
194    def _setSOAPAction(self, value):
195        if not isinstance(value, basestring):
196            raise TypeError("Setting request soapAction: got %r, expecting "
197                            "string type" % type(value))
198        self.__httpHeader['Soapaction'] = value
199       
200    soapAction = property(fget=_getSOAPAction, 
201                          fset=_setSOAPAction, 
202                          doc="SOAPAction HTTP header field setting") 
203       
204    def _getTimeout(self):
205        return self.__timeout
206
207    def _setTimeout(self, value):
208        if not isinstance(value, (int, float)):
209            raise TypeError("Setting request timeout: got %r, expecting int "
210                            "or float type" % type(value))
211        self.__timeout = value
212
213    timeout = property(fget=_getTimeout, 
214                       fset=_setTimeout, 
215                       doc="Timeout (seconds) for requests")
216
217    def _getOpenerDirector(self):
218        return self.__openerDirector
219
220    def _setOpenerDirector(self, value):
221        """This shouldn't need to be used much in practice because __init__
222        creates one"""
223        if not isinstance(value, urllib2.OpenerDirector):
224            raise TypeError("Setting opener: expecting %r; got %r" % 
225                            (urllib2.OpenerDirector, type(value)))
226        self.__openerDirector = value
227
228    openerDirector = property(fget=_getOpenerDirector, 
229                              fset=_setOpenerDirector, 
230                              doc="urllib2.OpenerDirector defines the "
231                                  "opener(s) for handling requests")
232   
233    def send(self, soapRequest):
234        """Make a request to the given URL with a SOAP Request object"""
235       
236        if not isinstance(soapRequest, UrlLib2SOAPRequest):
237            raise TypeError('UrlLib2SOAPClient.send: expecting %r '
238                            'derived type for SOAP request, got %r' % 
239                            (self.responseEnvelopeClass, type(soapRequest)))
240           
241        if not isinstance(soapRequest.envelope, self.responseEnvelopeClass):
242            raise TypeError('UrlLib2SOAPClient.send: expecting %r '
243                            'derived type for SOAP envelope, got %r' % 
244                            (self.responseEnvelopeClass, type(soapRequest)))
245                           
246        if self.timeout is not None:
247            arg = (self.timeout,)
248        else:
249            arg = ()
250           
251        soapRequestStr = soapRequest.envelope.serialize()
252
253        if log.getEffectiveLevel() <= logging.DEBUG:
254            from ndg.soap.utils.etree import prettyPrint
255            log.debug("SOAP Request:")
256            log.debug("_"*80)
257            log.debug(prettyPrint(soapRequest.envelope.elem))
258
259        soapResponse = UrlLib2SOAPResponse()
260        urllib2Request = urllib2.Request(soapRequest.url) 
261        for i in self.httpHeader.items():
262            urllib2Request.add_header(*i)
263           
264        response = self.openerDirector.open(urllib2Request, 
265                                            soapRequestStr, 
266                                            *arg)
267        if response.code != httplib.OK:
268            excep = HTTPException("Response for request to [%s] is: %d %s" % 
269                                  (soapRequest.url, 
270                                   response.code, 
271                                   response.msg))
272            excep.urllib2Response = response
273            raise excep
274       
275        if (response.headers.typeheader not in 
276            UrlLib2SOAPClient.RESPONSE_CONTENT_TYPES):
277            responseType = ', '.join(UrlLib2SOAPClient.RESPONSE_CONTENT_TYPES)
278            excep = SOAPResponseError("Expecting %r response type; got %r for "
279                                      "request to [%s]" % 
280                                      (responseType, 
281                                       response.headers.typeheader,
282                                       soapRequest.url))
283            excep.urllib2Response = response
284            raise excep
285           
286        soapResponse.fileObject = response
287        soapResponse.envelope = self.responseEnvelopeClass() 
288       
289        try:
290            soapResponse.envelope.parse(soapResponse.fileObject)
291        except Exception, e:
292            raise SOAPParseError("%r type error raised parsing response for "
293                                 "request to [%s]: %s"
294                                 % (type(e), soapRequest.url, e))
295           
296        return soapResponse
Note: See TracBrowser for help on using the repository browser.