1############################################################################ 2# Joshua R. Boverhof, LBNL 3# See Copyright for copyright notice! 4# $Id: $ 5########################################################################### 6import os, sys, types, inspect 7from StringIO import StringIO 8 9# twisted & related imports 10from zope.interface import classProvides, implements, Interface 11 12# ZSI imports 13from ZSI import _get_element_nsuri_name, EvaluateException, ParseException,\ 14 fault, ParsedSoap, SoapWriter 15from ZSI.twisted.reverse import DataHandler, ReverseHandlerChain,\ 16 HandlerChainInterface 17 18""" 19EXAMPLES: 20 21 See zsi/samples/WSGI 22 23 24""" 25 26def soapmethod(requesttypecode, responsetypecode, soapaction='', 27 operation=None, **kw): 28 """@soapmethod 29 decorator function for soap methods 30 """ 31 def _closure(func_cb): 32 func_cb.root = (requesttypecode.nspname,requesttypecode.pname) 33 func_cb.action = soapaction 34 func_cb.requesttypecode = requesttypecode 35 func_cb.responsetypecode = responsetypecode 36 func_cb.soapmethod = True 37 func_cb.operation = None 38 return func_cb 39 40 return _closure 41 42 43class SOAPCallbackHandler: 44 """ ps --> pyobj, pyobj --> sw 45 class variables: 46 writerClass -- ElementProxy implementation to use for SoapWriter instances. 47 """ 48 classProvides(HandlerChainInterface) 49 writerClass = None 50 51 @classmethod 52 def processRequest(cls, ps, **kw): 53 """invokes callback that should return a (request,response) tuple. 54 representing the SOAP request and response respectively. 55 ps -- ParsedSoap instance representing HTTP Body. 56 request -- twisted.web.server.Request 57 """ 58 resource = kw['resource'] 59 request = kw['request'] 60 61 root = _get_element_nsuri_name(ps.body_root) 62 for key,method in inspect.getmembers(resource, inspect.ismethod): 63 if (getattr(method, 'soapmethod', False) and method.root == root): 64 break 65 else: 66 raise RuntimeError, 'Missing soap callback method for root "%s"' %root 67 68 try: 69 req = ps.Parse(method.requesttypecode) 70 except Exception, ex: 71 raise 72 try: 73 rsp = method.responsetypecode.pyclass() 74 except Exception, ex: 75 raise 76 77 try: 78 req,rsp = method(req, rsp) 79 except Exception, ex: 80 raise 81 82 return rsp 83 84 @classmethod 85 def processResponse(cls, output, **kw): 86 sw = SoapWriter(outputclass=cls.writerClass) 87 sw.serialize(output) 88 return sw 89 90 91class SOAPHandlerChainFactory: 92 protocol = ReverseHandlerChain 93 94 @classmethod 95 def newInstance(cls): 96 return cls.protocol(DataHandler, SOAPCallbackHandler) 97 98 99class WSGIApplication(dict): 100 encoding = "UTF-8" 101 102 def __call__(self, env, start_response): 103 """do dispatching, else process 104 """ 105 script = env['SCRIPT_NAME'] # consumed 106 ipath = os.path.split(env['PATH_INFO'])[1:] 107 for i in range(1, len(ipath)+1): 108 path = os.path.join(*ipath[:i]) 109 print "PATH: ", path 110 application = self.get(path) 111 if application is not None: 112 env['SCRIPT_NAME'] = script + path 113 env['PATH_INFO'] = '' 114 print "SCRIPT: ", env['SCRIPT_NAME'] 115 return application(env, start_response) 116 117 return self._request_cb(env, start_response) 118 119 def _request_cb(self, env, start_response): 120 """callback method, override 121 """ 122 start_response("404 ERROR", [('Content-Type','text/plain')]) 123 return ['Move along people, there is nothing to see to hear'] 124 125 def putChild(self, path, resource): 126 """ 127 """ 128 path = path.split('/') 129 lp = len(path) 130 if lp == 0: 131 raise RuntimeError, 'bad path "%s"' %path 132 133 if lp == 1: 134 self[path[0]] = resource 135 136 for i in range(len(path)): 137 if not path[i]: continue 138 break 139 140 next = self.get(path[i], None) 141 if next is None: 142 next = self[path[i]] = WSGIApplication() 143 144 next.putChild('/'.join(path[-1:]), resource) 145 146 147 148 149class SOAPApplication(WSGIApplication): 150 """ 151 """ 152 factory = SOAPHandlerChainFactory 153 154 def __init__(self, **kw): 155 dict.__init__(self, **kw) 156 self.delegate = None 157 158 def _request_cb(self, env, start_response): 159 """process request, 160 """ 161 if env['REQUEST_METHOD'] == 'GET': 162 return self._handle_GET(env, start_response) 163 164 if env['REQUEST_METHOD'] == 'POST': 165 return self._handle_POST(env, start_response) 166 167 start_response("500 ERROR", [('Content-Type','text/plain')]) 168 s = StringIO() 169 h = env.items(); h.sort() 170 for k,v in h: 171 print >>s, k,'=',`v` 172 return [s.getvalue()] 173 174 def _handle_GET(self, env, start_response): 175 if env['QUERY_STRING'].lower() == 'wsdl': 176 start_response("200 OK", [('Content-Type','text/plain')]) 177 r = self.delegate or self 178 return _resourceToWSDL(r) 179 180 start_response("404 ERROR", [('Content-Type','text/plain')]) 181 return ['NO RESOURCE FOR GET'] 182 183 def _handle_POST(self, env, start_response): 184 """Dispatch Method called by twisted render, creates a 185 request/response handler chain. 186 request -- twisted.web.server.Request 187 """ 188 input = env['wsgi.input'] 189 data = input.read( int(env['CONTENT_LENGTH']) ) 190 mimeType = "text/xml" 191 if self.encoding is not None: 192 mimeType = 'text/xml; charset="%s"' % self.encoding 193 194 request = None 195 resource = self.delegate or self 196 chain = self.factory.newInstance() 197 try: 198 pyobj = chain.processRequest(data, request=request, resource=resource) 199 except Exception, ex: 200 start_response("500 ERROR", [('Content-Type',mimeType)]) 201 return [fault.FaultFromException(ex, False, sys.exc_info()[2]).AsSOAP()] 202 203 try: 204 soap = chain.processResponse(pyobj, request=request, resource=resource) 205 except Exception, ex: 206 start_response("500 ERROR", [('Content-Type',mimeType)]) 207 return [fault.FaultFromException(ex, False, sys.exc_info()[2]).AsSOAP()] 208 209 start_response("200 OK", [('Content-Type',mimeType)]) 210 return [soap] 211 212 213def test(app, port=8080, host="localhost"): 214 """ 215 """ 216 from twisted.internet import reactor 217 from twisted.python import log 218 from twisted.web2.channel import HTTPFactory 219 from twisted.web2.server import Site 220 from twisted.web2.wsgi import WSGIResource 221 222 log.startLogging(sys.stdout) 223 reactor.listenTCP(port, 224 HTTPFactory( Site(WSGIResource(app)) ), 225 interface=host, 226 ) 227 reactor.run() 228 229 230def _issoapmethod(f): 231 return type(f) is types.MethodType and getattr(f, 'soapmethod', False) 232 233def _resourceToWSDL(resource): 234 from xml.etree import ElementTree 235 from xml.etree.ElementTree import Element, QName 236 from ZSI.wstools.Namespaces import WSDL 237 238 r = resource 239 methods = filter(_issoapmethod, map(lambda i: getattr(r, i), dir(r))) 240 tns = '' 241 242 #tree = ElementTree() 243 defs = Element("{%s}definitions" %WSDL.BASE) 244 defs.attrib['name'] = 'SampleDefs' 245 defs.attrib['targetNamespace'] = tns 246 #tree.append(defs) 247 248 porttype = Element("{%s}portType" %WSDL) 249 porttype.attrib['name'] = QName("{%s}SamplePortType" %tns) 250 251 binding = Element("{%s}binding" %WSDL) 252 defs.append(binding) 253 binding.attrib['name'] = QName("{%s}SampleBinding" %tns) 254 binding.attrib['type'] = porttype.get('name') 255 256 for m in methods: 257 m.action 258 259 service = Element("{%s}service" %WSDL.BASE) 260 defs.append(service) 261 service.attrib['name'] = 'SampleService' 262 263 port = Element("{%s}port" %WSDL.BASE) 264 service.append(port) 265 port.attrib['name'] = "SamplePort" 266 port.attrib['binding'] = binding.get('name') 267 268 soapaddress = Element("{%s}address" %WSDL.BIND_SOAP) 269 soapaddress.attrib['location'] = 'http://localhost/bla' 270 port.append(soapaddress) 271 272 return [ElementTree.tostring(defs)] 273 274 275 276""" 277<?xml version="1.0" encoding="UTF-8"?> 278<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/"> 279 <wsdl:import namespace="http://counter.com" location="counter_flattened.wsdl"/> 280 <wsdl:binding name="CounterPortTypeSOAPBinding" type="porttype:CounterPortType"> 281 <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/> 282 <wsdl:operation name="createCounter"> 283 <soap:operation soapAction="http://counter.com/CounterPortType/createCounterRequest"/> 284 <wsdl:input> 285 <soap:body use="literal"/> 286 </wsdl:input> 287 <wsdl:output> 288 <soap:body use="literal"/> 289 </wsdl:output> 290 </wsdl:operation> 291 292 293<wsdl:definitions name="Counter" targetNamespace="http://counter.com/service" 294xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:binding="http://counter.com/bindings"> 295 <wsdl:import namespace="http://counter.com/bindings" location="counter_bindings.wsdl"/> 296 <wsdl:service name="CounterService"> 297 <wsdl:port name="CounterPortTypePort" binding="binding:CounterPortTypeSOAPBinding"> 298 <soap:address location="http://localhost:8080/wsrf/services/"/> 299 </wsdl:port> 300 </wsdl:service> 301</wsdl:definitions> 302""" 303 304