comparison piecrust/serving/wrappers.py @ 552:9612cfc6455a

serve: Rewrite of the Server-Sent Event code for build notifications. At the moment the server monitors the asset directories, and notifies the browser when an asset has changed and has been re-processed. * Fix issues around long-running requests/threads which mess up the ability to shutdown the server correctly with `CTRL-C` (see comments in code). * Move the notification queue to each SSE producer, to support having multiple pages open in a browser. * Add JS/CSS for showing quick notifications about re-processed assets. * Add support for hot-reloading CSS and pictures that have been re-processed.
author Ludovic Chabant <ludovic@chabant.com>
date Sat, 08 Aug 2015 16:12:04 -0700
parents fa3ee8a8ee2d
children cc6f3dbe3048
comparison
equal deleted inserted replaced
551:f2b875ecc940 552:9612cfc6455a
1 import os 1 import os
2 import logging
3 import threading
4 import urllib.request
2 from piecrust.serving.server import Server 5 from piecrust.serving.server import Server
3 from piecrust.serving.procloop import _sse_abort 6
7
8 logger = logging.getLogger(__name__)
4 9
5 10
6 def run_werkzeug_server(root_dir, host, port, 11 def run_werkzeug_server(root_dir, host, port,
7 debug_piecrust=False, sub_cache_dir=None, 12 debug_piecrust=False, sub_cache_dir=None,
8 use_debugger=False, use_reloader=False): 13 use_debugger=False, use_reloader=False):
20 25
21 app = _get_piecrust_server(root_dir, 26 app = _get_piecrust_server(root_dir,
22 debug=debug_piecrust, 27 debug=debug_piecrust,
23 sub_cache_dir=sub_cache_dir, 28 sub_cache_dir=sub_cache_dir,
24 run_sse_check=_run_sse_check) 29 run_sse_check=_run_sse_check)
25 try: 30
31 # We need to run Werkzeug in a background thread because we may have some
32 # SSE responses running. In theory we should be using a proper async
33 # server for this kind of stuff, but I'd rather avoid additional
34 # dependencies on stuff that's not necessarily super portable.
35 # Anyway we run the server in multi-threading mode, but the request
36 # threads are not set to `daemon` mode (and there's no way to set that
37 # flag without re-implementing `run_simple` apparently). So instead we
38 # run the server in a background thread so we keep the main thread to
39 # ourselves here, which means we can trap `KeyboardInterrupt`, and set
40 # a global flag that will kill all the long-running SSE threads and make
41 # this whole thing exit cleanly and properly (hopefully).
42 def _inner():
26 run_simple(host, port, app, 43 run_simple(host, port, app,
27 threaded=True, 44 threaded=True,
28 use_debugger=use_debugger, 45 use_debugger=use_debugger,
29 use_reloader=use_reloader) 46 use_reloader=use_reloader)
47
48 t = threading.Thread(name='WerkzeugServer', target=_inner)
49 t.start()
50 try:
51 while t.is_alive():
52 t.join(0.5)
53 except KeyboardInterrupt:
54 shutdown_url = 'http://%s:%s/__piecrust_debug/werkzeug_shutdown' % (
55 host, port)
56 logger.info("")
57 logger.info("Shutting down server...")
58 urllib.request.urlopen(shutdown_url)
30 finally: 59 finally:
31 _sse_abort.set() 60 logger.debug("Terminating push notifications...")
61 from piecrust.serving import procloop
62 procloop.server_shutdown = True
32 63
33 64
34 def run_gunicorn_server(root_dir, 65 def run_gunicorn_server(root_dir,
35 debug_piecrust=False, sub_cache_dir=None, 66 debug_piecrust=False, sub_cache_dir=None,
36 gunicorn_options=None): 67 gunicorn_options=None):