Improved request and preemptive caching
* UiPlugins may now disable preemptive caching * Preemptive cache recording will also be disabled when preemptive caching is disabled according to settings (so far only execution on startup was prevented then) * Preemptive cache forces configured view. E.g. if plugin "someplugin" was recorded in the preemptive cache, a custom context now makes sure that only that this view will be cached even if request parameters are hard to define to get routing to work correctly for an alternative UI
This commit is contained in:
parent
0fb1a41fef
commit
7ed71fad28
4 changed files with 87 additions and 19 deletions
|
|
@ -741,6 +741,16 @@ class UiPlugin(OctoPrintPlugin, SortablePlugin):
|
|||
"""
|
||||
return None
|
||||
|
||||
def get_ui_preemptive_caching_enabled(self):
|
||||
"""
|
||||
Allows to control whether the view provided by the plugin should be preemptively
|
||||
cached on server startup (default) or not.
|
||||
|
||||
Returns:
|
||||
bool: Whether to enable preemptive caching or not
|
||||
"""
|
||||
return True
|
||||
|
||||
def get_ui_data_for_preemptive_caching(self):
|
||||
"""
|
||||
Allows defining additional data to be persisted in the preemptive cache configuration, on
|
||||
|
|
|
|||
|
|
@ -752,6 +752,20 @@ class Server(object):
|
|||
entries = reversed(sorted(cache_data[route], key=lambda x: x.get("_count", 0)))
|
||||
for kwargs in entries:
|
||||
plugin = kwargs.get("plugin", None)
|
||||
if plugin:
|
||||
try:
|
||||
plugin_info = pluginManager.get_plugin_info(plugin, require_enabled=True)
|
||||
implementation = plugin_info.implementation
|
||||
if implementation is None or not isinstance(implementation, octoprint.plugin.UiPlugin):
|
||||
self._logger.debug("Plugin {} is not a UiPlugin, preemptive caching makes no sense".format(plugin))
|
||||
continue
|
||||
if not implementation.get_ui_preemptive_caching_enabled():
|
||||
self._logger.debug("Plugin {} has disabled preemptive caching".format(plugin))
|
||||
continue
|
||||
except:
|
||||
self._logger.exception("Error while trying to check if plugin {} has preemptive caching enabled, skipping entry")
|
||||
continue
|
||||
|
||||
additional_request_data = kwargs.get("_additional_request_data", dict())
|
||||
kwargs = dict((k, v) for k, v in kwargs.items() if not k.startswith("_") and not k == "plugin")
|
||||
kwargs.update(additional_request_data)
|
||||
|
|
@ -761,8 +775,9 @@ class Server(object):
|
|||
else:
|
||||
self._logger.info("Preemptively caching {} for {!r}".format(route, kwargs))
|
||||
builder = EnvironBuilder(**kwargs)
|
||||
with preemptive_cache.disable_access_logging():
|
||||
app(builder.get_environ(), lambda *a, **kw: None)
|
||||
with preemptive_cache.cache_environment(dict(plugin=plugin if plugin is not None else "_default")):
|
||||
with preemptive_cache.disable_access_logging():
|
||||
app(builder.get_environ(), lambda *a, **kw: None)
|
||||
except:
|
||||
self._logger.exception("Error while trying to preemptively cache {} for {!r}".format(route, kwargs))
|
||||
|
||||
|
|
|
|||
|
|
@ -383,11 +383,14 @@ class PreemptiveCache(object):
|
|||
|
||||
def __init__(self, cachefile):
|
||||
self.cachefile = cachefile
|
||||
self.environment = None
|
||||
|
||||
self._lock = threading.RLock()
|
||||
self._logger = logging.getLogger(__name__ + "." + self.__class__.__name__)
|
||||
self._log_access = True
|
||||
|
||||
self._lock = threading.RLock()
|
||||
self._environment_lock = threading.RLock()
|
||||
|
||||
def record(self, data, unless=None):
|
||||
if callable(unless) and unless():
|
||||
return
|
||||
|
|
@ -407,6 +410,13 @@ class PreemptiveCache(object):
|
|||
yield
|
||||
self._log_access = True
|
||||
|
||||
@contextlib.contextmanager
|
||||
def cache_environment(self, environment):
|
||||
with self._environment_lock:
|
||||
self.environment = environment
|
||||
yield
|
||||
self.environment = None
|
||||
|
||||
def clean_all_data(self, cleanup_function):
|
||||
assert callable(cleanup_function)
|
||||
|
||||
|
|
@ -585,13 +595,13 @@ def conditional(condition, met):
|
|||
|
||||
def check_etag(etag):
|
||||
return flask.request.method in ("GET", "HEAD") and \
|
||||
flask.request.if_none_match and \
|
||||
flask.request.if_none_match is not None and \
|
||||
etag in flask.request.if_none_match
|
||||
|
||||
|
||||
def check_lastmodified(lastmodified):
|
||||
return flask.request.method in ("GET", "HEAD") and \
|
||||
flask.request.if_modified_since and \
|
||||
flask.request.if_modified_since is not None and \
|
||||
lastmodified >= flask.request.if_modified_since
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ _valid_div_re = re.compile("[a-zA-Z_-]+")
|
|||
def index():
|
||||
global _templates, _plugin_names, _plugin_vars
|
||||
|
||||
preemptive_cache_enabled = settings().getBoolean(["devel", "cache", "preemptive"])
|
||||
|
||||
# helper to check if wizards are active
|
||||
def wizard_active(templates):
|
||||
return templates is not None and bool(templates["wizard"]["order"])
|
||||
|
|
@ -116,11 +118,13 @@ def index():
|
|||
files = collect_files()
|
||||
lastmodified = compute_lastmodified(files)
|
||||
lastmodified_ok = util.flask.check_lastmodified(lastmodified)
|
||||
etag_ok = util.flask.check_etag(compute_etag(files, lastmodified))
|
||||
etag_ok = util.flask.check_etag(compute_etag(files=files,
|
||||
lastmodified=lastmodified,
|
||||
additional=cache_key()))
|
||||
return lastmodified_ok and etag_ok
|
||||
|
||||
def validate_cache(cached):
|
||||
etag_different = compute_etag() != cached.get_etag()[0]
|
||||
etag_different = compute_etag(additional=cache_key()) != cached.get_etag()[0]
|
||||
return force_refresh or etag_different
|
||||
|
||||
def collect_files():
|
||||
|
|
@ -202,8 +206,7 @@ def index():
|
|||
decorated_view = util.flask.conditional(check_etag_and_lastmodified, NOT_MODIFIED)(decorated_view)
|
||||
return decorated_view
|
||||
|
||||
ui_plugins = pluginManager.get_implementations(octoprint.plugin.UiPlugin, sorting_context="UiPlugin.on_ui_render")
|
||||
for plugin in ui_plugins:
|
||||
def plugin_view(plugin):
|
||||
if plugin.will_handle_ui(request):
|
||||
# plugin claims responsibility, let it render the UI
|
||||
cached = get_cached_view(plugin._identifier,
|
||||
|
|
@ -214,17 +217,18 @@ def index():
|
|||
custom_etag=plugin.get_ui_custom_etag,
|
||||
custom_lastmodified=plugin.get_ui_custom_lastmodified)
|
||||
|
||||
preemptively_cached = get_preemptively_cached_view(plugin._identifier,
|
||||
cached,
|
||||
plugin.get_ui_data_for_preemptive_caching,
|
||||
plugin.get_ui_additional_request_data_for_preemptive_caching,
|
||||
plugin.get_ui_additional_unless)
|
||||
if preemptive_cache_enabled and plugin.get_ui_preemptive_caching_enabled():
|
||||
view = get_preemptively_cached_view(plugin._identifier,
|
||||
cached,
|
||||
plugin.get_ui_data_for_preemptive_caching,
|
||||
plugin.get_ui_additional_request_data_for_preemptive_caching,
|
||||
plugin.get_ui_additional_unless)
|
||||
else:
|
||||
view = cached
|
||||
|
||||
response = preemptively_cached(now, request, render_kwargs)
|
||||
if response is not None:
|
||||
break
|
||||
return view(now, request, render_kwargs)
|
||||
|
||||
else:
|
||||
def default_view():
|
||||
wizard = wizard_active(_templates)
|
||||
enable_accesscontrol = userManager.enabled
|
||||
accesscontrol_active = enable_accesscontrol and userManager.hasBeenCustomized()
|
||||
|
|
@ -254,7 +258,36 @@ def index():
|
|||
cached,
|
||||
dict(),
|
||||
dict())
|
||||
response = preemptively_cached()
|
||||
return preemptively_cached()
|
||||
|
||||
forced_view = None
|
||||
preemptive_cache_environment = preemptiveCache.environment
|
||||
if preemptive_cache_environment is not None and isinstance(preemptive_cache_environment, dict):
|
||||
forced_view = preemptive_cache_environment.get("plugin", "_default")
|
||||
|
||||
if forced_view:
|
||||
# we have view forced by the preemptive cache
|
||||
_logger.debug("Forcing rendering of view {}".format(forced_view))
|
||||
response = None
|
||||
if forced_view != "_default":
|
||||
plugin = pluginManager.get_plugin_info(forced_view, require_enabled=True)
|
||||
if plugin is not None and isinstance(plugin.implementation, octoprint.plugin.UiPlugin):
|
||||
response = plugin_view(plugin.implementation)
|
||||
|
||||
if response is None:
|
||||
return default_view()
|
||||
|
||||
else:
|
||||
# select view from plugins and fall back on default view if no plugin will handle it
|
||||
ui_plugins = pluginManager.get_implementations(octoprint.plugin.UiPlugin, sorting_context="UiPlugin.on_ui_render")
|
||||
for plugin in ui_plugins:
|
||||
identifier = plugin._identifier
|
||||
if plugin.will_handle_ui(request):
|
||||
response = plugin_view(plugin)
|
||||
if response is not None:
|
||||
break
|
||||
else:
|
||||
response = default_view()
|
||||
|
||||
return response
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue