diff piecrust/serving/middlewares.py @ 553:cc6f3dbe3048

serve: Extract some of the server's functionality into WSGI middlewares.
author Ludovic Chabant <ludovic@chabant.com>
date Sat, 08 Aug 2015 22:01:47 -0700
parents
children 93b656f0af54
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/piecrust/serving/middlewares.py	Sat Aug 08 22:01:47 2015 -0700
@@ -0,0 +1,94 @@
+import os.path
+from werkzeug.wrappers import Request, Response
+from werkzeug.wsgi import ClosingIterator
+from piecrust import RESOURCES_DIR, CACHE_DIR
+from piecrust.serving.util import make_wrapped_file_response
+
+
+class StaticResourcesMiddleware(object):
+    """ WSGI middleware that serves static files from the `resources/server`
+        directory in the PieCrust package.
+    """
+    def __init__(self, app):
+        self.app = app
+
+    def __call__(self, environ, start_response):
+        static_mount = '/__piecrust_static/'
+
+        request = Request(environ)
+        if request.path.startswith(static_mount):
+            rel_req_path = request.path[len(static_mount):]
+            mount = os.path.join(RESOURCES_DIR, 'server')
+            full_path = os.path.join(mount, rel_req_path)
+            try:
+                response = make_wrapped_file_response(
+                        environ, request, full_path)
+                return response(environ, start_response)
+            except OSError:
+                pass
+
+        return self.app(environ, start_response)
+
+
+class PieCrustDebugMiddleware(object):
+    """ WSGI middleware that handles debugging of PieCrust stuff.
+    """
+    def __init__(self, app, root_dir, debug=False,
+                 sub_cache_dir=None, run_sse_check=None):
+        self.app = app
+        self.root_dir = root_dir
+        self.debug = debug
+        self.run_sse_check = run_sse_check
+        self._proc_loop = None
+        self._out_dir = os.path.join(root_dir, CACHE_DIR, 'server')
+        if sub_cache_dir:
+            self._out_dir = os.path.join(sub_cache_dir, 'server')
+        self._handlers = {
+                'werkzeug_shutdown': self._shutdownWerkzeug,
+                'pipeline_status': self._startSSEProvider}
+
+        if not self.run_sse_check or self.run_sse_check():
+            # When using a server with code reloading, some implementations
+            # use process forking and we end up going here twice. We only want
+            # to start the pipeline loop in the inner process most of the
+            # time so we let the implementation tell us if this is OK.
+            from piecrust.serving.procloop import ProcessingLoop
+            self._proc_loop = ProcessingLoop(root_dir, self._out_dir,
+                                             sub_cache_dir=sub_cache_dir,
+                                             debug=debug)
+            self._proc_loop.start()
+
+    def __call__(self, environ, start_response):
+        debug_mount = '/__piecrust_debug/'
+
+        request = Request(environ)
+        if request.path.startswith(debug_mount):
+            rel_req_path = request.path[len(debug_mount):]
+            handler = self._handlers.get(rel_req_path)
+            if handler is not None:
+                return handler(request, start_response)
+
+        return self.app(environ, start_response)
+
+    def _shutdownWerkzeug(self, request, start_response):
+        shutdown_func = request.environ.get('werkzeug.server.shutdown')
+        if shutdown_func is None:
+            raise RuntimeError('Not running with the Werkzeug Server')
+        shutdown_func()
+        response = Response("Server shutting down...")
+        return response(request.environ, start_response)
+
+    def _startSSEProvider(self, request, start_response):
+        from piecrust.serving.procloop import (
+                PipelineStatusServerSentEventProducer)
+        provider = PipelineStatusServerSentEventProducer(
+                self._proc_loop)
+        it = provider.run()
+        response = Response(it, mimetype='text/event-stream')
+        response.headers['Cache-Control'] = 'no-cache'
+        response.headers['Last-Event-ID'] = \
+            self._proc_loop.last_status_id
+        return ClosingIterator(
+                response(request.environ, start_response),
+                [provider.close])
+