Package cherrypy :: Module _cpengine
[hide private]
[frames] | no frames]

Source Code for Module cherrypy._cpengine

  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(self, 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