source: fedd/fedd.py @ bb3769a

axis_examplecompt_changesinfo-opsversion-1.30version-2.00version-3.01version-3.02
Last change on this file since bb3769a was bb3769a, checked in by Ted Faber <faber@…>, 16 years ago

parsing SOAP faults (finally)

  • Property mode set to 100755
File size: 5.7 KB
Line 
1#!/usr/local/bin/python
2
3import sys
4
5from BaseHTTPServer import BaseHTTPRequestHandler
6
7from ZSI import Fault, ParseException, FaultFromNotUnderstood, \
8    FaultFromZSIException, FaultFromException, ParsedSoap, SoapWriter
9
10from M2Crypto import SSL
11from M2Crypto.SSL.SSLServer import SSLServer
12import xmlrpclib
13
14from optparse import OptionParser
15
16from fedd_util import fedd_ssl_context, fedid
17from fedd_proj import new_feddservice
18
19# The SSL server here is based on the implementation described at
20# http://www.xml.com/pub/a/ws/2004/01/20/salz.html
21
22# Turn off the matching of hostname to certificate ID
23SSL.Connection.clientPostConnectionCheck = None
24
25class fedd_server(SSLServer):
26    def __init__(self, ME, handler, ssl_ctx, impl):
27        SSLServer.__init__(self, ME, handler, ssl_ctx)
28        self.impl = impl
29
30class fedd_soap_handler(BaseHTTPRequestHandler):
31    server_version = "ZSI/2.0 fedd/0.1 " + BaseHTTPRequestHandler.server_version
32
33
34    def send_xml(self, text, code=200):
35        """Send an XML document as reply"""
36        self.send_response(code)
37        self.send_header('Content-type', 'text/xml; charset="utf-8"')
38        self.send_header('Content-Length', str(len(text)))
39        self.end_headers()
40        self.wfile.write(text)
41        self.wfile.flush()
42
43    def send_fault(self, f, code=500):
44        """Send a SOAP encoded fault as reply"""
45        self.send_xml(f.AsSOAP(processContents="lax"), code)
46
47    def check_headers(self, ps):
48        """Send a fault for any required envelope headers"""
49        for (uri, localname) in ps.WhatMustIUnderstand():
50            self.send_fault(FaultFromNotUnderstood(uri, lname, 'fedd'))
51            return False
52        return  True
53
54    def check_method(self, ps):
55        """Confirm that this class implements the namespace and SOAP method"""
56        root = ps.body_root
57        if root.namespaceURI != self.server.impl.soap_namespace:
58            self.send_fault(Fault(Fault.Client, 
59                'Unknown namespace "%s"' % root.namespaceURI))
60            return False
61
62        if getattr(root, 'localName', None) == None:
63            self.send_fault(Fault(Fault.Client, 'No method"'))
64            return False
65        return True
66
67    def do_POST(self):
68        """Treat an HTTP POST request as a SOAP service call"""
69        try:
70            cl = int(self.headers['content-length'])
71            data = self.rfile.read(cl)
72            ps = ParsedSoap(data)
73        except ParseException, e:
74            self.send_fault(Fault(Fault.Client, str(e)))
75            return
76        except Exception, e:
77            self.send_fault(FaultFromException(e, 0, sys.exc_info()[2]))
78            return
79        if not self.check_headers(ps): return
80        if not self.check_method(ps): return
81        try:
82            resp = self.server.impl.soap_dispatch(ps.body_root.localName, ps,
83                    fedid(cert=self.request.get_peer_cert()))
84        except Fault, f:
85            self.send_fault(f)
86            resp = None
87       
88        if resp != None:
89            sw = SoapWriter()
90            sw.serialize(resp)
91            self.send_xml(str(sw))
92
93class fedd_xmlrpc_handler(BaseHTTPRequestHandler):
94    server_version = "ZSI/2.0 fedd/0.1 " + BaseHTTPRequestHandler.server_version
95
96    def send_xml(self, text, code=200):
97        """Send an XML document as reply"""
98        self.send_response(code)
99        self.send_header('Content-type', 'text/xml; charset="utf-8"')
100        self.send_header('Content-Length', str(len(text)))
101        self.end_headers()
102        self.wfile.write(text)
103        self.wfile.flush()
104
105    def do_POST(self):
106        """Treat an HTTP POST request as an XMLRPC service call"""
107
108        resp = None
109        data = None
110        method = None
111        cl = int(self.headers['content-length'])
112        data = self.rfile.read(cl)
113
114        try:
115            params, method = xmlrpclib.loads(data)
116        except xmlrpclib.ResponseError:
117            data = xmlrpclib.dumps(xmlrpclib.Fault("Client", 
118                "Malformed request"), methodresponse=True)
119       
120        if method != None:
121            try:
122                resp = self.server.impl.xmlrpc_dispatch(method, params,
123                            fedid(cert=self.request.get_peer_cert()))
124                data = xmlrpclib.dumps((resp,), methodresponse=True)
125            #except Fault, f:
126        #       xf = xmlrpclib.Fault(f.code, f.string)
127        #       data = xmlrpclib.dumps(xf, methodresponse=True)
128        #       resp = None
129            except xmlrpclib.Fault, f:
130                data = xmlrpclib.dumps(f, methodresponse=True)
131                resp = None
132        self.send_xml(data)
133
134
135class fedd_opts(OptionParser):
136    """Encapsulate option processing in this class, rather than in main"""
137    def __init__(self):
138        OptionParser.__init__(self, usage="%prog [opts] (--help for details)",
139                version="0.1")
140
141        self.set_defaults(host="localhost", port=23235, 
142                transport="soap",debug=0)
143
144        self.add_option("-d", "--debug", action="count", dest="debug", 
145                help="Set debug.  Repeat for more information")
146        self.add_option("-f", "--configfile", action="store",
147                dest="configfile", help="Configuration file (required)")
148        self.add_option("-H", "--host", action="store", type="string",
149                dest="host", help="Hostname to listen on (default %default)")
150        self.add_option("-p", "--port", action="store", type="int",
151                dest="port", help="Port to listen on (default %default)")
152        self.add_option("-x","--transport", action="store", type="choice",
153                choices=("xmlrpc", "soap"),
154                help="Transport for request (xmlrpc|soap) (Default: %default)")
155        self.add_option("--trace", action="store_const", dest="tracefile", 
156                const=sys.stderr, help="Print SOAP exchange to stderr")
157
158opts, args = fedd_opts().parse_args()
159
160if opts.configfile != None: 
161    try:
162        impl = new_feddservice(opts.configfile)
163    except RuntimeError, e:
164        sys.exit("Error configuring fedd: %s" % e.message)
165else: 
166    sys.exit("--configfile is required")
167
168SOAP_port = (opts.host, opts.port)
169
170if impl.cert_file == None:
171    sys.exit("Must supply certificate file (probably in config)")
172
173ctx = None
174while ctx == None:
175    try:
176        ctx = fedd_ssl_context(impl.cert_file, impl.trusted_certs, 
177                password=impl.cert_pwd)
178    except SSL.SSLError, e:
179        if str(e) != "bad decrypt" or impl.cert_pwd != None:
180            raise
181
182if opts.transport == "soap":
183    s = fedd_server(SOAP_port, fedd_soap_handler, ctx, impl)
184elif opts.transport == "xmlrpc":
185    s = fedd_server(SOAP_port, fedd_xmlrpc_handler, ctx, impl)
186else:
187    s = None
188
189s.serve_forever()
Note: See TracBrowser for help on using the repository browser.