00001
00002 '''Simple Service Container
00003 -- use with wsdl2py generated modules.
00004 '''
00005
00006 import urlparse, types, os, sys, cStringIO as StringIO, thread,re
00007 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
00008 from ZSI import ParseException, FaultFromException, FaultFromZSIException, Fault
00009 from ZSI import _copyright, _seqtypes, _get_element_nsuri_name, resolvers
00010 from ZSI import _get_idstr
00011 from ZSI.address import Address
00012 from ZSI.parse import ParsedSoap
00013 from ZSI.writer import SoapWriter
00014 from ZSI.dispatch import _ModPythonSendXML, _ModPythonSendFault, _CGISendXML, _CGISendFault
00015 from ZSI.dispatch import SOAPRequestHandler as BaseSOAPRequestHandler
00016
00017 """
00018 Functions:
00019 _Dispatch
00020 AsServer
00021 GetSOAPContext
00022
00023 Classes:
00024 SOAPContext
00025 NoSuchService
00026 PostNotSpecified
00027 SOAPActionNotSpecified
00028 ServiceSOAPBinding
00029 WSAResource
00030 SimpleWSResource
00031 SOAPRequestHandler
00032 ServiceContainer
00033 """
00034 class NoSuchService(Exception): pass
00035 class UnknownRequestException(Exception): pass
00036 class PostNotSpecified(Exception): pass
00037 class SOAPActionNotSpecified(Exception): pass
00038 class WSActionException(Exception): pass
00039 class WSActionNotSpecified(WSActionException): pass
00040 class NotAuthorized(Exception): pass
00041 class ServiceAlreadyPresent(Exception): pass
00042
00043
00044 class SOAPContext:
00045 def __init__(self, container, xmldata, ps, connection, httpheaders,
00046 soapaction):
00047
00048 self.container = container
00049 self.xmldata = xmldata
00050 self.parsedsoap = ps
00051 self.connection = connection
00052 self.httpheaders= httpheaders
00053 self.soapaction = soapaction
00054
00055 _contexts = dict()
00056 def GetSOAPContext():
00057 global _contexts
00058 return _contexts[thread.get_ident()]
00059
00060 def _Dispatch(ps, server, SendResponse, SendFault, post, action, nsdict={}, **kw):
00061 '''Send ParsedSoap instance to ServiceContainer, which dispatches to
00062 appropriate service via post, and method via action. Response is a
00063 self-describing pyobj, which is passed to a SoapWriter.
00064
00065 Call SendResponse or SendFault to send the reply back, appropriately.
00066 server -- ServiceContainer instance
00067
00068 '''
00069 localURL = 'http://%s:%d%s' %(server.server_name,server.server_port,post)
00070 address = action
00071 service = server.getNode(post)
00072 isWSResource = False
00073 if isinstance(service, WSAResource):
00074 isWSResource = True
00075 service.setServiceURL(localURL)
00076 address = Address()
00077 try:
00078 address.parse(ps)
00079 except Exception, e:
00080 return SendFault(FaultFromException(e, 0, sys.exc_info()[2]), **kw)
00081 if action and action != address.getAction():
00082 e = WSActionException('SOAP Action("%s") must match WS-Action("%s") if specified.' \
00083 %(action,address.getAction()))
00084 return SendFault(FaultFromException(e, 0, None), **kw)
00085 action = address.getAction()
00086
00087 if isinstance(service, ServiceInterface) is False:
00088 e = NoSuchService('no service at POST(%s) in container: %s' %(post,server))
00089 return SendFault(FaultFromException(e, 0, sys.exc_info()[2]), **kw)
00090
00091 if not service.authorize(None, post, action):
00092 return SendFault(Fault(Fault.Server, "Not authorized"), code=401)
00093
00094
00095
00096
00097
00098
00099 try:
00100 method = service.getOperation(ps, address)
00101 except Exception, e:
00102 return SendFault(FaultFromException(e, 0, sys.exc_info()[2]), **kw)
00103
00104 try:
00105 if isWSResource is True:
00106 request,result = method(ps, address)
00107 else:
00108 request,result = method(ps)
00109 except Exception, e:
00110 return SendFault(FaultFromException(e, 0, sys.exc_info()[2]), **kw)
00111
00112
00113 service.verify(ps)
00114
00115
00116 if result is None:
00117 return SendResponse('', **kw)
00118
00119 sw = SoapWriter(nsdict=nsdict)
00120 try:
00121 sw.serialize(result)
00122 except Exception, e:
00123 return SendFault(FaultFromException(e, 0, sys.exc_info()[2]), **kw)
00124
00125 if isWSResource is True:
00126 action = service.getResponseAction(ps, action)
00127 addressRsp = Address(action=action)
00128 try:
00129 addressRsp.setResponseFromWSAddress(address, localURL)
00130 addressRsp.serialize(sw)
00131 except Exception, e:
00132 return SendFault(FaultFromException(e, 0, sys.exc_info()[2]), **kw)
00133
00134
00135 service.sign(sw)
00136
00137 try:
00138 soapdata = str(sw)
00139 return SendResponse(soapdata, **kw)
00140 except Exception, e:
00141 return SendFault(FaultFromException(e, 0, sys.exc_info()[2]), **kw)
00142
00143
00144 def AsServer(port=80, services=()):
00145 '''port --
00146 services -- list of service instances
00147 '''
00148 address = ('', port)
00149 sc = ServiceContainer(address, services)
00150 sc.serve_forever()
00151
00152
00153 class ServiceInterface:
00154 '''Defines the interface for use with ServiceContainer Handlers.
00155
00156 class variables:
00157 soapAction -- dictionary of soapAction keys, and operation name values.
00158 These are specified in the WSDL soap bindings. There must be a
00159 class method matching the operation name value. If WS-Action is
00160 used the keys are WS-Action request values, according to the spec
00161 if soapAction and WS-Action is specified they must be equal.
00162
00163 wsAction -- dictionary of operation name keys and WS-Action
00164 response values. These values are specified by the portType.
00165
00166 root -- dictionary of root element keys, and operation name values.
00167
00168 '''
00169 soapAction = {}
00170 wsAction = {}
00171 root = {}
00172
00173 def __init__(self, post):
00174 self.post = post
00175
00176 def authorize(self, auth_info, post, action):
00177 return 1
00178
00179 def __str__(self):
00180 return '%s(%s) POST(%s)' %(self.__class__.__name__, _get_idstr(self), self.post)
00181
00182 def sign(self, sw):
00183 return
00184
00185 def verify(self, ps):
00186 return
00187
00188 def getPost(self):
00189 return self.post
00190
00191 def getOperation(self, ps, action):
00192 '''Returns a method of class.
00193 action -- soapAction value
00194 '''
00195 opName = self.getOperationName(ps, action)
00196 return getattr(self, opName)
00197
00198 def getOperationName(self, ps, action):
00199 '''Returns operation name.
00200 action -- soapAction value
00201 '''
00202 method = self.root.get(_get_element_nsuri_name(ps.body_root)) or \
00203 self.soapAction.get(action)
00204 if method is None:
00205 raise UnknownRequestException, \
00206 'failed to map request to a method: action(%s), root%s' %(action,_get_element_nsuri_name(ps.body_root))
00207 return method
00208
00209
00210 class ServiceSOAPBinding(ServiceInterface):
00211 '''Binding defines the set of wsdl:binding operations, it takes as input a
00212 ParsedSoap instance and parses it into a pyobj. It returns a response pyobj.
00213 '''
00214 def __init__(self, post):
00215 ServiceInterface.__init__(self, post)
00216
00217 def __call___(self, action, ps):
00218 return self.getOperation(ps, action)(ps)
00219
00220
00221 class WSAResource(ServiceSOAPBinding):
00222 '''Simple WSRF service, performs method resolutions based
00223 on WS-Action values rather than SOAP Action.
00224
00225 class variables:
00226 encoding
00227 wsAction -- Must override to set output Action values.
00228 soapAction -- Must override to set input Action values.
00229 '''
00230 encoding = "UTF-8"
00231
00232 def __init__(self, post):
00233 '''
00234 post -- POST value
00235 '''
00236 assert isinstance(self.soapAction, dict), "soapAction must be a dict"
00237 assert isinstance(self.wsAction, dict), "wsAction must be a dict"
00238 ServiceSOAPBinding.__init__(self, post)
00239
00240 def __call___(self, action, ps, address):
00241 return self.getOperation(ps, action)(ps, address)
00242
00243 def getServiceURL(self):
00244 return self._url
00245
00246 def setServiceURL(self, url):
00247 self._url = url
00248
00249 def getOperation(self, ps, address):
00250 '''Returns a method of class.
00251 address -- ws-address
00252 '''
00253 action = address.getAction()
00254 opName = self.getOperationName(ps, action)
00255 return getattr(self, opName)
00256
00257 def getResponseAction(self, ps, action):
00258 '''Returns response WS-Action if available
00259 action -- request WS-Action value.
00260 '''
00261 opName = self.getOperationName(ps, action)
00262 if self.wsAction.has_key(opName) is False:
00263 raise WSActionNotSpecified, 'wsAction dictionary missing key(%s)' %opName
00264 return self.wsAction[opName]
00265
00266 def do_POST(self):
00267 '''The POST command. This is called by HTTPServer, not twisted.
00268 action -- SOAPAction(HTTP header) or wsa:Action(SOAP:Header)
00269 '''
00270 global _contexts
00271
00272 soapAction = self.headers.getheader('SOAPAction')
00273 post = self.path
00274 if not post:
00275 raise PostNotSpecified, 'HTTP POST not specified in request'
00276 if soapAction:
00277 soapAction = soapAction.strip('\'"')
00278 post = post.strip('\'"')
00279 try:
00280 ct = self.headers['content-type']
00281 if ct.startswith('multipart/'):
00282 cid = resolvers.MIMEResolver(ct, self.rfile)
00283 xml = cid.GetSOAPPart()
00284 ps = ParsedSoap(xml, resolver=cid.Resolve, readerclass=DomletteReader)
00285 else:
00286 length = int(self.headers['content-length'])
00287 ps = ParsedSoap(self.rfile.read(length), readerclass=DomletteReader)
00288 except ParseException, e:
00289 self.send_fault(FaultFromZSIException(e))
00290 except Exception, e:
00291
00292 self.send_fault(FaultFromException(e, 1, sys.exc_info()[2]))
00293 else:
00294
00295 thread_id = thread.get_ident()
00296 _contexts[thread_id] = SOAPContext(self.server, xml, ps,
00297 self.connection,
00298 self.headers, soapAction)
00299
00300 try:
00301 _Dispatch(ps, self.server, self.send_xml, self.send_fault,
00302 post=post, action=soapAction)
00303 except Exception, e:
00304 self.send_fault(FaultFromException(e, 0, sys.exc_info()[2]))
00305
00306
00307 if _contexts.has_key(thread_id):
00308 del _contexts[thread_id]
00309
00310
00311 class SOAPRequestHandler(BaseSOAPRequestHandler):
00312 '''SOAP handler.
00313 '''
00314 def do_POST(self):
00315 '''The POST command.
00316 action -- SOAPAction(HTTP header) or wsa:Action(SOAP:Header)
00317 '''
00318 soapAction = self.headers.getheader('SOAPAction')
00319 post = self.path
00320 if not post:
00321 raise PostNotSpecified, 'HTTP POST not specified in request'
00322 if soapAction:
00323 soapAction = soapAction.strip('\'"')
00324 post = post.strip('\'"')
00325 try:
00326 ct = self.headers['content-type']
00327 if ct.startswith('multipart/'):
00328 cid = resolvers.MIMEResolver(ct, self.rfile)
00329 xml = cid.GetSOAPPart()
00330 ps = ParsedSoap(xml, resolver=cid.Resolve)
00331 else:
00332 length = int(self.headers['content-length'])
00333 xml = self.rfile.read(length)
00334 ps = ParsedSoap(xml)
00335 except ParseException, e:
00336 self.send_fault(FaultFromZSIException(e))
00337 except Exception, e:
00338
00339 self.send_fault(FaultFromException(e, 1, sys.exc_info()[2]))
00340 else:
00341
00342 thread_id = thread.get_ident()
00343 _contexts[thread_id] = SOAPContext(self.server, xml, ps,
00344 self.connection,
00345 self.headers, soapAction)
00346
00347 try:
00348 _Dispatch(ps, self.server, self.send_xml, self.send_fault,
00349 post=post, action=soapAction)
00350 except Exception, e:
00351 self.send_fault(FaultFromException(e, 0, sys.exc_info()[2]))
00352
00353
00354 if _contexts.has_key(thread_id):
00355 del _contexts[thread_id]
00356
00357 def do_GET(self):
00358 '''The GET command.
00359 '''
00360 if self.path.lower().endswith("?wsdl"):
00361 service_path = self.path[:-5]
00362 service = self.server.getNode(service_path)
00363 if hasattr(service, "_wsdl"):
00364 wsdl = service._wsdl
00365
00366
00367
00368 proto = 'http'
00369 if hasattr(self.server,'proto'):
00370 proto = self.server.proto
00371 serviceUrl = '%s://%s:%d%s' % (proto,
00372 self.server.server_name,
00373 self.server.server_port,
00374 service_path)
00375 soapAddress = '<soap:address location="%s"/>' % serviceUrl
00376 wsdlre = re.compile('<soap:address[^>]*>',re.IGNORECASE)
00377 wsdl = re.sub(wsdlre,soapAddress,wsdl)
00378 self.send_xml(wsdl)
00379 else:
00380 self.send_error(404, "WSDL not available for that service [%s]." % self.path)
00381 else:
00382 self.send_error(404, "Service not found [%s]." % self.path)
00383
00384 class ServiceContainer(HTTPServer):
00385 '''HTTPServer that stores service instances according
00386 to POST values. An action value is instance specific,
00387 and specifies an operation (function) of an instance.
00388 '''
00389 class NodeTree:
00390 '''Simple dictionary implementation of a node tree
00391 '''
00392 def __init__(self):
00393 self.__dict = {}
00394
00395 def __str__(self):
00396 return str(self.__dict)
00397
00398 def listNodes(self):
00399 print self.__dict.keys()
00400
00401 def getNode(self, url):
00402 path = urlparse.urlsplit(url)[2]
00403 if path.startswith("/"):
00404 path = path[1:]
00405
00406 if self.__dict.has_key(path):
00407 return self.__dict[path]
00408 else:
00409 raise NoSuchService, 'No service(%s) in ServiceContainer' %path
00410
00411 def setNode(self, service, url):
00412 path = urlparse.urlsplit(url)[2]
00413 if path.startswith("/"):
00414 path = path[1:]
00415
00416 if not isinstance(service, ServiceSOAPBinding):
00417 raise TypeError, 'A Service must implement class ServiceSOAPBinding'
00418 if self.__dict.has_key(path):
00419 raise ServiceAlreadyPresent, 'Service(%s) already in ServiceContainer' % path
00420 else:
00421 self.__dict[path] = service
00422
00423 def removeNode(self, url):
00424 path = urlparse.urlsplit(url)[2]
00425 if path.startswith("/"):
00426 path = path[1:]
00427
00428 if self.__dict.has_key(path):
00429 node = self.__dict[path]
00430 del self.__dict[path]
00431 return node
00432 else:
00433 raise NoSuchService, 'No service(%s) in ServiceContainer' %path
00434
00435 def __init__(self, server_address, services=[], RequestHandlerClass=SOAPRequestHandler):
00436 '''server_address --
00437 RequestHandlerClass --
00438 '''
00439 HTTPServer.__init__(self, server_address, RequestHandlerClass)
00440 self._nodes = self.NodeTree()
00441 map(lambda s: self.setNode(s), services)
00442
00443 def __str__(self):
00444 return '%s(%s) nodes( %s )' %(self.__class__, _get_idstr(self), str(self._nodes))
00445
00446 def __call__(self, ps, post, action, address=None):
00447 '''ps -- ParsedSoap representing the request
00448 post -- HTTP POST --> instance
00449 action -- Soap Action header --> method
00450 address -- Address instance representing WS-Address
00451 '''
00452 method = self.getCallBack(ps, post, action)
00453 if (isinstance(method.im_self, WSAResource) or
00454 isinstance(method.im_self, SimpleWSResource)):
00455 return method(ps, address)
00456 return method(ps)
00457
00458
00459 def setNode(self, service, url=None):
00460 if url is None:
00461 url = service.getPost()
00462 self._nodes.setNode(service, url)
00463
00464 def getNode(self, url):
00465 return self._nodes.getNode(url)
00466
00467 def removeNode(self, url):
00468 self._nodes.removeNode(url)
00469
00470
00471 class SimpleWSResource(ServiceSOAPBinding):
00472
00473 def getNode(self, post):
00474 '''post -- POST HTTP value
00475 '''
00476 return self._nodes.getNode(post)
00477
00478 def setNode(self, service, post):
00479 '''service -- service instance
00480 post -- POST HTTP value
00481 '''
00482 self._nodes.setNode(service, post)
00483
00484 def getCallBack(self, ps, post, action):
00485 '''post -- POST HTTP value
00486 action -- SOAP Action value
00487 '''
00488 node = self.getNode(post)
00489 if node is None:
00490 raise NoSuchFunction
00491 if node.authorize(None, post, action):
00492 return node.getOperation(ps, action)
00493 else:
00494 raise NotAuthorized, "Authorization failed for method %s" % action
00495
00496
00497 if __name__ == '__main__': print _copyright