Package ndg :: Package soap :: Module client
[hide private]

Source Code for Module ndg.soap.client

  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 
15 16 17 -class SOAPClientError(Exception):
18 """Base class for SOAP Client exceptions"""
19
20 -class SOAPParseError(SOAPClientError):
21 """Error parsing SOAP response"""
22
23 24 -class 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
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 53 -class _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 74 -class 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 92 -class SOAPResponseBase(_SoapIOBase):
93 """Interface for SOAP responses"""
94 95 import httplib 96 import urllib2 97 from urllib import addinfourl
98 99 -class 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 124 -class SOAPResponseError(UrlLib2SOAPClientError):
125 """Raise for invalid SOAP response from server"""
126
127 -class HTTPException(UrlLib2SOAPClientError):
128 """Server returned HTTP code error code"""
129
130 -class UrlLib2SOAPRequest(SOAPRequestBase):
131 """Interface for UrlLib2 based SOAP Requests""" 132
133 134 -class 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 145 -class 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 174 -class 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 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