#!/usr/bin/env python """ AppServer FUTURE * Implement the additional settings that are commented out below. """ from Common import * from Object import Object from ConfigurableForServerSidePath import ConfigurableForServerSidePath from Application import Application from PlugIn import PlugIn import Profiler from threading import Thread, Event globalAppServer = None # Concrete app servers have to set this variable which then allows # any Python code to access the app server singleton like so: # from WebKit.AppServer import globalAppServer DefaultConfig = { 'PrintConfigAtStartUp': 1, 'Verbose': 1, 'PlugIns': ['../PSP'], 'CheckInterval': 100, # @@ 2000-04-27 ce: None of the following settings are implemented # 'ApplicationClassName': 'Application', } class AppServerError(Exception): pass class AppServer(ConfigurableForServerSidePath, Object): ## Init ## def __init__(self, path=None): self._startTime = time.time() global globalAppServer assert globalAppServer is None, 'more than one app server; or __init__() invoked more than once' globalAppServer = self ConfigurableForServerSidePath.__init__(self) Object.__init__(self) if path is None: path = os.path.dirname(__file__) #os.getcwd() self._serverSidePath = os.path.abspath(path) self._webKitPath = os.path.abspath(os.path.dirname(__file__)) self._webwarePath = os.path.dirname(self._webKitPath) self._verbose = self.setting('Verbose') self._plugIns = [] self._reqCount = 0 self.checkForInstall() self.config() # cache the config self.printStartUpMessage() sys.setcheckinterval(self.setting('CheckInterval')) self._app = self.createApplication() self.loadPlugIns() self.running = 1 if self.isPersistent(): self._closeEvent = Event() self._closeThread = Thread(target=self.closeThread) ## self._closeThread.setDaemon(1) self._closeThread.start() def checkForInstall(self): """ Exits with an error message if Webware was not installed. """ if not os.path.exists(os.path.join(self._webwarePath, '_installed')): sys.stdout = sys.stderr print 'ERROR: You have not installed Webware.' print 'Please run install.py from inside the Webware directory.' print 'For example:' print '> cd ..' print '> python install.py' print sys.exit(0) def readyForRequests(self): """ Should be invoked by subclasses when they are finally ready to accept requests. Records some stats and prints a message. """ Profiler.readyTime = time.time() Profiler.readyDuration = Profiler.readyTime - Profiler.startTime print "Ready (%.2f seconds after launch)\n" % Profiler.readyDuration sys.stdout.flush() sys.stderr.flush() def closeThread(self): self._closeEvent.wait() self.shutDown() def initiateShutdown(self): self._closeEvent.set() def recordPID(self): """ Save the pid of the AppServer to a file name appserverpid.txt. """ pidfile = open(os.path.join(self._serverSidePath, "appserverpid.txt"),"w") if os.name == 'posix': pidfile.write(str(os.getpid())) else: try: import win32api except: print "win32 extensions not present. Webkit Will not be able to detatch from the controlling terminal." if sys.modules.has_key('win32api'): pidfile.write(str(win32api.GetCurrentProcess())) def shutDown(self): """ Subclasses may override and normally follow this sequence: 1. set self._shuttingDown to 1 2. class specific statements for shutting down 3. Invoke super's shutDown() e.g., AppServer.shutDown(self) """ print "Shutting down the AppServer" self._shuttingDown = 1 self.running = 0 self._app.shutDown() del self._plugIns del self._app if Profiler.profiler: print 'Writing profile stats to %s...' % Profiler.statsFilename Profiler.dumpStats() # you might also considering having a page/servlet that lets you dump the stats on demand print print 'WARNING: Applications run much slower when profiled, so turn off profiling in Launch.py when you are finished.' print print 'AppServer ran for %0.2f seconds.' % (time.time()-Profiler.startTime) print "AppServer has been shutdown" ## Configuration ## def defaultConfig(self): return DefaultConfig def configFilename(self): return self.serverSidePath('Configs/AppServer.config') def configReplacementValues(self): return { # Since these strings will be eval'ed we need to double escape any backslashes 'WebwarePath' : string.replace(self._webwarePath, '\\', '\\\\'), 'WebKitPath' : string.replace(self._webKitPath, '\\', '\\\\'), 'serverSidePath' : string.replace(self._serverSidePath, '\\', '\\\\'), } ## Network Server ## def createApplication(self): """ Creates and returns an application object. Invoked by __init__. """ return Application(server=self) def printStartUpMessage(self): """ Invoked by __init__. """ print 'WebKit AppServer', self.version() print 'part of Webware for Python' print 'Copyright 1999-2001 by Chuck Esterbrook. All Rights Reserved.' print 'WebKit and Webware are open source.' print 'Please visit: http://webware.sourceforge.net' print print 'Process id is', os.getpid() print 'Date/time is', time.asctime(time.localtime(time.time())) print if self.setting('PrintConfigAtStartUp'): self.printConfig() ## Plug-ins ## def plugIns(self): """ Returns a list of the plug-ins loaded by the app server. Each plug-in is a python package. """ return self._plugIns def plugIn(self, name, default=NoDefault): """ Returns the plug-in with the given name. """ # @@ 2001-04-25 ce: linear search. yuck. Plus we should guarantee plug-in name uniqueness anyway for pi in self._plugIns: if pi.name()==name: return pi if default is NoDefault: raise KeyError, name else: return default def loadPlugIn(self, path): """ Loads and returns the given plug-in. May return None if loading was unsuccessful (in which case this method prints a message saying so). Used by loadPlugIns(). """ plugIn = None path = self.serverSidePath(path) try: plugIn = PlugIn(self, path) willNotLoadReason = plugIn.load() if willNotLoadReason: print ' Plug-in %s cannot be loaded because:\n %s' % (path, willNotLoadReason) return None plugIn.install() except: import traceback traceback.print_exc(file=sys.stderr) self.error('Plug-in %s raised exception.' % path) return plugIn def loadPlugIns(self): """ A plug-in allows you to extend the functionality of WebKit without necessarily having to modify it's source. Plug-ins are loaded by AppServer at startup time, just before listening for requests. See the docs for PlugIn.py for more info. """ plugIns = self.setting('PlugIns') plugIns = map(lambda path, ssp=self.serverSidePath: ssp(path), plugIns) # Scan each directory named in the PlugInDirs list. # If those directories contain Python packages (that # don't have a "dontload" file) then add them to the # plugs in list. for plugInDir in self.setting('PlugInDirs'): plugInDir = self.serverSidePath(plugInDir) for filename in os.listdir(plugInDir): filename = os.path.normpath(os.path.join(plugInDir, filename)) if os.path.isdir(filename) and \ os.path.exists(os.path.join(filename, '__init__.py')) and \ os.path.exists(os.path.join(filename, 'Properties.py')) and \ not os.path.exists(os.path.join(filename, 'dontload')) and \ os.path.basename(filename)!='WebKit' and \ filename not in plugIns: plugIns.append(filename) print 'Plug-ins list:', string.join(plugIns, ', ') # Now that we have our plug-in list, load them... for plugInPath in plugIns: plugIn = self.loadPlugIn(plugInPath) if plugIn: self._plugIns.append(plugIn) print ## Access ## def version(self): if not hasattr(self,'_webKitVersionString'): from MiscUtils.PropertiesObject import PropertiesObject props = PropertiesObject(os.path.join(self.webKitPath(), 'Properties.py')) self._webKitVersionString = props['versionString'] return self._webKitVersionString def application(self): return self._app def startTime(self): """ Returns the time the app server was started (as seconds, like time()). """ return self._startTime def numRequests(self): """ Return the number of requests received by this server since it was launched. """ return self._reqCount def isPersistent(self): raise AbstractError, self.__class__ def serverSidePath(self, path=None): """ Returns the absolute server-side path of the WebKit app server. If the optional path is passed in, then it is joined with the server side directory to form a path relative to the app server. """ if path: return os.path.normpath(os.path.join(self._serverSidePath, path)) else: return self._serverSidePath def webwarePath(self): return self._webwarePath def webKitPath(self): return self._webKitPath ## Warnings and Errors ## def warning(self, msg): # @@ 2000-04-25 ce: would this be useful? raise NotImplementedError def error(self, msg): """ Flushes stdout and stderr, prints the message to stderr and exits with code 1. """ sys.stdout.flush() sys.stderr.flush() sys.stderr.write('ERROR: %s\n' % msg) sys.stderr.flush() sys.exit(1) # @@ 2000-05-29 ce: Doesn't work. Perhaps because of threads def main(): try: server = AppServer() return print "Ready" print print 'WARNING: There is nothing to do here with the abstract AppServer. Use one of the adapters such as WebKit.cgi (with ThreadedAppServer) or OneShot.cgi' server.shutDown() except Exception, exc: # Need to kill the Sweeper thread somehow print 'Caught exception:', exc print "Exiting AppServer" server.shutDown() del server sys.exit() def stop(*args, **kw): pidfile = os.path.join(os.path.dirname(__file__),"appserverpid.txt") pid = int(open(pidfile,"r").read()) #now what for windows? if os.name == 'posix': import signal os.kill(pid,signal.SIGINT) else: try: import win32process except: print "Win32 extensions not present. Webkit cannot terminate the running process." if sys.modules.has_key('win32process'): win32process.TerminateProcess(pid,0) if __name__=='__main__': main()