diff piecrust/serving.py @ 133:9e4c2e68a129

Optimize server for files that already exist. * Only try to find new assets if no previously existing asset or page could be used. * Tidy up a bit the API for passing and returning bake/process records. * Put the process record in its place.
author Ludovic Chabant <ludovic@chabant.com>
date Tue, 18 Nov 2014 21:32:04 -0800
parents 7f81c84f7ddb
children 50b65c700c96
line wrap: on
line diff
--- a/piecrust/serving.py	Tue Nov 18 21:29:12 2014 -0800
+++ b/piecrust/serving.py	Tue Nov 18 21:32:04 2014 -0800
@@ -16,7 +16,6 @@
 from piecrust.data.filters import (PaginationFilter, HasFilterClause,
         IsFilterClause)
 from piecrust.environment import StandardEnvironment
-from piecrust.page import Page
 from piecrust.processing.base import ProcessorPipeline
 from piecrust.rendering import PageRenderingContext, render_page
 from piecrust.sources.base import PageFactory, MODE_PARSING
@@ -125,15 +124,21 @@
         if response is not None:
             return response(environ, start_response)
 
-        # Nope. Let's hope it's an actual page.
+        # Nope. Let's see if it's an actual page.
+        # We trap any exception that says "there's no such page" so we can
+        # try another thing before bailing out. But we let any exception
+        # that says "something's wrong" through.
+        exc = None
         try:
             response = self._try_serve_page(app, environ, request)
             return response(environ, start_response)
-        except HTTPException as ex:
-            raise
         except (RouteNotFoundError, SourceNotFoundError) as ex:
             logger.exception(ex)
-            raise NotFound()
+            exc = NotFound(str(ex))
+        except NotFound as ex:
+            exc = ex
+        except HTTPException:
+            raise
         except Exception as ex:
             if app.debug:
                 logger.exception(ex)
@@ -142,41 +147,71 @@
             logger.error(msg)
             raise InternalServerError(msg)
 
+        # Nothing worked so far... let's see if there's a new asset.
+        response = self._try_serve_new_asset(app, environ, request)
+        if response is not None:
+            return response(environ, start_response)
+
+        # Nope. Raise the exception we had in store.
+        raise exc
+
     def _try_serve_asset(self, app, environ, request):
-        logger.debug("Searching for asset with path: %s" % request.path)
+        logger.debug("Searching %d entries for asset with path: %s" %
+                (len(self._asset_record.entries), request.path))
         rel_req_path = request.path.lstrip('/').replace('/', os.sep)
-        entry = self._asset_record.previous.findEntry(rel_req_path)
-        do_synchronous_process = True
-        mounts = app.assets_dirs
+        entry = self._asset_record.findEntry(rel_req_path)
         if entry is None:
-            # We don't know any asset that could have created this path,
-            # but we'll see if there's a new asset that could fit.
-            pipeline = ProcessorPipeline(
-                    app, mounts, self._out_dir,
-                    skip_patterns=self._skip_patterns,
-                    force_patterns=self._force_patterns)
-            record = pipeline.run(new_only=True)
-            entry = record.current.findEntry(rel_req_path)
-            if entry is None:
-                return None
-
-            logger.debug("Found new asset: %s" % entry.path)
-            self._asset_record.addEntry(entry)
-            do_synchronous_process = False
+            # We don't know any asset that could have created this path.
+            # It could be a new asset that the user just created, but we'll
+            # check for that later.
+            return None
 
         # Yep, we know about this URL because we processed an asset that
         # maps to it... make sure it's up to date by re-processing it
         # before serving.
+        mounts = app.assets_dirs
         asset_in_path = entry.path
         asset_out_path = os.path.join(self._out_dir, rel_req_path)
 
-        if self.synchronous_asset_pipeline and do_synchronous_process:
+        if self.synchronous_asset_pipeline:
+            logger.debug("Making sure '%s' is up-to-date." % asset_in_path)
             pipeline = ProcessorPipeline(
                     app, mounts, self._out_dir,
                     skip_patterns=self._skip_patterns,
-                    force_patterns=self._force_patterns)
-            pipeline.run(asset_in_path)
+                    force_patterns=self._force_patterns,
+                    num_workers=1)
+            r = pipeline.run(asset_in_path, delete=False, save_record=False,
+                             previous_record=self._asset_record)
+            assert len(r.entries) == 1
+            self._asset_record.replaceEntry(r.entries[0])
+
+        logger.debug("Serving %s" % asset_out_path)
+        wrapper = wrap_file(environ, open(asset_out_path, 'rb'))
+        response = Response(wrapper)
+        _, ext = os.path.splitext(rel_req_path)
+        response.mimetype = self._mimetype_map.get(
+                ext.lstrip('.'), 'text/plain')
+        return response
 
+    def _try_serve_new_asset(self, app, environ, request):
+        logger.debug("Searching for a new asset with path: %s" % request.path)
+        mounts = app.assets_dirs
+        pipeline = ProcessorPipeline(
+                app, mounts, self._out_dir,
+                skip_patterns=self._skip_patterns,
+                force_patterns=self._force_patterns)
+        r = pipeline.run(new_only=True, delete=False, save_record=False,
+                         previous_record=self._asset_record)
+        for e in r.entries:
+            self._asset_record.addEntry(e)
+
+        rel_req_path = request.path.lstrip('/').replace('/', os.sep)
+        entry = self._asset_record.findEntry(rel_req_path)
+        if entry is None:
+            return None
+
+        asset_out_path = os.path.join(self._out_dir, rel_req_path)
+        logger.debug("Found new asset: %s" % entry.path)
         logger.debug("Serving %s" % asset_out_path)
         wrapper = wrap_file(environ, open(asset_out_path, 'rb'))
         response = Response(wrapper)