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.