"""Create and manage the CherryPy application engine."""importcgiimportosimportsignalimportsysimportthreadingimporttimeimportcherrypyfromcherrypyimport_cprequest# Use a flag to indicate the state of the application engine.STOPPED=0STARTING=NoneSTARTED=1deffileattr(m):ifhasattr(m,"__loader__"):ifhasattr(m.__loader__,"archive"):returnm.__loader__.archivereturngetattr(m,"__file__",None)try:ifhasattr(signal,"SIGHUP"):defSIGHUP(signum=None,frame=None):cherrypy.engine.reexec()signal.signal(signal.SIGHUP,SIGHUP)ifhasattr(signal,"SIGTERM"):defSIGTERM(signum=None,frame=None):cherrypy.server.stop()cherrypy.engine.stop()signal.signal(signal.SIGTERM,SIGTERM)exceptValueError,_signal_exc:if_signal_exc.args[0]!="signal only works in main thread":raiseclassEngine(object):"""The application engine, which exposes a request interface to (HTTP) servers."""request_class=_cprequest.Requestresponse_class=_cprequest.Responsedef__init__(self):self.state=STOPPED# Startup/shutdown hooksself.on_start_engine_list=[]self.on_stop_engine_list=[]self.on_start_thread_list=[]self.on_stop_thread_list=[]self.seen_threads={}self.servings=[]self.mtimes={}self.reload_files=[]self.monitor_thread=Nonedefstart(self,blocking=True):"""Start the application engine."""self.state=STARTINGconf=cherrypy.config.get# Output config options to logifconf("log_config",True):cherrypy.config.log_config()forfuncinself.on_start_engine_list:func()self.state=STARTEDfreq=float(cherrypy.config.get('deadlock_poll_freq',60))iffreq>0:self.monitor_thread=threading.Timer(freq,self.monitor)self.monitor_thread.start()ifblocking:self.block()defblock(self):"""Block forever (wait for stop(), KeyboardInterrupt or SystemExit)."""try:autoreload=cherrypy.config.get('autoreload.on',False)ifautoreload:i=0freq=cherrypy.config.get('autoreload.frequency',1)whileself.state!=STOPPED:time.sleep(.1)# Autoreloadifautoreload:i+=.1ifi>freq:i=0self.autoreload()exceptKeyboardInterrupt:cherrypy.log("<Ctrl-C> hit: shutting down app engine","ENGINE")cherrypy.server.stop()self.stop()exceptSystemExit:cherrypy.log("SystemExit raised: shutting down app engine","ENGINE")cherrypy.server.stop()self.stop()raiseexcept:# Don't bother logging, since we're going to re-raise.# Note that we don't stop the HTTP server here.self.stop()raisedefreexec(self):"""Re-execute the current process."""cherrypy.server.stop()self.stop()args=sys.argv[:]cherrypy.log("Re-spawning %s"%" ".join(args),"ENGINE")args.insert(0,sys.executable)ifsys.platform=="win32":args=['"%s"'%argforarginargs]os.execv(sys.executable,args)defautoreload(self):"""Reload the process if registered files have been modified."""forfilenameinmap(fileattr,sys.modules.values())+self.reload_files:iffilename:iffilename.endswith(".pyc"):filename=filename[:-1]oldtime=self.mtimes.get(filename,0)ifoldtimeisNone:# Module with no .py file. Skip it.continuetry:mtime=os.stat(filename).st_mtimeexceptOSError:# Either a module with no .py file, or it's been deleted.mtime=Noneiffilenamenotinself.mtimes:# If a module has no .py file, this will be None.self.mtimes[filename]=mtimeelse:ifmtimeisNoneormtime>oldtime:# The file has been deleted or modified.self.reexec()defstop(self):"""Stop the application engine."""ifself.state!=STOPPED:forthread_ident,iinself.seen_threads.iteritems():forfuncinself.on_stop_thread_list:func(i)self.seen_threads.clear()forfuncinself.on_stop_engine_list:func()ifself.monitor_thread:self.monitor_thread.cancel()self.monitor_thread=Noneself.state=STOPPEDcherrypy.log("CherryPy shut down","ENGINE")defrestart(self):"""Restart the application engine (doesn't block)."""self.stop()self.start(blocking=False)defwait(self):"""Block the caller until ready to receive requests (or error)."""whilenotself.ready:time.sleep(.1)def_is_ready(self):returnbool(self.state==STARTED)ready=property(_is_ready,doc="Return True if the engine is ready to"" receive requests, False otherwise.")defrequest(self,local_host,remote_host,scheme="http"):"""Obtain an HTTP Request object. local_host should be an http.Host object with the server info. remote_host should be an http.Host object with the client info. scheme: either "http" or "https"; defaults to "http" """ifself.state==STOPPED:req=NotReadyRequest("The CherryPy engine has stopped.")elifself.state==STARTING:req=NotReadyRequest("The CherryPy engine could not start.")else:# Only run on_start_thread_list if the engine is running.threadID=threading._get_ident()ifthreadIDnotinself.seen_threads:i=len(self.seen_threads)+1self.seen_threads[threadID]=iforfuncinself.on_start_thread_list:func(i)req=self.request_class(local_host,remote_host,scheme)cherrypy.serving.request=reqcherrypy.serving.response=resp=self.response_class()self.servings.append((req,resp))returnreqdefmonitor(self):"""Check timeout on all responses."""ifself.state==STARTED:forreq,respinself.servings:resp.check_timeout()freq=float(cherrypy.config.get('deadlock_poll_freq',60))self.monitor_thread=threading.Timer(freq,self.monitor)self.monitor_thread.start()defstart_with_callback(self,func,args=None,kwargs=None):"""Start, then callback the given func in a new thread."""ifargsisNone:args=()ifkwargsisNone:kwargs={}args=(func,)+argsdef_callback(func,*a,**kw):self.wait()func(*a,**kw)t=threading.Thread(target=_callback,args=args,kwargs=kwargs)t.setName("CPEngine Callback "+t.getName())t.start()self.start()classNotReadyRequest:def__init__(self,msg):self.msg=msgself.protocol=(1,1)defclose(self):passdefrun(self,method,path,query_string,protocol,headers,rfile):self.method="GET"cherrypy.HTTPError(503,self.msg).set_response()cherrypy.response.finalize()returncherrypy.responsedefdrop_privileges(new_user='nobody',new_group='nogroup'):"""Drop privileges. UNIX only."""# Special thanks to Gavin Baker: http://antonym.org/node/100.importpwd,grpdefnames():returnpwd.getpwuid(os.getuid())[0],grp.getgrgid(os.getgid())[0]name,group=names()cherrypy.log('Started as %r/%r'%(name,group),"PRIV")ifos.getuid()!=0:# We're not root so, like, whatever dude.cherrypy.log("Already running as %r"%name,"PRIV")return# Try setting the new uid/gid (from new_user/new_group).try:os.setgid(grp.getgrnam(new_group)[2])exceptOSError,e:cherrypy.log('Could not set effective group id: %r'%e,"PRIV")try:os.setuid(pwd.getpwnam(new_user)[2])exceptOSError,e:cherrypy.log('Could not set effective user id: %r'%e,"PRIV")# Ensure a very convervative umaskold_umask=os.umask(077)cherrypy.log('Old umask: %o, new umask: 077'%old_umask,"PRIV")cherrypy.log('Running as %r/%r'%names(),"PRIV")