""" msHTTPServer.py - stand-alone single-web-application server (c) by Michael Stroeder This module is distributed under the terms of the GPL (GNU GENERAL PUBLIC LICENSE) Version 2 (see http://www.gnu.org/copyleft/gpl.html) $Id: msHTTPServer.py,v 1.62 2005/02/23 17:43:38 michael Exp $ """ __version__ = '0.2.0' import sys,os,time,ipadr,SocketServer,socket,traceback try: from os import getuid except ImportError: def getuid(): return None def split_server_address(v,server_address): """ Split a server address string host:port, with fall-back to port """ adr = v.split(':') if len(adr)==2: server_address = \ socket.gethostbyname(adr[0].strip()) or server_address[0], \ int(adr[1].strip()) or server_address[1] elif len(adr)==1: server_address = server_address[0],int(v.strip()) else: raise ValueError return server_address class HTTPServer(SocketServer.TCPServer): """ Base class for a HTTP server. """ def __init__(self, server_address, RequestHandlerClass): SocketServer.TCPServer.__init__(self, server_address, RequestHandlerClass) def server_bind(self): """Override server_bind to set socket options.""" self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) SocketServer.TCPServer.server_bind(self) def format_log(self,log_msg,client_address): return os.linesep.join( ['%s %s:%d %s\n' % ( time.strftime( '%Y-%m-%dT%H:%M:%SZ',time.gmtime(time.time()) ), client_address[0],client_address[1], log_msg, )] ) def handle_request(self): """Handle one request, possibly blocking.""" try: request, client_address = self.get_request() except socket.error: return if self.verify_request(request, client_address): try: self.process_request(request, client_address) except: self.handle_error(request, client_address) self.close_request(request) def handle_error(self,request,client_address): """ Generic low-level handler for exceptions """ exc_obj,exc_value,exc_traceback = sys.exc_info() if isinstance(exc_value,KeyboardInterrupt): raise KeyboardInterrupt elif isinstance(exc_value,IOError): if exc_value.errno==32 or exc_value.errno==104: # The user has aborted access and the remote # connection end-point is not available anymore sys.stdout.write(self.format_log( 'IOError %s: User probably interrupted connection.' % ( repr(exc_value.errno)), client_address )) elif exc_value.errno==24: # Ressource limit reached sys.stdout.write(self.format_log( str(exc_value), client_address )) else: sys.stderr.write(self.format_log( str(exc_value), client_address )) elif isinstance(exc_value,socket.error): sys.stderr.write(self.format_log( 'Socket error: %s' % (repr(exc_value.args)), client_address )) else: sys.stderr.write(self.format_log( 'Unhandled exception:\n'+''.join(traceback.format_exception(exc_obj,exc_value,exc_traceback,20)), client_address )) # Avoid memory leaks exc_obj=None;exc_value=None;exc_traceback=None del exc_obj;del exc_value;del exc_traceback try: import threading except ImportError: ThreadingHTTPServer = HTTPServer else: class HTTPThread(threading.Thread): """Thread class for HTTP handler thread""" def __init__(self,serverInstance,func,request,client_address,handle_error): self.serverInstance = serverInstance self._func=func self._request=request self._client_address=client_address self._handle_error=handle_error self.started=time.time() threading.Thread.__init__(self) self.setName( self.__class__.__name__+self.getName()[6:] ) def __repr__(self): return '%s:%d - %s started %s' % ( self._client_address[0],self._client_address[1], self.getName(), time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime(self.started)), ) def isAlive(self): """Check if remote end is still alive by sending empty string""" try: bytessent = self._request.send("") except IOError: return 0 else: return 1 def run(self): try: try: apply( self._func, (self._request,self._client_address) ) except: apply( self._handle_error, (self._request,self._client_address) ) raise finally: self.serverInstance.close_request(self._request) del self.serverInstance class ThreadingHTTPServer(HTTPServer): def process_request(self, request, client_address): """Start a new thread to process the request.""" t = HTTPThread( self,self.finish_request,request,client_address,self.handle_error ) t.start() if SocketServer.__version__<='0.3': def close_request(self,request): """Work around bug in SocketServer.BaseServer.handle_request() of Python 2.1""" return def PrintUsage(ErrorMsg=''): print """ usage: %s [options] Options: -h or -? Print out this message -d on/off demon mode (detach from console) Default: on -t on/off Run multi-threaded HTTP server. If starting multi-threaded fails the script falls backs to running a single-threaded HTTP server. Default: on -s on/off Have SSL on/off. -l [hostname:]port Listen to hostname:port. Either hostname:port or port is allowed. Default: your hostname:1760 -u numeric uid or username Switch to given UID or username after binding to socket. Default: Current UID if not started as root. nobody if started as root. """ % (sys.argv[0]) if ErrorMsg: print '*** Error: %s' % (ErrorMsg) sys.exit(1) # Get the server startup parameters from defaults and command-line options def GetCommandlineParams( server_address, run_detached=1, run_threaded=0, ssl_enabled=0, uidparam='' ): current_uid = getuid() if not current_uid is None: try: import pwd except ImportError: nopwd = 1 else: if os.getuid()==0 or os.geteuid()==0: try: uid = pwd.getpwnam(uidparam)[2] except KeyError: uid = pwd.getpwnam('nobody')[2] print """Warning: User %s (specified by configuration) does not exist on your system => using nobody as default""" % (uidparam) else: uid = current_uid nopwd = 0 else: uid = current_uid # Get startup arguments from command-line options import getopt try: optlist, args=getopt.getopt(sys.argv[1:],"?hs:d:t:u:l:") except getopt.error,e: PrintUsage(str(e)) sys.exit(1) for k,v in optlist: if k=="-d": flag = v.lower() if flag in ['on','off']: run_detached = (flag=='on') and (os.name=='posix') else: PrintUsage('Detach option (option -d) must be on or off.') if k=="-u": try: uid = int(v) except ValueError: if current_uid is None: print 'Changing UID is not available on this platform. Aborting.' sys.exit(1) if nopwd: print 'Module pwd is not available on this system. Aborting.' sys.exit(1) try: uid = pwd.getpwnam(v)[2] except KeyError: print "Fatal Error: User %s (specified by option -u) does not exist on your system" % (v) sys.exit(1) if k=="-l": try: server_address = split_server_address(v,server_address) except ValueError: PrintUsage('Bind address (option -l) has invalid format.') if k=="-t": flag = v.lower() if flag in ['on','off']: run_threaded = flag=='on' else: PrintUsage('Threading option (option -t) must be on or off.') if k=="-s": flag = v.lower() if flag in ['on','off']: ssl_enabled = flag=='on' else: PrintUsage('SSL option (option -s) must be on or off.') if (k=="-h") or (k=="-?"): PrintUsage() return ( (server_address[0],server_address[1]), run_detached, run_threaded, ssl_enabled,uid ) def RunServer( HTTPHandlerClass, server_address, run_detached, run_threaded, ssl_enabled=0, ssl_randfile='', ssl_Protocols=[], ssl_CertificateFile='', ssl_CertificateKeyFile='', ssl_CACertificateFile='', ssl_VerifyClient=0, ssl_VerifyDepth=1, ): # We will never read from stdin => close for security reasons sys.stdin.close() # Change current directory if not os.path.isdir(os.path.abspath(HTTPHandlerClass.server_env['DOCUMENT_ROOT'])): print 'Warning: document_root %s does not exist.' % ( HTTPHandlerClass.server_env['DOCUMENT_ROOT'] ) if ssl_enabled: PrintUsage('SSL currently not supported!') ServerClass = { 0:HTTPServer,1:ThreadingHTTPServer }[run_threaded] ServerInstance = ServerClass(server_address,HTTPHandlerClass) # Set the server name if ServerInstance.server_address[0]: try: ServerInstance.server_name = socket.gethostbyaddr(ServerInstance.server_address[0])[0] except socket.error: ServerInstance.server_name = ServerInstance.server_address[0] else: ServerInstance.server_name = socket.gethostbyaddr(socket.gethostbyname(socket.gethostname()))[0] # Set the server port ServerInstance.server_port = ServerInstance.server_address[1] # Write startup info to stderr sys.stderr.write( '%s Started %s web server on %s:%s with SSL %s\n' % ( time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())), { 0:'single-threaded',1:'multi-threaded' }[run_threaded], ServerInstance.server_name, ServerInstance.server_port, {0:'disabled',1:'enabled'}[ssl_enabled], ) ) sys.stderr.write('Accepted IP address ranges: %s' % ( ','.join([ ipadr.AddrMask_Tuple2Str(a) for a in HTTPHandlerClass.access_allowed ]))) print """ Point your favourite browser to %s://%s:%s%s to access the web application.""" % ( {0:'http',1:'https'}[ssl_enabled], ServerInstance.server_name, ServerInstance.server_port, HTTPHandlerClass.script_name ) if run_detached: sys.stdout = HTTPHandlerClass.debug_log sys.stderr = HTTPHandlerClass.error_log try: ServerInstance.serve_forever() finally: ServerInstance.socket.close() else: try: ServerInstance.serve_forever() except KeyboardInterrupt: print 'Shutting down web server' ServerInstance.socket.close() return # RunServer()