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(Base HTTPServer.HTTP Server):
""" A threaded HTTP server """
port = 80
# Maximum requests on hold
request_queue_s ize = 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_qu eue_size = reqSize
if workers is not None: self.workers = workers
self.sharedPath = os.path.dirname (os.path.abspat h(__file__))
# self.sharedPath must be set before calling self.getHandler s
self.handler = self.getHandler s()
self.jobQueue = Queue.Queue(sel f.request_queue _size)
addr = ('', self.port)
BaseHTTPServer. HTTPServer.__in it__(self, addr,
HTTPRequestHand ler)
# Exit threads when application exits
self.daemon_thr eads = True
self.createThre adPool()
self.serve_fore ver()
def getHandlers(sel f):
""" 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.sharedPat h) \
if x.endswith('.py ') and \
x != os.path.basenam e(__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.get argspec(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 createThreadPoo l(self):
""" Creates pool of worker threads, servicing requests """
self.tpool = []
for i in range(self.work ers):
self.tpool.appe nd(threading.Th read
(target=self.pr ocess_request_t hread,
args=()))
if self.daemon_thr eads:
self.tpool[-1].setDaemon(1)
self.tpool[-1].start()
def serve_forever(s elf):
""" Handle requests "forever" """
while 1:
self.handle_req uest()
def process_request (self, request, client_address) :
""" Task source: dispath requests to job queue """
self.jobQueue.p ut((request, client_address) )
def process_request _thread(self):
""" Task sink: process requests from job queue """
while 1:
request, client_address = self.jobQueue.g et()
try:
self.finish_req uest(request, client_address)
self.close_requ est(request)
except:
self.handle_err or(request, client_address)
self.close_requ est(request)
class HTTPRequestHand ler(BaseHTTPSer ver.BaseHTTPReq uestHandler):
""" Handles HTTP requests. GET is the only method currently
supported """
# Set HTTP protocol version 1.1
protocol_versio n = 'HTTP/1.1'
# Set default response headers
responseHeaders = {'Content-Type': 'text/html',
'Content-Length': '',
'Connection': 'close'}
def log_message(sel f, format, *args):
""" Log function: see BaseHTTPServer. py for info on arguments
"""
pass
def do_GET(self):
""" Handles HTTP GET requests """
name = self.path.lstri p('/')
try:
handler = self.server.han dler[name].run
except KeyError:
self.send_error (code=404, message='File %s not found' %
name)
else:
data = handler(self.he aders, self.responseHe aders)
# Set Content-Length header
self.responseHe aders['Content-Length'] = str(len(data))
# Send headers and content
self.send_respo nse(200, message='OK')
for name, value in self.responseHe aders.items():
self.send_heade r(name, value)
self.end_header s()
self.wfile.writ e(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.Messa ge
instance, while response is a dictionary containing the default
response headers.
"""
html = '<html><head><t itle></title></head><body>Hell o, 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.