Hi, all I would like some feedback on a multithreaded HTTP server I've written. The server serves python-scripts by dynamically loading scripts in the same directory as itself. When a request is made to one of these scripts the script is executed and its output is returned to the requester.
Here is the server code, HTTPServer.py: # Basic, threaded HTTP server, serving requests via python scripts # Author: Tor Erik Soenvisen # Std lib imports import BaseHTTPServer, os, threading, Queue class HTTPServer(BaseHTTPServer.HTTPServer): """ A threaded HTTP server """ port = 80 # Maximum requests on hold request_queue_size = 250 # Maximum requests serviced concurrently workers = 20 def __init__(self, port=None, reqSize=None, workers=None): if port is not None: self.port = port if reqSize is not None: self.request_queue_size = reqSize if workers is not None: self.workers = workers self.sharedPath = os.path.dirname(os.path.abspath(__file__)) # self.sharedPath must be set before calling self.getHandlers self.handler = self.getHandlers() self.jobQueue = Queue.Queue(self.request_queue_size) addr = ('', self.port) BaseHTTPServer.HTTPServer.__init__(self, addr, HTTPRequestHandler) # Exit threads when application exits self.daemon_threads = True self.createThreadPool() self.serve_forever() def getHandlers(self): """ Imports all python scripts in the current directory except this one. These scripts are the response generators corresponding to all valid path requests. The idea is to comprise something similar to a lightweight CGI, where each script generate HTTP responses to HTTP requests """ import inspect # 1. List all files in current directory # 2. Skip files not having a .py extension, in addition to this file # 3. Slice of .py extension handler = dict.fromkeys([x[:-3] for x in os.listdir (self.sharedPath) \ if x.endswith('.py') and \ x != os.path.basename(__file__)]) for name in handler: handler[name] = __import__(name) # Make sure the handler contains a run function accepting at least # one parameter if not hasattr(handler[name], 'run') or \ len(inspect.getargspec(handler[name].run)[0]) != 2: print 'Handler %s.py dropped because it lacks ' % name + \ 'a function named run accepting two arguments' del handler[name] return handler def createThreadPool(self): """ Creates pool of worker threads, servicing requests """ self.tpool = [] for i in range(self.workers): self.tpool.append(threading.Thread (target=self.process_request_thread, args=())) if self.daemon_threads: self.tpool[-1].setDaemon(1) self.tpool[-1].start() def serve_forever(self): """ Handle requests "forever" """ while 1: self.handle_request() def process_request(self, request, client_address): """ Task source: dispath requests to job queue """ self.jobQueue.put((request, client_address)) def process_request_thread(self): """ Task sink: process requests from job queue """ while 1: request, client_address = self.jobQueue.get() try: self.finish_request(request, client_address) self.close_request(request) except: self.handle_error(request, client_address) self.close_request(request) class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): """ Handles HTTP requests. GET is the only method currently supported """ # Set HTTP protocol version 1.1 protocol_version = 'HTTP/1.1' # Set default response headers responseHeaders = {'Content-Type': 'text/html', 'Content-Length': '', 'Connection': 'close'} def log_message(self, format, *args): """ Log function: see BaseHTTPServer.py for info on arguments """ pass def do_GET(self): """ Handles HTTP GET requests """ name = self.path.lstrip('/') try: handler = self.server.handler[name].run except KeyError: self.send_error(code=404, message='File %s not found' % name) else: data = handler(self.headers, self.responseHeaders) # Set Content-Length header self.responseHeaders['Content-Length'] = str(len(data)) # Send headers and content self.send_response(200, message='OK') for name, value in self.responseHeaders.items(): self.send_header(name, value) self.end_headers() self.wfile.write(data) if __name__ == '__main__': """ For testing purposes """ HTTPServer(port=8081) Pointing the browser to http://localhost:8081/index yields Hello, world! when the script index.py is contained in the same directory as the server. index.py: def run(request, response): """ Entrypoint function, called by web-server Argument request is the request headers as a mimetools.Message instance, while response is a dictionary containing the default response headers. """ html = '<html><head><title></title></head><body>Hello, world!</body> </html>' return html Suggestions for making the server faster, more secure, more general, and/or more compact will be appreciated. Regards, Tor Erik PS: Yes, I know there exist heaps of web-servers out there. -- http://mail.python.org/mailman/listinfo/python-list