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
15 STOPPED = 0
16 STARTING = None
17 STARTED = 1
18
19
21
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
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
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
57 self.state = STOPPED
58
59
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):
94
118
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
132
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
145 reexec_retry = 2
146
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
166 continue
167
168 try:
169 mtime = os.stat(filename).st_mtime
170 except OSError:
171
172 mtime = None
173
174 if filename not in self.mtimes:
175
176 self.mtimes[filename] = mtime
177 else:
178 if mtime is None or mtime > oldtime:
179
180 self.reexec()
181
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
202 """Restart the application engine (does not block)."""
203 self.stop()
204 self.start(blocking=False)
205
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
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
254
256 """Check timeout on all responses. (Internal)"""
257 if self.state == STARTED:
258 for req, resp in self.servings:
259 resp.check_timeout()
260
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
281
282 SIGHUP = None
283 SIGTERM = None
284
285 if hasattr(signal, "SIGHUP"):
286 - def SIGHUP(self, signum=None, frame=None):
288
289 if hasattr(signal, "SIGTERM"):
290 - def SIGTERM(self, signum=None, frame=None):
293
299
300
301
302
303
304 try:
305 import pwd, grp
306 except ImportError:
307 try:
308 os.umask
309 except AttributeError:
311 """Drop privileges. Not implemented on this platform."""
312 raise NotImplementedError
313 else:
314 umask = None
315
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
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
380