1 """SOAP client module for NDG SAML - for use with SAML SOAP binding
2
3 NERC 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 $'
11 import logging
12 log = logging.getLogger(__name__)
13
14 from ndg.soap import SOAPEnvelopeBase
18 """Base class for SOAP Client exceptions"""
19
21 """Error parsing SOAP response"""
22
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
34
37
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
54 """Base class for request and response classes"""
55
57 self.__envelope = None
58
60 return self.__envelope
61
68
69 envelope = property(fget=_getEnvelope,
70 fset=_setEnvelope,
71 doc="SOAP Envelope object used in request/response")
72
75 """Interface for SOAP requests"""
77 self.__url = None
78 self.__envelope = None
79
82
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
93 """Interface for SOAP responses"""
94
95 import httplib
96 import urllib2
97 from urllib import addinfourl
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
106 Exception.__init__(self, *arg, **kw)
107 self.__urllib2Response = None
108
110 return self.__urllib2Response
111
118
119 urllib2Response = property(_getUrllib2Response,
120 _setUrllib2Response,
121 doc="Urllib2Response")
122
125 """Raise for invalid SOAP response from server"""
126
128 """Server returned HTTP code error code"""
129
131 """Interface for UrlLib2 based SOAP Requests"""
132
135 """Interface for UrlLib2 based SOAP Responses"""
137 self.__fileobject = None
138
139 @property
141 "urllib2 file object returned from request"
142 return self.__fileobject
143
146 """Extend dict type to make keys capitalized. Keys must be string type"""
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
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
169 """Explicit copy implementation to ensure CapitalizedKeysDict return
170 type"""
171 return CapitalizedKeysDict(self)
172
175 """urllib2 based SOAP Client"""
176 DEFAULT_HTTP_HEADER = CapitalizedKeysDict({'Content-type': 'text/xml'})
177
185
186 @property
188 "Set HTTP header fields in this dict object"
189 return self.__httpHeader
190
192 return self.__httpHeader.get('Soapaction')
193
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
205 return self.__timeout
206
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
218 return self.__openerDirector
219
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 logLevel = log.getEffectiveLevel()
254 if logLevel <= logging.DEBUG:
255 from ndg.soap.utils.etree import prettyPrint
256 log.debug("SOAP Request:")
257 log.debug("_"*80)
258 log.debug(prettyPrint(soapRequest.envelope.elem))
259
260 soapResponse = UrlLib2SOAPResponse()
261 urllib2Request = urllib2.Request(soapRequest.url)
262 for i in self.httpHeader.items():
263 urllib2Request.add_header(*i)
264
265 response = self.openerDirector.open(urllib2Request,
266 soapRequestStr,
267 *arg)
268 if response.code != httplib.OK:
269 excep = HTTPException("Response for request to [%s] is: %d %s" %
270 (soapRequest.url,
271 response.code,
272 response.msg))
273 excep.urllib2Response = response
274 raise excep
275
276 if (response.headers.typeheader not in
277 UrlLib2SOAPClient.RESPONSE_CONTENT_TYPES):
278 responseType = ', '.join(UrlLib2SOAPClient.RESPONSE_CONTENT_TYPES)
279 excep = SOAPResponseError("Expecting %r response type; got %r for "
280 "request to [%s]" %
281 (responseType,
282 response.headers.typeheader,
283 soapRequest.url))
284 excep.urllib2Response = response
285 raise excep
286
287 soapResponse.fileObject = response
288 soapResponse.envelope = self.responseEnvelopeClass()
289
290 try:
291 soapResponse.envelope.parse(soapResponse.fileObject)
292 except Exception, e:
293 raise SOAPParseError("%r type error raised parsing response for "
294 "request to [%s]: %s"
295 % (type(e), soapRequest.url, e))
296
297 if logLevel <= logging.DEBUG:
298 from ndg.soap.utils.etree import prettyPrint
299 log.debug("SOAP Response:")
300 log.debug("_"*80)
301 log.debug(prettyPrint(soapResponse.envelope.elem))
302
303 return soapResponse
304