#!/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()