source: n4d/trunk/fuentes/install-files/usr/share/n4d/xmlrpc-server/server.py @ 3547

Last change on this file since 3547 was 3547, checked in by hectorgh, 3 years ago

VariablesManager? changes. Read changelog. startup thread is launched after service is up

  • Property svn:executable set to *
File size: 12.3 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4import locale
5locale.setlocale(locale.LC_ALL, 'C.UTF-8')
6
7"""
8 *******************************************************************************
9 *
10 * $Id: SecureDocXMLRPCServer.py 4 2008-06-04 18:44:13Z yingera $
11 * $URL: https://xxxxxx/repos/utils/trunk/tools/SVNRPCServer.py $
12 *
13 * $Date: 2008-06-04 13:44:13 -0500 (Wed, 04 Jun 2008) $
14 * $Author: yingera $
15 *
16 * Authors: Laszlo Nagy, Andrew Yinger
17 *
18 * Description: Threaded, Documenting SecureDocXMLRPCServer.py - over HTTPS.
19 *
20 *  requires pyOpenSSL: http://sourceforge.net/project/showfiles.php?group_id=31249
21 *   ...and open SSL certs installed.
22 *
23 * Based on this article: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81549
24 *
25 *******************************************************************************
26"""
27
28import SocketServer
29import BaseHTTPServer
30import SimpleHTTPServer
31import SimpleXMLRPCServer
32import imp
33import glob
34
35
36import fcntl
37import time
38import random
39import base64
40import socket, os
41from OpenSSL import SSL
42from threading import Event, currentThread, Thread, Condition
43from thread import start_new_thread as start
44from DocXMLRPCServer import DocXMLRPCServer, DocXMLRPCRequestHandler
45
46import xmlrpclib
47import threading
48
49threading._DummyThread._Thread__stop = lambda x: 42
50
51#locale.resetlocale()
52
53# static stuff
54
55'''
56DEFAULTKEYFILE='/etc/lliurex-secrets/pki/n4d/n4d.key'   # Replace with your PEM formatted key file
57DEFAULTCERTFILE='/etc/lliurex-secrets/certs/n4d/n4d'  # Replace with your PEM formatted certificate file
58'''
59
60DEFAULTKEYFILE='/etc/n4d/cert/n4dkey.pem'       # Replace with your PEM formatted key file
61DEFAULTCERTFILE='/etc/n4d/cert/n4dcert.pem'  # Replace with your PEM formatted certificate file
62
63SITESENABLEDPATH="/usr/share/n4d/sites-enabled/"
64
65TRIGGER_BLOCK=False
66
67
68class SecureDocXMLRpcRequestHandler(DocXMLRPCRequestHandler):
69        """Secure Doc XML-RPC request handler class.
70        It it very similar to DocXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
71        """
72       
73       
74        def setup(self):
75                self.connection = self.request
76                self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
77                self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
78
79        def address_string(self):
80                "getting 'FQDN' from host seems to stall on some ip addresses, so... just (quickly!) return raw host address"
81                host, port = self.client_address
82                #print dir(self)
83                try:
84                        self.client_url=self.headers["host"]
85                except Exception as e:
86                        self.client_url=None
87                #return socket.getfqdn(host)
88                return host
89
90
91        def do_POST(self):
92                """Handles the HTTPS POST request.
93                It was copied out from SimpleXMLRPCServer.py and modified to shutdown the socket cleanly.
94                """
95                try:
96                        # get arguments
97                        data = self.rfile.read(int(self.headers["content-length"]))
98                        # In previous versions of SimpleXMLRPCServer, _dispatch
99                        # could be overridden in this class, instead of in
100                        # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
101                        # check to see if a subclass implements _dispatch and dispatch
102                        # using that method if present.
103                        addr,num=self.client_address
104                        response = self.server._marshaled_dispatch(data, getattr(self, '_dispatch', None),client_address=addr)
105                       
106                       
107                except: # This should only happen if the module is buggy
108                        # internal error, report as HTTP server error
109                        self.send_response(500)
110                        self.end_headers()
111                else:
112                        # got a valid XML RPC response
113                        self.send_response(200)
114                        self.send_header("Content-type", "text/xml")
115                        self.send_header("Content-length", str(len(response)))
116                        self.end_headers()
117                        self.wfile.write(response)
118
119                        # shut down the connection
120                        self.wfile.flush()
121                        '''
122                        global TRIGGER_BLOCK
123                        while(TRIGGER_BLOCK):
124                                print "waiting for trigger_block to be unlocked..."
125                                time.sleep(int(5*random.random()))
126                        TRIGGER_BLOCK=True
127                        print "done with response. Sleeping..."
128                        time.sleep(5)
129                        print "done sleeping."
130                        TRIGGER_BLOCK=False
131                        '''
132                        self.connection.shutdown() # Modified here!
133
134        def do_GET(self):
135                """Handles the HTTP GET request.
136
137                Interpret all HTTP GET requests as requests for server
138                documentation.
139                """
140               
141                self.address_string()
142               
143                web_modules_list=os.listdir(SITESENABLEDPATH)
144                path=self.path.lstrip("/")
145               
146               
147                if path+".py" in web_modules_list:
148                       
149                        try:
150                                obj=imp.load_source(path,SITESENABLEDPATH+path+".py")
151                                inst=getattr(obj,path)()
152                                info={}
153                                info["client_ip"]=self.client_address[0]
154                                info["client_url"]=self.client_url
155                                response=inst.get_response(info)
156                                self.send_response(200)
157                                self.send_header("Content-type", "text/html")
158                                self.send_header("Content-length", str(len(response)))
159                                self.end_headers()
160                                self.wfile.write(response)                     
161                                return
162                               
163                        except Exception as e:
164                                print e
165                               
166               
167                '''
168                if "/n4d/" in self.path:
169                       
170                        if os.path.exists(self.path) and os.path.isfile(self.path):
171                       
172                                file_length=os.stat(self.path).st_size
173                                f=open(self.path,"rB")
174                                response=f.read(file_length)
175                                f.close()
176                       
177                                self.send_response(200)
178                                self.send_header("Content-type","text/html")
179                                self.send_header("Content-length",file_length)
180                                self.end_headers()
181                               
182                                self.wfile.write(response)     
183                       
184                                return
185                '''
186               
187                # Check that the path is legal
188               
189                if not self.is_rpc_path_valid():
190                        self.report_404()
191                        return
192               
193               
194                #print self.path
195                #response = self.server.generate_html_documentation()
196               
197                header="<html><body>"
198                foot="</body></html>"
199               
200                response=header
201               
202                for plugin in l.cm.plugins:
203                        response+="<b>["+plugin.type+":"+plugin.class_name+"]</b><br>"
204                        for method in plugin.function:
205                                try:
206                                        #server.register_function(getattr(l.objects[plugin.class_name],method))
207                                        args=getattr(l.objects[plugin.class_name],method).func_code.co_varnames[:getattr(l.objects[plugin.class_name],method).func_code.co_argcount]
208                                        response+="<pre>      " + method+str(args).replace("'self',","")+" , " + str(getattr(l.objects[plugin.class_name],method).__doc__) + "</pre>"
209                                except:
210                                        pass
211               
212                response+=foot
213               
214
215               
216                self.send_response(200)
217                self.send_header("Content-type", "text/html")
218                self.send_header("Content-length", str(len(response)))
219                self.end_headers()
220                self.wfile.write(response)
221
222                # shut down the connection
223                self.wfile.flush()
224                self.connection.shutdown() # Modified here!
225               
226               
227
228        def report_404 (self):
229                # Report a 404 error
230                self.send_response(404)
231                response = 'No such page'
232                self.send_header("Content-type", "text/plain")
233                self.send_header("Content-length", str(len(response)))
234                self.end_headers()
235                self.wfile.write(response)
236                # shut down the connection
237                self.wfile.flush()
238                self.connection.shutdown() # Modified here!
239               
240        def do_OPTIONS(self):
241                self.send_response(200)
242                self.end_headers()
243                self.wfile.flush()
244                self.connection.shutdown() # Modified here!
245
246        def end_headers(self):
247                self.send_header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
248                self.send_header("Access-Control-Allow-Origin", "*")
249                DocXMLRPCRequestHandler.end_headers(self)
250
251class CustomThreadingMixIn:
252        """Mix-in class to handle each request in a new thread."""
253        # Decides how threads will act upon termination of the main process
254        daemon_threads = True
255
256        def process_request_thread(self, request, client_address):
257                """Same as in BaseServer but as a thread.
258                In addition, exception handling is done here.
259                """
260                try:
261                        self.finish_request(request, client_address)
262                        self.close_request(request)
263                except (socket.error, SSL.SysCallError), why:
264                        print 'socket.error finishing request from "%s"; Error: %s' % (client_address, str(why))
265                        self.close_request(request)
266                except:
267                        self.handle_error(request, client_address)
268                        self.close_request(request)
269
270        def process_request(self, request, client_address):
271                """Start a new thread to process the request."""
272                t = Thread(target = self.process_request_thread, args = (request, client_address))
273                if self.daemon_threads:
274                        t.setDaemon(1)
275                t.start()
276
277
278
279class SecureDocXMLRPCServer(CustomThreadingMixIn, DocXMLRPCServer):
280        def __init__(self, registerInstance, server_address, keyFile=DEFAULTKEYFILE, certFile=DEFAULTCERTFILE, logRequests=True):
281                """Secure Documenting XML-RPC server.
282                It it very similar to DocXMLRPCServer but it uses HTTPS for transporting XML data.
283                """
284                DocXMLRPCServer.__init__(self, server_address, SecureDocXMLRpcRequestHandler, logRequests,True)
285                self.logRequests = logRequests
286
287                # stuff for doc server
288                try: self.set_server_title(registerInstance.title)
289                except AttributeError: self.set_server_title('N4D Documentation page')
290                try: self.set_server_name(registerInstance.name)
291                except AttributeError: self.set_server_name('N4D')
292                #for plugin in registerInstace
293                if registerInstance.__doc__: self.set_server_documentation(registerInstance.__doc__)
294                else: self.set_server_documentation('default documentation')
295               
296               
297                #self.register_introspection_functions()
298               
299                # init stuff, handle different versions:
300                try:
301                        SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self,allow_none=True)
302                except TypeError:
303                        # An exception is raised in Python 2.5 as the prototype of the __init__
304                        # method has changed and now has 3 arguments (self, allow_none, encoding)
305                        SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, allow_none=True, encoding=None)
306                SocketServer.BaseServer.__init__(self, server_address, SecureDocXMLRpcRequestHandler)
307                self.register_instance(registerInstance) # for some reason, have to register instance down here!
308
309                # SSL socket stuff
310                ctx = SSL.Context(SSL.SSLv23_METHOD)
311                ctx.use_privatekey_file(keyFile)
312                ctx.use_certificate_file(certFile)
313                self.socket = SSL.Connection(ctx, socket.socket(self.address_family, self.socket_type))
314                #self.socket = socket.socket(self.address_family, self.socket_type)
315               
316                old = fcntl.fcntl(self.socket.fileno(), fcntl.F_GETFD) 
317                fcntl.fcntl(self.socket.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)       
318               
319                self.server_bind()
320                self.server_activate()
321
322                # requests count and condition, to allow for keyboard quit via CTL-C
323                self.requests = 0
324                self.rCondition = Condition()
325
326
327        def startup(self,funct):
328                'run until quit signaled from keyboard...'
329                print '[SERVER] Starting; Press CTRL-C to quit ...'
330                funct()
331                while True:
332                        try:
333                                self.rCondition.acquire()
334                                start(self.handle_request, ()) # we do this async, because handle_request blocks!
335                                while not self.requests:
336                                        self.rCondition.wait(timeout=3.0)
337                                if self.requests: self.requests -= 1
338                                self.rCondition.release()
339                        except KeyboardInterrupt:
340                                print "[SERVER] Exiting..."
341                                return
342
343        def get_request(self):
344                request, client_address = self.socket.accept()
345                self.rCondition.acquire()
346                self.requests += 1
347                self.rCondition.notifyAll()
348                self.rCondition.release()
349                return (request, client_address)
350
351        def listMethods(self):
352                'return list of method names (strings)'
353                methodNames = self.funcs.keys()
354                methodNames.sort()
355                return methodNames
356
357        def methodHelp(self, methodName):
358                'method help'
359                if methodName in self.funcs:
360                        return self.funcs[methodName].__doc__
361                else:
362                        raise Exception('method "%s" is not supported' % methodName)
363
364
365        def _marshaled_dispatch(self, data, dispatch_method = None, path = None, client_address=None):
366                self.allow_none=True
367                try:
368                        params, method = xmlrpclib.loads(data)
369                        params=(client_address,)+params
370
371                        if dispatch_method is not None:
372                                response = dispatch_method(method, params)
373                        else:
374
375                                response = self._dispatch(method, params)
376
377                        response = (response,)
378                        response = xmlrpclib.dumps(response, methodresponse=1,allow_none=self.allow_none, encoding=self.encoding)
379
380                except Fault, fault:
381                        response = xmlrpclib.dumps(fault, allow_none=self.allow_none,encoding=self.encoding)
382                except:
383
384                        exc_type, exc_value, exc_tb = sys.exc_info()
385                        response = xmlrpclib.dumps(xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)),encoding=self.encoding, allow_none=self.allow_none,)
386
387                return response
388
389
390
391if __name__ == '__main__':
392       
393        obj=imp.load_source("core","/usr/share/n4d/xmlrpc-server/core.py")
394        l=obj.Core()
395        server_address = ('', 9779) # (address, port)
396        tries=1
397        max_tries=5
398        while tries <= max_tries:
399                try:
400                        server = SecureDocXMLRPCServer(l, server_address, DEFAULTKEYFILE, DEFAULTCERTFILE)     
401                        tries=max_tries+1
402                except:
403                        print "[SERVER] Failed to obtain socket. Trying again [%s/%s]..."%(tries,max_tries)
404                        time.sleep(1)
405                        tries+=1
406                        if tries >= 5:
407                                print "[SERVER] Unable to open socket. Exiting..."
408                                sys.exit(1)
409                       
410        sa = server.socket.getsockname()
411        print "[SERVER] HTTPS on", sa[0], "port", sa[1]
412        server.startup(obj.exec_threads)
413       
414
415
Note: See TracBrowser for help on using the repository browser.