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