Mercurial > piecrust2
comparison piecrust/serving/server.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 | f2b875ecc940 |
children | cc6f3dbe3048 |
comparison
equal
deleted
inserted
replaced
551:f2b875ecc940 | 552:9612cfc6455a |
---|---|
108 wsgi_wrapper = WsgiServerWrapper(self) | 108 wsgi_wrapper = WsgiServerWrapper(self) |
109 return wsgi_wrapper | 109 return wsgi_wrapper |
110 | 110 |
111 def _run_request(self, environ, start_response): | 111 def _run_request(self, environ, start_response): |
112 try: | 112 try: |
113 return self._try_run_request(environ, start_response) | 113 response = self._try_run_request(environ) |
114 if isinstance(response, tuple): | |
115 response, close_func = response | |
116 return ClosingIterator(response(environ, start_response), | |
117 [close_func]) | |
118 else: | |
119 return response(environ, start_response) | |
114 except Exception as ex: | 120 except Exception as ex: |
115 if self.debug: | 121 if self.debug: |
116 raise | 122 raise |
117 return self._handle_error(ex, environ, start_response) | 123 return self._handle_error(ex, environ, start_response) |
118 | 124 |
119 def _try_run_request(self, environ, start_response): | 125 def _try_run_request(self, environ): |
120 request = Request(environ) | 126 request = Request(environ) |
121 | 127 |
122 # We don't support anything else than GET requests since we're | 128 # We don't support anything else than GET requests since we're |
123 # previewing something that will be static later. | 129 # previewing something that will be static later. |
124 if self.static_preview and request.method != 'GET': | 130 if self.static_preview and request.method != 'GET': |
127 raise MethodNotAllowed() | 133 raise MethodNotAllowed() |
128 | 134 |
129 # Handle special requests right away. | 135 # Handle special requests right away. |
130 response = self._try_special_request(environ, request) | 136 response = self._try_special_request(environ, request) |
131 if response is not None: | 137 if response is not None: |
132 return response(environ, start_response) | 138 return response |
133 | 139 |
134 # Also handle requests to a pipeline-built asset right away. | 140 # Also handle requests to a pipeline-built asset right away. |
135 response = self._try_serve_asset(environ, request) | 141 response = self._try_serve_asset(environ, request) |
136 if response is not None: | 142 if response is not None: |
137 return response(environ, start_response) | 143 return response |
138 | 144 |
139 # Create the app for this request. | 145 # Create the app for this request. |
140 app = PieCrust(root_dir=self.root_dir, debug=self.debug) | 146 app = PieCrust(root_dir=self.root_dir, debug=self.debug) |
141 if self.sub_cache_dir: | 147 if self.sub_cache_dir: |
142 app._useSubCacheDir(self.sub_cache_dir) | 148 app._useSubCacheDir(self.sub_cache_dir) |
151 app.env.base_asset_url_format = '/_asset/%path%' | 157 app.env.base_asset_url_format = '/_asset/%path%' |
152 | 158 |
153 # Let's see if it can be a page asset. | 159 # Let's see if it can be a page asset. |
154 response = self._try_serve_page_asset(app, environ, request) | 160 response = self._try_serve_page_asset(app, environ, request) |
155 if response is not None: | 161 if response is not None: |
156 return response(environ, start_response) | 162 return response |
157 | 163 |
158 # Nope. Let's see if it's an actual page. | 164 # Nope. Let's see if it's an actual page. |
159 try: | 165 try: |
160 response = self._try_serve_page(app, environ, request) | 166 response = self._try_serve_page(app, environ, request) |
161 return response(environ, start_response) | 167 return response |
162 except (RouteNotFoundError, SourceNotFoundError) as ex: | 168 except (RouteNotFoundError, SourceNotFoundError) as ex: |
163 raise NotFound() from ex | 169 raise NotFound() from ex |
164 except HTTPException: | 170 except HTTPException: |
165 raise | 171 raise |
166 except Exception as ex: | 172 except Exception as ex: |
185 pass | 191 pass |
186 | 192 |
187 debug_mount = '/__piecrust_debug/' | 193 debug_mount = '/__piecrust_debug/' |
188 if request.path.startswith(debug_mount): | 194 if request.path.startswith(debug_mount): |
189 rel_req_path = request.path[len(debug_mount):] | 195 rel_req_path = request.path[len(debug_mount):] |
196 if rel_req_path == 'werkzeug_shutdown': | |
197 shutdown_func = environ.get('werkzeug.server.shutdown') | |
198 if shutdown_func is None: | |
199 raise RuntimeError('Not running with the Werkzeug Server') | |
200 shutdown_func() | |
201 return Response("Server shutting down...") | |
202 | |
190 if rel_req_path == 'pipeline_status': | 203 if rel_req_path == 'pipeline_status': |
191 from piecrust.serving.procloop import ( | 204 from piecrust.serving.procloop import ( |
192 PipelineStatusServerSideEventProducer) | 205 PipelineStatusServerSentEventProducer) |
193 provider = PipelineStatusServerSideEventProducer( | 206 provider = PipelineStatusServerSentEventProducer( |
194 self._proc_loop.status_queue) | 207 self._proc_loop) |
195 it = ClosingIterator(provider.run(), [provider.close]) | 208 it = provider.run() |
196 response = Response(it) | 209 response = Response(it, mimetype='text/event-stream') |
197 response.headers['Cache-Control'] = 'no-cache' | 210 response.headers['Cache-Control'] = 'no-cache' |
198 if 'text/event-stream' in request.accept_mimetypes: | 211 response.headers['Last-Event-ID'] = \ |
199 response.mimetype = 'text/event-stream' | 212 self._proc_loop.last_status_id |
200 response.direct_passthrough = True | 213 return response, provider.close |
201 response.implicit_sequence_conversion = False | |
202 return response | |
203 | 214 |
204 return None | 215 return None |
205 | 216 |
206 def _try_serve_asset(self, environ, request): | 217 def _try_serve_asset(self, environ, request): |
207 rel_req_path = request.path.lstrip('/').replace('/', os.sep) | 218 rel_req_path = request.path.lstrip('/').replace('/', os.sep) |