1 """Things to do when the TurboGears server is started."""
2
3 __all__ = [
4 'call_on_startup',
5 'call_on_shutdown',
6 'reloader_thread',
7 'start_bonjour',
8 'stop_bonjour',
9 'start_server',
10 'startTurboGears',
11 'stopTurboGears',
12 'webpath',
13 ]
14
15 import atexit
16 import errno
17 import logging
18 import os
19 import sys
20 import signal
21 import time
22
23 from os.path import abspath, exists
24
25 import pkg_resources
26 import cherrypy
27 from cherrypy import _cputil, request, server
28 from cherrypy._cpwsgi import wsgiApp, CPHTTPRequest
29 from cherrypy._cpwsgiserver import CherryPyWSGIServer
30
31 pkg_resources.require("TurboGears")
32
33 from turbogears import config, database, scheduler, view
34 from turbogears.database import hub_registry, EndTransactionsFilter
35 from turbogears.filters import (MonkeyDecodingFilter, NestedVariablesFilter,
36 SafeMultipartFilter, VirtualPathFilter)
37
38
39
40 DNS_SD_PID = None
41 call_on_startup = []
42 call_on_shutdown = []
43 webpath = ''
44 log = logging.getLogger("turbogears.startup")
45
46
47
49 """Monkeypatch for the reloader provided by CherryPy.
50
51 This reloader is designed to reload a single package. This is
52 more efficient and, more important, compatible with zipped
53 libraries that may not provide access to the individual files."""
54
55 def archive_selector(module):
56 if hasattr(module, '__loader__'):
57 if hasattr(module.__loader__, 'archive'):
58 return module.__loader__.archive
59 return module
60
61 mtimes = {}
62 package = config.get("autoreload.package", None)
63 if package is None:
64 print ("TurboGears requires autoreload.package to be set. "
65 "It can be an empty value, which will use CherryPy's default "
66 "behavior which is to check every module. Setting an actual "
67 "package makes the check much faster.")
68 return
69 while cherrypy.lib.autoreload.RUN_RELOADER:
70 if package:
71 modnames = filter(lambda modname: modname.startswith(package),
72 sys.modules.keys())
73 modlist = [sys.modules[modname] for modname in modnames]
74 else:
75 modlist = map(archive_selector, sys.modules.values())
76 for filename in filter(lambda v: v,
77 map(lambda m: getattr(m, "__file__", None), modlist)):
78 if filename.endswith(".kid") or filename == "<string>":
79 continue
80 orig_filename = filename
81 if filename.endswith(".pyc"):
82 filename = filename[:-1]
83 try:
84 mtime = os.stat(filename).st_mtime
85 except OSError, e:
86 if orig_filename.endswith('.pyc') and e[0] == errno.ENOENT:
87
88
89
90 try: os.unlink(orig_filename)
91 except: pass
92 sys.exit(3)
93 if filename not in mtimes:
94 mtimes[filename] = mtime
95 continue
96 if mtime > mtimes[filename]:
97 sys.exit(3)
98 time.sleep(freq)
99
100 cherrypy.lib.autoreload.reloader_thread = reloader_thread
101
102 old_object_trail = _cputil.get_object_trail
103
104
106 trail = old_object_trail(object_path)
107 try:
108 request.object_trail = trail
109 except AttributeError:
110 pass
111 return trail
112
113 _cputil.get_object_trail = get_object_trail
114
115
116
118 """Register the TurboGears server with the Bonjour framework.
119
120 Currently only Unix-like systems are supported where either the 'avahi'
121 daemon (Linux etc.) is available or the 'dns-sd' program (Mac OS X).
122
123 """
124 global DNS_SD_PID
125 if DNS_SD_PID:
126 return
127 if not getattr(cherrypy, 'root', None):
128 return
129 if not package:
130 package = cherrypy.root.__module__
131 package = package[:package.find('.')]
132
133 host = config.get('server.socket_host', '')
134 port = str(config.get('server.socket_port'))
135 env = config.get('server.environment')
136 name = package + ": " + env
137 type = "_http._tcp"
138
139 cmds = [['/usr/bin/avahi-publish-service', ["-H", host, name, type, port]],
140 ['/usr/bin/dns-sd', ['-R', name, type, "."+host, port, "path=/"]]]
141
142 for cmd, args in cmds:
143
144
145
146
147 if exists(cmd):
148 DNS_SD_PID = os.spawnv(os.P_NOWAIT, cmd, [cmd]+args)
149 atexit.register(stop_bonjour)
150 break
151
153 """Stop the bonjour publishing daemon if it is running."""
154 if not DNS_SD_PID:
155 return
156 try:
157 os.kill(DNS_SD_PID, signal.SIGTERM)
158 except OSError:
159 pass
160
162 """Handles TurboGears tasks when the CherryPy server starts.
163
164 This performs the following initialization tasks (in given order):
165
166 * Turns off CherryPy's logging filter when in development mode
167 * If logging is not already set up, turns on old-style stdlib logging.
168 * Adds a static filter for TurboGears's static files (URL '/tg_static').
169 * Adds a static filter for TurboGears's JavaScript files (URL '/tg_js').
170 * Loads the template engines and the base templates.
171 * Adds the CherryPy request filters to the root controller.
172 * Adds the decoding filter to the root URL ('/') if enabled in the
173 configuration.
174 * Registers the server with the Bonjour framework, if available.
175 * Calls 'turbogears.database.bind_metadata' when using SQLAlchemy.
176 * Loads all turbogears.extensions entry points and calls their
177 'start_extension' method.
178 * Calls the callables registered in 'turbogears.call_on_startup'.
179 * Starts the TurboGears scheduler.
180
181 """
182 global webpath
183 conf = config.get
184 rfn = pkg_resources.resource_filename
185
186 cherrypy.config.environments['development'][
187 'log_debug_info_filter.on'] = False
188
189
190
191 if not conf('tg.new_style_logging', False):
192 if conf('server.log_to_screen'):
193 setuplog = logging.getLogger()
194 setuplog.setLevel(logging.DEBUG)
195 fmt = logging.Formatter(
196 "%(asctime)s %(name)s %(levelname)s %(message)s")
197 handler = logging.StreamHandler(sys.stdout)
198 handler.setLevel(logging.DEBUG)
199 handler.setFormatter(fmt)
200 setuplog.addHandler(handler)
201
202 logfile = conf('server.log_file')
203 if logfile:
204 setuplog = logging.getLogger('turbogears.access')
205 setuplog.propagate = 0
206 fmt = logging.Formatter("%(message)s")
207 handler = logging.FileHandler(logfile)
208 handler.setLevel(logging.INFO)
209 handler.setFormatter(fmt)
210 setuplog.addHandler(handler)
211
212
213 config.update({'/tg_static': {
214 'static_filter.on': True,
215 'static_filter.dir': abspath(rfn(__name__, 'static')),
216 'log_debug_info_filter.on': False,
217 }})
218 config.update({'/tg_js': {
219 'static_filter.on': True,
220 'static_filter.dir': abspath(rfn(__name__, 'static/js')),
221 'log_debug_info_filter.on': False,
222 }})
223
224 if conf('decoding_filter.on', path='/') is None:
225 config.update({'/': {
226 'decoding_filter.on': True,
227 'decoding_filter.encoding': conf('kid.encoding', 'utf8')
228 }})
229
230
231 view.load_engines()
232 view.loadBaseTemplates()
233
234
235 webpath = conf('server.webpath') or ''
236
237 if getattr(cherrypy, 'root', None):
238 if not hasattr(cherrypy.root, '_cp_filters'):
239 cherrypy.root._cp_filters = []
240 extra_filters = [
241 VirtualPathFilter(webpath),
242 EndTransactionsFilter(),
243 NestedVariablesFilter(),
244 SafeMultipartFilter()
245 ]
246
247 for cp_filter in cherrypy.root._cp_filters[:]:
248 for candidate in extra_filters:
249 if candidate.__class__ is cp_filter.__class__:
250 extra_filters.remove(candidate)
251 break
252 cherrypy.root._cp_filters.extend(extra_filters)
253
254
255
256 decoding_filter = MonkeyDecodingFilter()
257 for index, active_filter in enumerate(
258 cherrypy.filters._filterhooks.get('before_main', [])):
259 if (active_filter.im_class is
260 cherrypy.filters.decodingfilter.DecodingFilter):
261 cherrypy.filters._filterhooks['before_main'].pop(index)
262 if conf('decoding_filter.on', False, path='/'):
263 log.info("Request decoding filter activated.")
264 cherrypy.filters._filterhooks['before_main'].insert(
265 index, decoding_filter.before_main)
266
267
268 webpath = webpath.lstrip('/')
269 if webpath and not webpath.endswith('/'):
270 webpath += '/'
271
272
273 bonjoursetting = conf('tg.bonjour', None)
274 if bonjoursetting or conf('server.environment') == 'development':
275 start_bonjour(bonjoursetting)
276
277
278 if conf('sqlalchemy.dburi'):
279 database.bind_metadata()
280
281
282 extensions = pkg_resources.iter_entry_points('turbogears.extensions')
283 for entrypoint in extensions:
284
285
286
287
288
289
290 try:
291 ext = entrypoint.load()
292 except Exception, e:
293 log.exception("Error loading TurboGears extension plugin '%s': %s",
294 entrypoint, e)
295 continue
296 if hasattr(ext, 'start_extension'):
297 try:
298 ext.start_extension()
299 except Exception, e:
300 log.exception("Error starting TurboGears extension '%s': %s",
301 entrypoint, e)
302
303
304 for item in call_on_startup:
305 item()
306
307
308 if conf('tg.scheduler', False):
309 scheduler._start_scheduler()
310 log.info("Scheduler started")
311
313 """Handles TurboGears tasks when the CherryPy server stops.
314
315 Ends all open database transactions, shuts down all extensions, calls user
316 provided shutdown functions and stops the scheduler.
317
318 """
319
320
321 for hub in hub_registry:
322 hub.end()
323 hub_registry.clear()
324
325 stop_bonjour()
326
327
328 extensions= pkg_resources.iter_entry_points( "turbogears.extensions" )
329 for entrypoint in extensions:
330 try:
331 ext = entrypoint.load()
332 except Exception, e:
333 log.exception("Error loading TurboGears extension plugin '%s': %s",
334 entrypoint, e)
335 continue
336 if hasattr(ext, "shutdown_extension"):
337 try:
338 ext.shutdown_extension()
339 except Exception, e:
340 log.exception(
341 "Error shutting down TurboGears extension '%s': %s",
342 entrypoint, e)
343
344 for item in call_on_shutdown:
345 item()
346
347 if config.get("tg.scheduler", False):
348 scheduler._stop_scheduler()
349 log.info("Scheduler stopped")
350
352 cherrypy.tree.mount(root)
353 if config.get('tg.fancy_exception', False):
354 server.start(server=SimpleWSGIServer())
355 else:
356 server.start()
357
358
360 """A WSGI server that accepts a WSGI application as a parameter."""
361 RequestHandlerClass = CPHTTPRequest
362
364 conf = cherrypy.config.get
365 wsgi_app = wsgiApp
366 if conf('server.environment') == 'development':
367 try:
368 from paste.evalexception.middleware import EvalException
369 except ImportError:
370 pass
371 else:
372 wsgi_app = EvalException(wsgi_app, global_conf={})
373 cherrypy.config.update({'server.throw_errors':True})
374 bind_addr = (conf('server.socket_host'), conf('server.socket_port'))
375 CherryPyWSGIServer.__init__(self, bind_addr, wsgi_app,
376 conf("server.thread_pool"), conf("server.socket_host"),
377 request_queue_size = conf("server.socket_queue_size"))
378
379
380 if startTurboGears not in server.on_start_server_list:
381 server.on_start_server_list.append(startTurboGears)
382
383 if stopTurboGears not in server.on_stop_server_list:
384 server.on_stop_server_list.append(stopTurboGears)
385