• Main Page
  • Related Pages
  • Modules
  • Namespaces
  • Data Structures
  • Files
  • File List
  • Globals

contrib/opal/ZSI/build/lib/ZSI/ServiceContainer.py

00001 #! /usr/bin/env python
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         #try:
00094         #    raise NotAuthorized()
00095         #except Exception, e:
00096             #return SendFault(FaultFromException(e, 0, None), code=401, **kw)
00097             ##return SendFault(FaultFromException(NotAuthorized(), 0, None), code=401, **kw)
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     # Verify if Signed
00113     service.verify(ps)
00114 
00115     # If No response just return.
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     # Create Signatures
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             # Faulted while processing; assume it's in the header.
00292             self.send_fault(FaultFromException(e, 1, sys.exc_info()[2]))
00293         else:
00294             # Keep track of calls
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             # Clean up after the call
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             # Faulted while processing; assume it's in the header.
00339             self.send_fault(FaultFromException(e, 1, sys.exc_info()[2]))
00340         else:
00341             # Keep track of calls
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             # Clean up after the call
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                 # update the soap:location tag in the wsdl to the actual server
00366                 #   location
00367                 # - default to 'http' as protocol, or use server-specified protocol
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

Generated on Wed Oct 20 2010 11:12:16 for APBS by  doxygen 1.7.2