1 """Things to do when the TurboGears server is started."""
2
3 __all__ = ['call_on_startup', 'call_on_shutdown',
4 'reloader_thread', 'webpath',
5 'start_bonjour', 'stop_bonjour', 'start_server',
6 'start_turbogears', 'stop_turbogears']
7
8 import atexit
9 import logging
10 import os
11 import signal
12 import sys
13
14 from os.path import abspath, exists
15
16 import pkg_resources
17 import cherrypy
18
19 pkg_resources.require("TurboGears")
20
21 from turbogears import config, database, scheduler, view
22 from turbogears.visit.api import VisitTool
23 from turbogears.identity.exceptions import IdentityConfigurationException
24 from turbogears.identity.base import verify_identity_status
25 from turbogears.database import hub_registry
26 from turbogears.dispatchers import VirtualPathDispatcher
27 from turbogears.hooks import NestedVariablesHook
28
29
30
31
32 log = logging.getLogger("turbogears.startup")
33
34 dns_sd_pid = None
35 call_on_startup = []
36 call_on_shutdown = []
37 webpath = ''
38 started = False
39
40
41
42
44 """Register the TurboGears server with Apple's Bonjour framework.
45
46 Currently only Unix-like systems are supported where either the 'avahi'
47 daemon (Linux etc.) is available or the 'dns-sd' program (Mac OS X).
48
49 """
50 global dns_sd_pid
51 if dns_sd_pid:
52 return
53 if sys.platform in ('win32', 'os2'):
54 dns_sd_pid = -1
55 return
56
57 if not package:
58 app = cherrypy.tree.apps.get('')
59 if not app:
60 return
61 package = app.root.__module__
62 package = package.split('.', 1)[0]
63
64 host = config.get('server.socket_host', '0.0.0.0')
65 port = str(config.get('server.socket_port'))
66 env = config.get('environment') or 'development'
67 name = '%s:%s' % (package, env)
68 typ = '_http._tcp'
69 cmds = [
70 ('/usr/bin/avahi-publish-service', ['-H', host, name, typ, port]),
71 ('/usr/bin/dns-sd', ['-R', name, typ, '.' + host, port, 'path=/'])]
72
73 for cmd, args in cmds:
74
75
76
77
78 if exists(cmd):
79 dns_sd_pid = os.spawnv(os.P_NOWAIT, cmd, [cmd] + args)
80 atexit.register(stop_bonjour)
81 break
82 else:
83 dns_sd_pid = -1
84
85
87 """Stop the Bonjour publishing daemon if it is running."""
88 if not dns_sd_pid or dns_sd_pid < 0:
89 return
90 try:
91 os.kill(dns_sd_pid, signal.SIGTERM)
92 except OSError:
93 pass
94
95
97 """Configure serving static content used by TurboGears."""
98 config.update({'/tg_static': {
99 'tools.staticdir.on': True,
100 'tools.staticdir.dir': abspath(
101 pkg_resources.resource_filename(__name__, 'static'))}})
102 config.update({'/tg_js': {
103 'tools.staticdir.on': True,
104 'tools.staticdir.dir': abspath(
105 pkg_resources.resource_filename(__name__, 'static/js'))}})
106
107
109 """Configure the encoding and virtual path for the root controller."""
110 global webpath
111 encoding = config.get('genshi.default_encoding',
112 config.get('kid.encoding', 'utf-8'))
113 config.update({'/': {
114 'tools.decode.on': True,
115 'tools.decode.encoding': encoding,
116 'tools.encode.on': False,
117 'tools.encode.encoding': encoding,
118 'tools.encode.text_only': False,
119 'tools.encode.add_charset': False}})
120 webpath = config.get('server.webpath') or ''
121 if webpath:
122
123 webpath = webpath.strip('/')
124 if webpath:
125 webpath = '/' + webpath
126 config.update({'server.webpath': webpath})
127
128 if webpath:
129 config.update({'/': {'request.dispatch': VirtualPathDispatcher(
130 config.get('request.dispatch'), webpath)}})
131 if config.get('tg.fancy_exception', False):
132 from paste import evalexception
133 config.update({'request.throw_errors': True, '/': {
134 'wsgi.pipeline': [('evalexc', evalexception.EvalException)],
135 'wsgi.evalexc.global_conf': {},
136 'wsgi.evalexc.xmlhttp_key': "_xml"}})
137
138
140 """Handles TurboGears tasks when the CherryPy server starts.
141
142 This performs the following initialization tasks (in given order):
143
144 * Loads the template engines and the base templates.
145 * Turns off CherryPy access and error logging to screen since
146 it disrupts with our own logging configuration. You can use
147 the qualnames cherrypy.access and cherrypy.error for these messages.
148 * Adds a static tool for TurboGears's static files (URL '/tg_static').
149 * Adds a static tool for TurboGears's JavaScript files (URL '/tg_js').
150 * Adds a tool for decoding request parameters to Unicode.
151 * Adds a virtual path dispatcher if enabled in the configuration.
152 * Adds CherryPy tools and hooks for visit tracking, identity,
153 database and decoding parameters into nested dictionaries.
154 * Registers the server with the Bonjour framework, if available.
155 * Calls 'turbogears.database.bind_metadata' when using SQLAlchemy.
156 * Loads all turbogears.extensions entry points and calls their
157 'start_extension' method.
158 * Calls the callables registered in 'turbogears.call_on_startup'.
159 * Starts the TurboGears scheduler if enabled in the configuration.
160
161 """
162 global started
163 if started:
164 log.info("TurboGears has already been started.")
165 return
166 log.info("Starting TurboGears...")
167
168
169 log.info("Loading template engines...")
170 view.load_engines()
171 view.loadBaseTemplates()
172
173
174 log.info("Adding CherryPy tools, hooks and dispatchers...")
175 config_static()
176 config_root()
177 hooks = cherrypy.request.hooks
178 cherrypy.request.original_hooks = hooks.copy()
179 hooks.attach('before_finalize', verify_identity_status)
180 hooks.attach('on_end_resource', database.EndTransactions)
181
182
183 hooks.attach('before_handler', NestedVariablesHook, priority=64)
184 if config.get('visit.on', False):
185
186
187
188 cherrypy.tools.visit = cherrypy.Tool(
189 'before_handler', VisitTool(), priority=62)
190
191
192 bonjour = config.get('tg.bonjour', None)
193 env = config.get('environment') or 'development'
194 if bonjour or env == 'development':
195 log.info("Starting the Bonjour service...")
196 start_bonjour(bonjour)
197
198
199 if config.get('sqlalchemy.dburi'):
200 log.info("Binding metadata for SQLAlchemy...")
201 database.bind_metadata()
202
203
204 extensions = pkg_resources.iter_entry_points('turbogears.extensions')
205 for entrypoint in extensions:
206
207
208
209
210
211
212 log.info("Starting TurboGears extension %s..." % entrypoint)
213 try:
214 ext = entrypoint.load()
215 except Exception, e:
216 log.exception("Error loading TurboGears extension plugin %s: %s",
217 entrypoint, e)
218 continue
219 if hasattr(ext, 'start_extension'):
220 try:
221 ext.start_extension()
222 except Exception, e:
223 log.exception("Error starting TurboGears extension %s: %s",
224 entrypoint, e)
225 if isinstance(e, IdentityConfigurationException):
226 raise
227
228
229 if call_on_startup:
230 log.info("Running the registered startup functions...")
231 for startup_function in call_on_startup:
232 startup_function()
233
234
235 if config.get('tg.scheduler', False):
236 log.info("Starting the scheduler...")
237 scheduler.start_scheduler()
238
239 started = True
240 log.info("TurboGears has been started.")
241
242
244 """Handles TurboGears tasks when the CherryPy server stops.
245
246 Ends all open database transactions, shuts down all extensions,
247 calls user provided shutdown functions and stops the scheduler.
248
249 """
250 global started
251 if not started:
252 log.info("TurboGears has already been stopped.")
253 return
254 log.info("Stopping TurboGears...")
255
256 if config.get('tg.scheduler', False):
257 log.info("Stopping the scheduler...")
258 scheduler.stop_scheduler()
259
260
261 if call_on_shutdown:
262 log.info("Running the registered shutdown functions...")
263 for shutdown_function in call_on_shutdown:
264 shutdown_function()
265
266
267 extensions = pkg_resources.iter_entry_points('turbogears.extensions')
268 for entrypoint in extensions:
269 log.info("Stopping TurboGears extension %s" % entrypoint)
270 try:
271 ext = entrypoint.load()
272 except Exception, e:
273 log.exception("Error loading TurboGears extension plugin '%s': %s",
274 entrypoint, e)
275 continue
276 if hasattr(ext, 'shutdown_extension'):
277 try:
278 ext.shutdown_extension()
279 except Exception, e:
280 log.exception(
281 "Error shutting down TurboGears extension '%s': %s",
282 entrypoint, e)
283
284
285
286 if hub_registry:
287 log.info("Ending all registered database hubs...")
288 for hub in hub_registry:
289 hub.end()
290 hub_registry.clear()
291
292 log.info("Stopping the Bonjour service...")
293 stop_bonjour()
294
295
296 log.info("Removing additional CherryPy hooks...")
297 try:
298 cherrypy.request.hooks = cherrypy.request.original_hooks
299 except AttributeError:
300 log.debug("CherryPy hooks could not be restored.")
301
302 started = False
303 log.info("TurboGears has been stopped.")
304
305
322
323
324
325
326 cherrypy.engine.subscribe('start', start_turbogears)
327 cherrypy.engine.subscribe('stop', stop_turbogears)
328