1#! /usr/bin/env python 2# $Header$ 3'''Simple CGI dispatching. 4''' 5 6import types, os, sys 7from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer 8from ZSI import * 9from ZSI import _child_elements, _copyright, _seqtypes, _find_arraytype, _find_type, resolvers 10from ZSI.auth import _auth_tc, AUTH, ClientBinding 11 12 13# Client binding information is stored in a global. We provide an accessor 14# in case later on it's not. 15_client_binding = None 16 17def GetClientBinding(): 18 '''Return the client binding object. 19 ''' 20 return _client_binding 21 22gettypecode = lambda mod,e: getattr(mod, str(e.localName)).typecode 23def _Dispatch(ps, modules, SendResponse, SendFault, nsdict={}, typesmodule=None, 24 gettypecode=gettypecode, rpc=False, docstyle=False, **kw): 25 '''Find a handler for the SOAP request in ps; search modules. 26 Call SendResponse or SendFault to send the reply back, appropriately. 27 28 Behaviors: 29 default -- Call "handler" method with pyobj representation of body root, and return 30 a self-describing request (w/typecode). Parsing done via a typecode from 31 typesmodule, or Any. 32 33 docstyle -- Call "handler" method with ParsedSoap instance and parse result with an 34 XML typecode (DOM). Behavior, wrap result in a body_root "Response" appended message. 35 36 rpc -- Specify RPC wrapper of result. Behavior, ignore body root (RPC Wrapper) 37 of request, parse all "parts" of message via individual typecodes. Expect 38 the handler to return the parts of the message, whether it is a dict, single instance, 39 or a list try to serialize it as a Struct but if this is not possible put it in an Array. 40 Parsing done via a typecode from typesmodule, or Any. 41 42 ''' 43 global _client_binding 44 try: 45 what = str(ps.body_root.localName) 46 47 # See what modules have the element name. 48 if modules is None: 49 modules = ( sys.modules['__main__'], ) 50 51 handlers = [ getattr(m, what) for m in modules if hasattr(m, what) ] 52 if len(handlers) == 0: 53 raise TypeError("Unknown method " + what) 54 55 # Of those modules, see who's callable. 56 handlers = [ h for h in handlers if callable(h) ] 57 if len(handlers) == 0: 58 raise TypeError("Unimplemented method " + what) 59 if len(handlers) > 1: 60 raise TypeError("Multiple implementations found: " + `handlers`) 61 handler = handlers[0] 62 63 _client_binding = ClientBinding(ps) 64 if docstyle: 65 result = handler(ps.body_root) 66 tc = TC.XML(aslist=1, pname=what+'Response') 67 elif not rpc: 68 try: 69 tc = gettypecode(typesmodule, ps.body_root) 70 except Exception: 71 tc = TC.Any() 72 73 try: 74 arg = tc.parse(ps.body_root, ps) 75 except EvaluateException, ex: 76 SendFault(FaultFromZSIException(ex), **kw) 77 return 78 79 try: 80 result = handler(arg) 81 except Exception,ex: 82 SendFault(FaultFromZSIException(ex), **kw) 83 return 84 85 try: 86 tc = result.typecode 87 except AttributeError,ex: 88 SendFault(FaultFromZSIException(ex), **kw) 89 return 90 91 elif typesmodule is not None: 92 kwargs = {} 93 for e in _child_elements(ps.body_root): 94 try: 95 tc = gettypecode(typesmodule, e) 96 except Exception: 97 tc = TC.Any() 98 99 try: 100 kwargs[str(e.localName)] = tc.parse(e, ps) 101 except EvaluateException, ex: 102 SendFault(FaultFromZSIException(ex), **kw) 103 return 104 105 result = handler(**kwargs) 106 aslist = False 107 # make sure data is wrapped, try to make this a Struct 108 if type(result) in _seqtypes: 109 for o in result: 110 aslist = hasattr(result, 'typecode') 111 if aslist: break 112 elif type(result) is not dict: 113 aslist = not hasattr(result, 'typecode') 114 result = (result,) 115 116 tc = TC.Any(pname=what+'Response', aslist=aslist) 117 else: 118 # if this is an Array, call handler with list 119 # if this is an Struct, call handler with dict 120 tp = _find_type(ps.body_root) 121 isarray = ((type(tp) in (tuple,list) and tp[1] == 'Array') or _find_arraytype(ps.body_root)) 122 data = _child_elements(ps.body_root) 123 tc = TC.Any() 124 if isarray and len(data) == 0: 125 result = handler() 126 elif isarray: 127 try: arg = [ tc.parse(e, ps) for e in data ] 128 except EvaluateException, e: 129 #SendFault(FaultFromZSIException(e), **kw) 130 SendFault(RuntimeError("THIS IS AN ARRAY: %s" %isarray)) 131 return 132 133 result = handler(*arg) 134 else: 135 try: kwarg = dict([ (str(e.localName),tc.parse(e, ps)) for e in data ]) 136 except EvaluateException, e: 137 SendFault(FaultFromZSIException(e), **kw) 138 return 139 140 result = handler(**kwarg) 141 142 # reponse typecode 143 #tc = getattr(result, 'typecode', TC.Any(pname=what+'Response')) 144 tc = TC.Any(pname=what+'Response') 145 146 sw = SoapWriter(nsdict=nsdict) 147 sw.serialize(result, tc) 148 return SendResponse(str(sw), **kw) 149 except Fault, e: 150 return SendFault(e, **kw) 151 except Exception, e: 152 # Something went wrong, send a fault. 153 return SendFault(FaultFromException(e, 0, sys.exc_info()[2]), **kw) 154 155 156def _ModPythonSendXML(text, code=200, **kw): 157 req = kw['request'] 158 req.content_type = 'text/xml' 159 req.content_length = len(text) 160 req.send_http_header() 161 req.write(text) 162 163 164def _ModPythonSendFault(f, **kw): 165 _ModPythonSendXML(f.AsSOAP(), 500, **kw) 166 167def _JonPySendFault(f, **kw): 168 _JonPySendXML(f.AsSOAP(), 500, **kw) 169 170def _JonPySendXML(text, code=200, **kw): 171 req = kw['request'] 172 req.set_header("Content-Type", 'text/xml; charset="%s"' %UNICODE_ENCODING) 173 req.set_header("Content-Length", str(len(text))) 174 req.write(text) 175 176def _CGISendXML(text, code=200, **kw): 177 print 'Status: %d' % code 178 print 'Content-Type: text/xml; charset="%s"' %UNICODE_ENCODING 179 print 'Content-Length: %d' % len(text) 180 print '' 181 print text 182 183def _CGISendFault(f, **kw): 184 _CGISendXML(f.AsSOAP(), 500, **kw) 185 186 187class SOAPRequestHandler(BaseHTTPRequestHandler): 188 '''SOAP handler. 189 ''' 190 server_version = 'ZSI/1.1 ' + BaseHTTPRequestHandler.server_version 191 192 def send_xml(self, text, code=200): 193 '''Send some XML. 194 ''' 195 self.send_response(code) 196 197 if text: 198 self.send_header('Content-type', 'text/xml; charset="%s"' %UNICODE_ENCODING) 199 self.send_header('Content-Length', str(len(text))) 200 201 self.end_headers() 202 203 if text: 204 self.wfile.write(text) 205 206 self.wfile.flush() 207 208 def send_fault(self, f, code=500): 209 '''Send a fault. 210 ''' 211 self.send_xml(f.AsSOAP(), code) 212 213 def do_POST(self): 214 '''The POST command. 215 ''' 216 try: 217 ct = self.headers['content-type'] 218 if ct.startswith('multipart/'): 219 cid = resolvers.MIMEResolver(ct, self.rfile) 220 xml = cid.GetSOAPPart() 221 ps = ParsedSoap(xml, resolver=cid.Resolve) 222 else: 223 length = int(self.headers['content-length']) 224 ps = ParsedSoap(self.rfile.read(length)) 225 except ParseException, e: 226 self.send_fault(FaultFromZSIException(e)) 227 return 228 except Exception, e: 229 # Faulted while processing; assume it's in the header. 230 self.send_fault(FaultFromException(e, 1, sys.exc_info()[2])) 231 return 232 233 _Dispatch(ps, self.server.modules, self.send_xml, self.send_fault, 234 docstyle=self.server.docstyle, nsdict=self.server.nsdict, 235 typesmodule=self.server.typesmodule, rpc=self.server.rpc) 236 237def AsServer(port=80, modules=None, docstyle=False, nsdict={}, typesmodule=None, 238 rpc=False, addr=''): 239 address = (addr, port) 240 httpd = HTTPServer(address, SOAPRequestHandler) 241 httpd.modules = modules 242 httpd.docstyle = docstyle 243 httpd.nsdict = nsdict 244 httpd.typesmodule = typesmodule 245 httpd.rpc = rpc 246 httpd.serve_forever() 247 248def AsCGI(nsdict={}, typesmodule=None, rpc=False, modules=None): 249 '''Dispatch within a CGI script. 250 ''' 251 if os.environ.get('REQUEST_METHOD') != 'POST': 252 _CGISendFault(Fault(Fault.Client, 'Must use POST')) 253 return 254 ct = os.environ['CONTENT_TYPE'] 255 try: 256 if ct.startswith('multipart/'): 257 cid = resolvers.MIMEResolver(ct, sys.stdin) 258 xml = cid.GetSOAPPart() 259 ps = ParsedSoap(xml, resolver=cid.Resolve) 260 else: 261 length = int(os.environ['CONTENT_LENGTH']) 262 ps = ParsedSoap(sys.stdin.read(length)) 263 except ParseException, e: 264 _CGISendFault(FaultFromZSIException(e)) 265 return 266 _Dispatch(ps, modules, _CGISendXML, _CGISendFault, nsdict=nsdict, 267 typesmodule=typesmodule, rpc=rpc) 268 269def AsHandler(request=None, modules=None, **kw): 270 '''Dispatch from within ModPython.''' 271 ps = ParsedSoap(request) 272 kw['request'] = request 273 _Dispatch(ps, modules, _ModPythonSendXML, _ModPythonSendFault, **kw) 274 275def AsJonPy(request=None, modules=None, **kw): 276 '''Dispatch within a jonpy CGI/FastCGI script. 277 ''' 278 279 kw['request'] = request 280 if request.environ.get('REQUEST_METHOD') != 'POST': 281 _JonPySendFault(Fault(Fault.Client, 'Must use POST'), **kw) 282 return 283 ct = request.environ['CONTENT_TYPE'] 284 try: 285 if ct.startswith('multipart/'): 286 cid = resolvers.MIMEResolver(ct, request.stdin) 287 xml = cid.GetSOAPPart() 288 ps = ParsedSoap(xml, resolver=cid.Resolve) 289 else: 290 length = int(request.environ['CONTENT_LENGTH']) 291 ps = ParsedSoap(request.stdin.read(length)) 292 except ParseException, e: 293 _JonPySendFault(FaultFromZSIException(e), **kw) 294 return 295 _Dispatch(ps, modules, _JonPySendXML, _JonPySendFault, **kw) 296 297 298if __name__ == '__main__': print _copyright 299