source: fedd/fedd.py @ a97394b

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

beginnings of a real multithreaded server

  • Property mode set to 100755
File size: 7.3 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_deter_impl import new_feddservice
18from service_error import *
19
20from threading import *
21
22# The SSL server here is based on the implementation described at
23# http://www.xml.com/pub/a/ws/2004/01/20/salz.html
24
25# Turn off the matching of hostname to certificate ID
26SSL.Connection.clientPostConnectionCheck = None
27
28class fedd_server(SSLServer):
29    def __init__(self, ME, handler, ssl_ctx, impl):
30        SSLServer.__init__(self, ME, handler, ssl_ctx)
31        self.impl = impl
32        self.soap_methods = impl.get_soap_services()
33        self.xmlrpc_methods = impl.get_xmlrpc_services()
34
35class fedd_soap_handler(BaseHTTPRequestHandler):
36    server_version = "ZSI/2.0 fedd/0.1 " + BaseHTTPRequestHandler.server_version
37
38    def send_xml(self, text, code=200):
39        """Send an XML document as reply"""
40        self.send_response(code)
41        self.send_header('Content-type', 'text/xml; charset="utf-8"')
42        self.send_header('Content-Length', str(len(text)))
43        self.end_headers()
44        self.wfile.write(text)
45        self.wfile.flush()
46
47    def send_fault(self, f, code=500):
48        """Send a SOAP encoded fault as reply"""
49        self.send_xml(f.AsSOAP(processContents="lax"), code)
50
51    def check_headers(self, ps):
52        """Send a fault for any required envelope headers"""
53        for (uri, localname) in ps.WhatMustIUnderstand():
54            self.send_fault(FaultFromNotUnderstood(uri, lname, 'fedd'))
55            return False
56        return  True
57
58    def check_method(self, ps):
59        """Confirm that this class implements the namespace and SOAP method"""
60        root = ps.body_root
61        if root.namespaceURI not in self.server.impl.soap_namespaces:
62            self.send_fault(Fault(Fault.Client, 
63                'Unknown namespace "%s"' % root.namespaceURI))
64            return False
65
66        if getattr(root, 'localName', None) == None:
67            self.send_fault(Fault(Fault.Client, 'No method"'))
68            return False
69        return True
70
71    def do_POST(self):
72        """Treat an HTTP POST request as a SOAP service call"""
73        try:
74            cl = int(self.headers['content-length'])
75            data = self.rfile.read(cl)
76            ps = ParsedSoap(data)
77        except ParseException, e:
78            self.send_fault(Fault(Fault.Client, str(e)))
79            return
80        except Exception, e:
81            self.send_fault(FaultFromException(e, 0, sys.exc_info()[2]))
82            return
83        if not self.check_headers(ps): return
84        if not self.check_method(ps): return
85        try:
86            resp = self.soap_dispatch(ps.body_root.localName, ps,
87                    fedid(cert=self.request.get_peer_cert()))
88        except Fault, f:
89            self.send_fault(f)
90            resp = None
91       
92        if resp != None:
93            sw = SoapWriter()
94            sw.serialize(resp)
95            self.send_xml(str(sw))
96
97    def soap_dispatch(self, method, req, fid):
98        if self.server.soap_methods.has_key(method):
99            try:
100                return self.server.soap_methods[method](req, fid)
101            except service_error, e:
102                de = ns0.faultType_Def(
103                        (ns0.faultType_Def.schema,
104                            "FeddFaultBody")).pyclass()
105                de._code=e.code
106                de._errstr=e.code_string()
107                de._desc=e.desc
108                if  e.is_server_error():
109                    raise Fault(Fault.Server, e.code_string(), detail=de)
110                else:
111                    raise Fault(Fault.Client, e.code_string(), detail=de)
112        else:
113            raise Fault(Fault.Client, "Unknown method: %s" % method)
114
115
116class fedd_xmlrpc_handler(BaseHTTPRequestHandler):
117    server_version = "ZSI/2.0 fedd/0.1 " + BaseHTTPRequestHandler.server_version
118
119    def send_xml(self, text, code=200):
120        """Send an XML document as reply"""
121        self.send_response(code)
122        self.send_header('Content-type', 'text/xml; charset="utf-8"')
123        self.send_header('Content-Length', str(len(text)))
124        self.end_headers()
125        self.wfile.write(text)
126        self.wfile.flush()
127
128    def do_POST(self):
129        """Treat an HTTP POST request as an XMLRPC service call"""
130
131        resp = None
132        data = None
133        method = None
134        cl = int(self.headers['content-length'])
135        data = self.rfile.read(cl)
136
137        try:
138            params, method = xmlrpclib.loads(data)
139        except xmlrpclib.ResponseError:
140            data = xmlrpclib.dumps(xmlrpclib.Fault("Client", 
141                "Malformed request"), methodresponse=True)
142       
143        if method != None:
144            try:
145                resp = self.xmlrpc_dispatch(method, params,
146                            fedid(cert=self.request.get_peer_cert()))
147                data = xmlrpclib.dumps((resp,), encoding='UTF-8', 
148                        methodresponse=True)
149            except xmlrpclib.Fault, f:
150                data = xmlrpclib.dumps(f, methodresponse=True)
151                resp = None
152        self.send_xml(data)
153
154
155    def xmlrpc_dispatch(self, method, req, fid):
156        if self.server.xmlrpc_methods.has_key(method):
157            try:
158                return self.server.xmlrpc_methods[method](req, fid)
159            except service_error, e:
160                raise xmlrpclib.Fault(e.code_string(), e.desc)
161        else:
162            raise xmlrpclib.Fault(100, "Unknown method: %s" % method)
163
164class fedd_opts(OptionParser):
165    """Encapsulate option processing in this class, rather than in main"""
166    def __init__(self):
167        OptionParser.__init__(self, usage="%prog [opts] (--help for details)",
168                version="0.1")
169
170        self.set_defaults(host="localhost", port=23235, transport="soap",
171                debug=0)
172
173        self.add_option("-d", "--debug", action="count", dest="debug", 
174                help="Set debug.  Repeat for more information")
175        self.add_option("-f", "--configfile", action="store",
176                dest="configfile", help="Configuration file (required)")
177        self.add_option("-H", "--host", action="store", type="string",
178                dest="host", help="Hostname to listen on (default %default)")
179        self.add_option("-p", "--port", action="store", type="int",
180                dest="port", help="Port to listen on (default %default)")
181        self.add_option("-s", "--service", action="append", type="string",
182                dest="services",
183                help="Service description: host:port:transport")
184        self.add_option("-x","--transport", action="store", type="choice",
185                choices=("xmlrpc", "soap"),
186                help="Transport for request (xmlrpc|soap) (Default: %default)")
187        self.add_option("--trace", action="store_const", dest="tracefile", 
188                const=sys.stderr, help="Print SOAP exchange to stderr")
189
190def run_server(s):
191    if s: s.serve_forever()
192
193services = [ ]
194servers = [ ]
195
196opts, args = fedd_opts().parse_args()
197
198if opts.configfile != None: 
199    try:
200        impl = new_feddservice(opts.configfile)
201    except RuntimeError, e:
202        str = getattr(e, 'desc', None) or getattr(e,'message', None) or \
203                "No message"
204        sys.exit("Error configuring fedd: %s" % str)
205else: 
206    sys.exit("--configfile is required")
207
208SOAP_port = (opts.host, opts.port)
209
210if impl.cert_file == None:
211    sys.exit("Must supply certificate file (probably in config)")
212
213ctx = None
214while ctx == None:
215    try:
216        ctx = fedd_ssl_context(impl.cert_file, impl.trusted_certs, 
217                password=impl.cert_pwd)
218    except SSL.SSLError, e:
219        if str(e) != "bad decrypt" or impl.cert_pwd != None:
220            raise
221
222if opts.services:
223    for s in opts.services:
224        h, p, t  = s.split(':')
225
226        if not h: h = opts.host
227        if not p: p = opts.port
228        if not t: h = opts.transport
229
230        p = int(p)
231
232        services.append((t, (h, p)))
233else:
234    services.append((opts.transport, (opts.host, opts.port)))
235
236for s in services:
237    if s[0] == "soap":
238        servers.append(fedd_server(s[1], fedd_soap_handler, ctx, impl))
239    elif s[0] == "xmlrpc":
240        servers.append(fedd_server(s[1], fedd_xmlrpc_handler, ctx, impl))
241    else: print >>sys.stderr, "Unknown transport: %s" % s[0]
242
243for s in servers:
244    t = Thread(target=run_server, args=(s,))
245    t.start()
Note: See TracBrowser for help on using the repository browser.