source file: /Library/Python/2.3/site-packages/CherryPy-3.0.1-py2.3.egg/cherrypy/_cpengine.py
file stats: 246 lines, 141 executed: 57.3% covered
   1. """Create and manage the CherryPy application engine."""
   2. 
   3. import cgi
   4. import os
   5. import re
   6. import signal
   7. import sys
   8. import threading
   9. import time
  10. 
  11. import cherrypy
  12. from cherrypy import _cprequest
  13. 
  14. # Use a flag to indicate the state of the application engine.
  15. STOPPED = 0
  16. STARTING = None
  17. STARTED = 1
  18. 
  19. 
  20. class PerpetualTimer(threading._Timer):
  21. 
  22.     def run(self):
  23.         while True:
  24.             self.finished.wait(self.interval)
  25.             if self.finished.isSet():
  26.                 return
  27.             self.function(*self.args, **self.kwargs)
  28. 
  29. 
  30. class Engine(object):
  31.     """Interface for (HTTP) applications, plus process controls.
  32. 
  33.     Servers and gateways should not instantiate Request objects directly.
  34.     Instead, they should ask an Engine object for a request via the
  35.     Engine.request method.
  36. 
  37.     Blocking is completely optional! The Engine's blocking, signal and
  38.     interrupt handling, privilege dropping, and autoreload features are
  39.     not a good idea when driving CherryPy applications from another
  40.     deployment tool (but an Engine is a great deployment tool itself).
  41.     By calling start(blocking=False), you avoid blocking and interrupt-
  42.     handling issues. By setting Engine.SIGHUP and Engine.SIGTERM to None,
  43.     you can completely disable the signal handling (and therefore disable
  44.     autoreloads triggered by SIGHUP). Set Engine.autoreload_on to False
  45.     to disable autoreload entirely.
  46.     """
  47. 
  48.     # Configurable attributes
  49.     request_class = _cprequest.Request
  50.     response_class = _cprequest.Response
  51.     deadlock_poll_freq = 60
  52.     autoreload_on = True
  53.     autoreload_frequency = 1
  54.     autoreload_match = ".*"
  55. 
  56.     def __init__(self):
  57.         self.state = STOPPED
  58. 
  59.         # Startup/shutdown hooks
  60.         self.on_start_engine_list = []
  61.         self.on_stop_engine_list = []
  62.         self.on_start_thread_list = []
  63.         self.on_stop_thread_list = []
  64.         self.seen_threads = {}
  65. 
  66.         self.servings = []
  67. 
  68.         self.mtimes = {}
  69.         self.reload_files = []
  70. 
  71.         self.monitor_thread = None
  72. 
  73.     def start(self, blocking=True):
  74.         """Start the application engine."""
  75.         self.state = STARTING
  76. 
  77.         cherrypy.checker()
  78. 
  79.         for func in self.on_start_engine_list:
  80.             func()
  81. 
  82.         self.state = STARTED
  83. 
  84.         self._set_signals()
  85. 
  86.         freq = self.deadlock_poll_freq
  87.         if freq > 0:
  88.             self.monitor_thread = PerpetualTimer(freq, self.monitor)
  89.             self.monitor_thread.setName("CPEngine Monitor")
  90.             self.monitor_thread.start()
  91. 
  92.         if blocking:
  93.             self.block()
  94. 
  95.     def block(self):
  96.         """Block forever (wait for stop(), KeyboardInterrupt or SystemExit)."""
  97.         try:
  98.             while self.state != STOPPED:
  99.                 # Note that autoreload_frequency controls
 100.                 # sleep timer even if autoreload is off.
 101.                 time.sleep(self.autoreload_frequency)
 102.                 if self.autoreload_on:
 103.                     self.autoreload()
 104.         except KeyboardInterrupt:
 105.             cherrypy.log("<Ctrl-C> hit: shutting down app engine", "ENGINE")
 106.             cherrypy.server.stop()
 107.             self.stop()
 108.         except SystemExit:
 109.             cherrypy.log("SystemExit raised: shutting down app engine", "ENGINE")
 110.             cherrypy.server.stop()
 111.             self.stop()
 112.             raise
 113.         except:
 114.             # Don't bother logging, since we're going to re-raise.
 115.             # Note that we don't stop the HTTP server here.
 116.             self.stop()
 117.             raise
 118. 
 119.     def reexec(self):
 120.         """Re-execute the current process."""
 121.         cherrypy.server.stop()
 122.         self.stop()
 123. 
 124.         args = sys.argv[:]
 125.         cherrypy.log("Re-spawning %s" % " ".join(args), "ENGINE")
 126.         args.insert(0, sys.executable)
 127. 
 128.         if sys.platform == "win32":
 129.             args = ['"%s"' % arg for arg in args]
 130. 
 131.         # Some platforms (OS X) will error if all threads are not
 132.         # ABSOLUTELY terminated. See http://www.cherrypy.org/ticket/581.
 133.         for trial in xrange(self.reexec_retry * 10):
 134.             try:
 135.                 os.execv(sys.executable, args)
 136.                 return
 137.             except OSError, x:
 138.                 if x.errno != 45:
 139.                     raise
 140.                 time.sleep(0.1)
 141.         else:
 142.             raise
 143. 
 144.     # Number of seconds to retry reexec if os.execv fails.
 145.     reexec_retry = 2
 146. 
 147.     def autoreload(self):
 148.         """Reload the process if registered files have been modified."""
 149.         sysfiles = []
 150.         for k, m in sys.modules.items():
 151.             if re.match(self.autoreload_match, k):
 152.                 if hasattr(m, "__loader__"):
 153.                     if hasattr(m.__loader__, "archive"):
 154.                         k = m.__loader__.archive
 155.                 k = getattr(m, "__file__", None)
 156.                 sysfiles.append(k)
 157. 
 158.         for filename in sysfiles + self.reload_files:
 159.             if filename:
 160.                 if filename.endswith(".pyc"):
 161.                     filename = filename[:-1]
 162. 
 163.                 oldtime = self.mtimes.get(filename, 0)
 164.                 if oldtime is None:
 165.                     # Module with no .py file. Skip it.
 166.                     continue
 167. 
 168.                 try:
 169.                     mtime = os.stat(filename).st_mtime
 170.                 except OSError:
 171.                     # Either a module with no .py file, or it's been deleted.
 172.                     mtime = None
 173. 
 174.                 if filename not in self.mtimes:
 175.                     # If a module has no .py file, this will be None.
 176.                     self.mtimes[filename] = mtime
 177.                 else:
 178.                     if mtime is None or mtime > oldtime:
 179.                         # The file has been deleted or modified.
 180.                         self.reexec()
 181. 
 182.     def stop(self):
 183.         """Stop the application engine."""
 184.         if self.state != STOPPED:
 185.             for thread_ident, i in self.seen_threads.iteritems():
 186.                 for func in self.on_stop_thread_list:
 187.                     func(i)
 188.             self.seen_threads.clear()
 189. 
 190.             for func in self.on_stop_engine_list:
 191.                 func()
 192. 
 193.             if self.monitor_thread:
 194.                 self.monitor_thread.cancel()
 195.                 self.monitor_thread.join()
 196.                 self.monitor_thread = None
 197. 
 198.             self.state = STOPPED
 199.             cherrypy.log("CherryPy shut down", "ENGINE")
 200. 
 201.     def restart(self):
 202.         """Restart the application engine (does not block)."""
 203.         self.stop()
 204.         self.start(blocking=False)
 205. 
 206.     def wait(self):
 207.         """Block the caller until ready to receive requests (or error)."""
 208.         while not (self.state == STARTED):
 209.             time.sleep(.1)
 210. 
 211.     def request(self, local_host, remote_host, scheme="http",
 212.                 server_protocol="HTTP/1.1"):
 213.         """Obtain and return an HTTP Request object. (Core)
 214. 
 215.         local_host should be an http.Host object with the server info.
 216.         remote_host should be an http.Host object with the client info.
 217.         scheme: either "http" or "https"; defaults to "http"
 218.         """
 219.         if self.state == STOPPED:
 220.             req = NotReadyRequest("The CherryPy engine has stopped.")
 221.         elif self.state == STARTING:
 222.             req = NotReadyRequest("The CherryPy engine could not start.")
 223.         else:
 224.             # Only run on_start_thread_list if the engine is running.
 225.             threadID = threading._get_ident()
 226.             if threadID not in self.seen_threads:
 227.                 i = len(self.seen_threads) + 1
 228.                 self.seen_threads[threadID] = i
 229. 
 230.                 for func in self.on_start_thread_list:
 231.                     func(i)
 232.             req = self.request_class(local_host, remote_host, scheme,
 233.                                      server_protocol)
 234.         resp = self.response_class()
 235.         cherrypy.serving.load(req, resp)
 236.         self.servings.append((req, resp))
 237.         return req
 238. 
 239.     def release(self):
 240.         """Close and de-reference the current request and response. (Core)"""
 241.         req = cherrypy.serving.request
 242. 
 243.         try:
 244.             req.close()
 245.         except:
 246.             cherrypy.log(traceback=True)
 247. 
 248.         try:
 249.             self.servings.remove((req, cherrypy.serving.response))
 250.         except ValueError:
 251.             pass
 252. 
 253.         cherrypy.serving.clear()
 254. 
 255.     def monitor(self):
 256.         """Check timeout on all responses. (Internal)"""
 257.         if self.state == STARTED:
 258.             for req, resp in self.servings:
 259.                 resp.check_timeout()
 260. 
 261.     def start_with_callback(self, func, args=None, kwargs=None):
 262.         """Start the given func in a new thread, then start self and block."""
 263. 
 264.         if args is None:
 265.             args = ()
 266.         if kwargs is None:
 267.             kwargs = {}
 268.         args = (func,) + args
 269. 
 270.         def _callback(func, *a, **kw):
 271.             self.wait()
 272.             func(*a, **kw)
 273.         t = threading.Thread(target=_callback, args=args, kwargs=kwargs)
 274.         t.setName("CPEngine Callback " + t.getName())
 275.         t.start()
 276. 
 277.         self.start()
 278. 
 279. 
 280.     #                           Signal handling                           #
 281. 
 282.     SIGHUP = None
 283.     SIGTERM = None
 284. 
 285.     if hasattr(signal, "SIGHUP"):
 286.         def SIGHUP(self, signum=None, frame=None):
 287.             self.reexec()
 288. 
 289.     if hasattr(signal, "SIGTERM"):
 290.         def SIGTERM(signum=None, frame=None):
 291.             cherrypy.server.stop()
 292.             self.stop()
 293. 
 294.     def _set_signals(self):
 295.         if self.SIGHUP:
 296.             signal.signal(signal.SIGHUP, self.SIGHUP)
 297.         if self.SIGTERM:
 298.             signal.signal(signal.SIGTERM, self.SIGTERM)
 299. 
 300. 
 301.     #                           Drop privileges                           #
 302. 
 303.     # Special thanks to Gavin Baker: http://antonym.org/node/100.
 304.     try:
 305.         import pwd, grp
 306.     except ImportError:
 307.         try:
 308.             os.umask
 309.         except AttributeError:
 310.             def drop_privileges(self):
 311.                 """Drop privileges. Not implemented on this platform."""
 312.                 raise NotImplementedError
 313.         else:
 314.             umask = None
 315. 
 316.             def drop_privileges(self):
 317.                 """Drop privileges. Windows version (umask only)."""
 318.                 if self.umask is not None:
 319.                     old_umask = os.umask(self.umask)
 320.                     cherrypy.log('umask old: %03o, new: %03o' %
 321.                                  (old_umask, self.umask), "PRIV")
 322.     else:
 323.         uid = None
 324.         gid = None
 325.         umask = None
 326. 
 327.         def drop_privileges(self):
 328.             """Drop privileges. UNIX version (uid, gid, and umask)."""
 329.             if not (self.uid is None and self.gid is None):
 330.                 if self.uid is None:
 331.                     uid = None
 332.                 elif isinstance(self.uid, basestring):
 333.                     uid = self.pwd.getpwnam(self.uid)[2]
 334.                 else:
 335.                     uid = self.uid
 336. 
 337.                 if self.gid is None:
 338.                     gid = None
 339.                 elif isinstance(self.gid, basestring):
 340.                     gid = self.grp.getgrnam(self.gid)[2]
 341.                 else:
 342.                     gid = self.gid
 343. 
 344.                 def names():
 345.                     name = self.pwd.getpwuid(os.getuid())[0]
 346.                     group = self.grp.getgrgid(os.getgid())[0]
 347.                     return name, group
 348. 
 349.                 cherrypy.log('Started as %r/%r' % names(), "PRIV")
 350.                 if gid is not None:
 351.                     os.setgid(gid)
 352.                 if uid is not None:
 353.                     os.setuid(uid)
 354.                 cherrypy.log('Running as %r/%r' % names(), "PRIV")
 355. 
 356.             if self.umask is not None:
 357.                 old_umask = os.umask(self.umask)
 358.                 cherrypy.log('umask old: %03o, new: %03o' %
 359.                              (old_umask, self.umask), "PRIV")
 360. 
 361. 
 362. class NotReadyRequest:
 363. 
 364.     throw_errors = True
 365.     show_tracebacks = True
 366.     error_page = {}
 367. 
 368.     def __init__(self, msg):
 369.         self.msg = msg
 370.         self.protocol = (1,1)
 371. 
 372.     def close(self):
 373.         pass
 374. 
 375.     def run(self, method, path, query_string, protocol, headers, rfile):
 376.         self.method = "GET"
 377.         cherrypy.HTTPError(503, self.msg).set_response()
 378.         cherrypy.response.finalize()
 379.         return cherrypy.response
 380.