00001
00002
00003
00004
00005
00006 import os, sys, types, inspect
00007 from StringIO import StringIO
00008
00009
00010 from zope.interface import classProvides, implements, Interface
00011
00012
00013 from ZSI import _get_element_nsuri_name, EvaluateException, ParseException,\
00014 fault, ParsedSoap, SoapWriter
00015 from ZSI.twisted.reverse import DataHandler, ReverseHandlerChain,\
00016 HandlerChainInterface
00017
00018 """
00019 EXAMPLES:
00020
00021 See zsi/samples/WSGI
00022
00023
00024 """
00025
00026 def soapmethod(requesttypecode, responsetypecode, soapaction='',
00027 operation=None, **kw):
00028 """@soapmethod
00029 decorator function for soap methods
00030 """
00031 def _closure(func_cb):
00032 func_cb.root = (requesttypecode.nspname,requesttypecode.pname)
00033 func_cb.action = soapaction
00034 func_cb.requesttypecode = requesttypecode
00035 func_cb.responsetypecode = responsetypecode
00036 func_cb.soapmethod = True
00037 func_cb.operation = None
00038 return func_cb
00039
00040 return _closure
00041
00042
00043 class SOAPCallbackHandler:
00044 """ ps --> pyobj, pyobj --> sw
00045 class variables:
00046 writerClass -- ElementProxy implementation to use for SoapWriter instances.
00047 """
00048 classProvides(HandlerChainInterface)
00049 writerClass = None
00050
00051 @classmethod
00052 def processRequest(cls, ps, **kw):
00053 """invokes callback that should return a (request,response) tuple.
00054 representing the SOAP request and response respectively.
00055 ps -- ParsedSoap instance representing HTTP Body.
00056 request -- twisted.web.server.Request
00057 """
00058 resource = kw['resource']
00059 request = kw['request']
00060
00061 root = _get_element_nsuri_name(ps.body_root)
00062 for key,method in inspect.getmembers(resource, inspect.ismethod):
00063 if (getattr(method, 'soapmethod', False) and method.root == root):
00064 break
00065 else:
00066 raise RuntimeError, 'Missing soap callback method for root "%s"' %root
00067
00068 try:
00069 req = ps.Parse(method.requesttypecode)
00070 except Exception, ex:
00071 raise
00072 try:
00073 rsp = method.responsetypecode.pyclass()
00074 except Exception, ex:
00075 raise
00076
00077 try:
00078 req,rsp = method(req, rsp)
00079 except Exception, ex:
00080 raise
00081
00082 return rsp
00083
00084 @classmethod
00085 def processResponse(cls, output, **kw):
00086 sw = SoapWriter(outputclass=cls.writerClass)
00087 sw.serialize(output)
00088 return sw
00089
00090
00091 class SOAPHandlerChainFactory:
00092 protocol = ReverseHandlerChain
00093
00094 @classmethod
00095 def newInstance(cls):
00096 return cls.protocol(DataHandler, SOAPCallbackHandler)
00097
00098
00099 class WSGIApplication(dict):
00100 encoding = "UTF-8"
00101
00102 def __call__(self, env, start_response):
00103 """do dispatching, else process
00104 """
00105 script = env['SCRIPT_NAME']
00106 ipath = os.path.split(env['PATH_INFO'])[1:]
00107 for i in range(1, len(ipath)+1):
00108 path = os.path.join(*ipath[:i])
00109 print "PATH: ", path
00110 application = self.get(path)
00111 if application is not None:
00112 env['SCRIPT_NAME'] = script + path
00113 env['PATH_INFO'] = ''
00114 print "SCRIPT: ", env['SCRIPT_NAME']
00115 return application(env, start_response)
00116
00117 return self._request_cb(env, start_response)
00118
00119 def _request_cb(self, env, start_response):
00120 """callback method, override
00121 """
00122 start_response("404 ERROR", [('Content-Type','text/plain')])
00123 return ['Move along people, there is nothing to see to hear']
00124
00125 def putChild(self, path, resource):
00126 """
00127 """
00128 path = path.split('/')
00129 lp = len(path)
00130 if lp == 0:
00131 raise RuntimeError, 'bad path "%s"' %path
00132
00133 if lp == 1:
00134 self[path[0]] = resource
00135
00136 for i in range(len(path)):
00137 if not path[i]: continue
00138 break
00139
00140 next = self.get(path[i], None)
00141 if next is None:
00142 next = self[path[i]] = WSGIApplication()
00143
00144 next.putChild('/'.join(path[-1:]), resource)
00145
00146
00147
00148
00149 class SOAPApplication(WSGIApplication):
00150 """
00151 """
00152 factory = SOAPHandlerChainFactory
00153
00154 def __init__(self, **kw):
00155 dict.__init__(self, **kw)
00156 self.delegate = None
00157
00158 def _request_cb(self, env, start_response):
00159 """process request,
00160 """
00161 if env['REQUEST_METHOD'] == 'GET':
00162 return self._handle_GET(env, start_response)
00163
00164 if env['REQUEST_METHOD'] == 'POST':
00165 return self._handle_POST(env, start_response)
00166
00167 start_response("500 ERROR", [('Content-Type','text/plain')])
00168 s = StringIO()
00169 h = env.items(); h.sort()
00170 for k,v in h:
00171 print >>s, k,'=',`v`
00172 return [s.getvalue()]
00173
00174 def _handle_GET(self, env, start_response):
00175 if env['QUERY_STRING'].lower() == 'wsdl':
00176 start_response("200 OK", [('Content-Type','text/plain')])
00177 r = self.delegate or self
00178 return _resourceToWSDL(r)
00179
00180 start_response("404 ERROR", [('Content-Type','text/plain')])
00181 return ['NO RESOURCE FOR GET']
00182
00183 def _handle_POST(self, env, start_response):
00184 """Dispatch Method called by twisted render, creates a
00185 request/response handler chain.
00186 request -- twisted.web.server.Request
00187 """
00188 input = env['wsgi.input']
00189 data = input.read( int(env['CONTENT_LENGTH']) )
00190 mimeType = "text/xml"
00191 if self.encoding is not None:
00192 mimeType = 'text/xml; charset="%s"' % self.encoding
00193
00194 request = None
00195 resource = self.delegate or self
00196 chain = self.factory.newInstance()
00197 try:
00198 pyobj = chain.processRequest(data, request=request, resource=resource)
00199 except Exception, ex:
00200 start_response("500 ERROR", [('Content-Type',mimeType)])
00201 return [fault.FaultFromException(ex, False, sys.exc_info()[2]).AsSOAP()]
00202
00203 try:
00204 soap = chain.processResponse(pyobj, request=request, resource=resource)
00205 except Exception, ex:
00206 start_response("500 ERROR", [('Content-Type',mimeType)])
00207 return [fault.FaultFromException(ex, False, sys.exc_info()[2]).AsSOAP()]
00208
00209 start_response("200 OK", [('Content-Type',mimeType)])
00210 return [soap]
00211
00212
00213 def test(app, port=8080, host="localhost"):
00214 """
00215 """
00216 from twisted.internet import reactor
00217 from twisted.python import log
00218 from twisted.web2.channel import HTTPFactory
00219 from twisted.web2.server import Site
00220 from twisted.web2.wsgi import WSGIResource
00221
00222 log.startLogging(sys.stdout)
00223 reactor.listenTCP(port,
00224 HTTPFactory( Site(WSGIResource(app)) ),
00225 interface=host,
00226 )
00227 reactor.run()
00228
00229
00230 def _issoapmethod(f):
00231 return type(f) is types.MethodType and getattr(f, 'soapmethod', False)
00232
00233 def _resourceToWSDL(resource):
00234 from xml.etree import ElementTree
00235 from xml.etree.ElementTree import Element, QName
00236 from ZSI.wstools.Namespaces import WSDL
00237
00238 r = resource
00239 methods = filter(_issoapmethod, map(lambda i: getattr(r, i), dir(r)))
00240 tns = ''
00241
00242
00243 defs = Element("{%s}definitions" %WSDL.BASE)
00244 defs.attrib['name'] = 'SampleDefs'
00245 defs.attrib['targetNamespace'] = tns
00246
00247
00248 porttype = Element("{%s}portType" %WSDL)
00249 porttype.attrib['name'] = QName("{%s}SamplePortType" %tns)
00250
00251 binding = Element("{%s}binding" %WSDL)
00252 defs.append(binding)
00253 binding.attrib['name'] = QName("{%s}SampleBinding" %tns)
00254 binding.attrib['type'] = porttype.get('name')
00255
00256 for m in methods:
00257 m.action
00258
00259 service = Element("{%s}service" %WSDL.BASE)
00260 defs.append(service)
00261 service.attrib['name'] = 'SampleService'
00262
00263 port = Element("{%s}port" %WSDL.BASE)
00264 service.append(port)
00265 port.attrib['name'] = "SamplePort"
00266 port.attrib['binding'] = binding.get('name')
00267
00268 soapaddress = Element("{%s}address" %WSDL.BIND_SOAP)
00269 soapaddress.attrib['location'] = 'http://localhost/bla'
00270 port.append(soapaddress)
00271
00272 return [ElementTree.tostring(defs)]
00273
00274
00275
00276 """
00277 <?xml version="1.0" encoding="UTF-8"?>
00278 <wsdl:definitions name="Counter" targetNamespace="http://counter.com/bindings" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:porttype="http://counter.com" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
00279 <wsdl:import namespace="http://counter.com" location="counter_flattened.wsdl"/>
00280 <wsdl:binding name="CounterPortTypeSOAPBinding" type="porttype:CounterPortType">
00281 <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
00282 <wsdl:operation name="createCounter">
00283 <soap:operation soapAction="http://counter.com/CounterPortType/createCounterRequest"/>
00284 <wsdl:input>
00285 <soap:body use="literal"/>
00286 </wsdl:input>
00287 <wsdl:output>
00288 <soap:body use="literal"/>
00289 </wsdl:output>
00290 </wsdl:operation>
00291
00292
00293 <wsdl:definitions name="Counter" targetNamespace="http://counter.com/service"
00294 xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:binding="http://counter.com/bindings">
00295 <wsdl:import namespace="http://counter.com/bindings" location="counter_bindings.wsdl"/>
00296 <wsdl:service name="CounterService">
00297 <wsdl:port name="CounterPortTypePort" binding="binding:CounterPortTypeSOAPBinding">
00298 <soap:address location="http://localhost:8080/wsrf/services/"/>
00299 </wsdl:port>
00300 </wsdl:service>
00301 </wsdl:definitions>
00302 """
00303