From ef876cfd353f862926264f0dcd94088808776d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 8 Dec 2015 13:28:12 +0100 Subject: [PATCH] Fire up intermediary server on host and port That way people will not see connection failed messages while the server is still starting up. Served intermediary page als "pings" an image on the backend to detect if a) the backend is still responding at all ("intermediary.gif") and b) whether the server has fully started up ("online.gif"). If the backend stops responding for 5s, a message is output that tells the user that something went really wrong and to please check the log file. Once the server becomes online, the intermediary page reloads/switches to the actual UI. --- src/octoprint/server/__init__.py | 97 ++++++++++- src/octoprint/server/util/tornado.py | 13 ++ src/octoprint/static/intermediary.html | 214 +++++++++++++++++++++++++ 3 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 src/octoprint/static/intermediary.html diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 643e32cc..acbd0345 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -22,6 +22,7 @@ import logging import logging.config import atexit import signal +import base64 SUCCESS = {} NO_CONTENT = ("", 204) @@ -124,6 +125,8 @@ class Server(): self._template_searchpaths = [] + self._intermediary_server = None + def run(self): if not self._allowRoot: self._check_for_root() @@ -172,6 +175,9 @@ class Server(): sys.excepthook = exception_logger self._logger.info("Starting OctoPrint %s" % DISPLAY_VERSION) + # start the intermediary server + self._start_intermediary_server(s) + # then initialize the plugin manager pluginManager = octoprint.plugin.plugin_manager(init=True) @@ -361,7 +367,10 @@ class Server(): # camera snapshot (r"/downloads/camera/current", util.tornado.UrlProxyHandler, dict(url=s.get(["webcam", "snapshot"]), as_attachment=True, access_validation=util.tornado.access_validation_factory(app, loginManager, util.flask.user_validator))), # generated webassets - (r"/static/webassets/(.*)", util.tornado.LargeResponseHandler, dict(path=os.path.join(s.getBaseFolder("generated"), "webassets"))) + (r"/static/webassets/(.*)", util.tornado.LargeResponseHandler, dict(path=os.path.join(s.getBaseFolder("generated"), "webassets"))), + # online indicators - text file with "online" as content and a transparent gif + (r"/online.txt", util.tornado.StaticDataHandler, dict(data="online\n")), + (r"/online.gif", util.tornado.StaticDataHandler, dict(data=bytes(base64.b64decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")), content_type="image/gif")) ] for name, hook in pluginManager.get_hooks("octoprint.server.http.routes").items(): try: @@ -414,6 +423,8 @@ class Server(): self._logger.debug("Adding maximum body size of {size}B for {method} requests to {route})".format(**locals())) max_body_sizes.append((method, route, size)) + self._stop_intermediary_server() + self._server = util.tornado.CustomHTTPServer(self._tornado_app, max_body_sizes=max_body_sizes, default_max_body_size=s.getInt(["server", "maxSize"])) self._server.listen(self._port, address=self._host) @@ -938,6 +949,90 @@ class Server(): assets.register("css_app", css_app_bundle) assets.register("less_app", all_less_bundle) + def _start_intermediary_server(self, s): + import BaseHTTPServer + import SimpleHTTPServer + import threading + + host = self._host + port = self._port + if host is None: + host = s.get(["server", "host"]) + if port is None: + port = s.getInt(["server", "port"]) + + self._logger.debug("Starting intermediary server on {}:{}".format(host, port)) + + class IntermediaryServerHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def __init__(self, rules=None, *args, **kwargs): + if rules is None: + rules = [] + self.rules = rules + SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs) + + def do_GET(self): + request_path = self.path + if "?" in request_path: + request_path = request_path[0:request_path.find("?")] + + for rule in self.rules: + path, data, content_type = rule + if request_path == path: + self.send_response(200) + if content_type: + self.send_header("Content-Type", content_type) + self.end_headers() + self.wfile.write(data) + break + else: + self.send_response(404) + self.wfile.write("Not found") + + base_path = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "static")) + rules = [ + ("/", ["intermediary.html",], "text/html"), + ("/favicon.ico", ["img", "tentacle-20x20.png"], "image/png"), + ("/intermediary.gif", bytes(base64.b64decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")), "image/gif") + ] + + def contents(args): + path = os.path.join(base_path, *args) + if not os.path.isfile(path): + return "" + + with open(path, "rb") as f: + data = f.read() + return data + + def process(rule): + if len(rule) == 2: + path, data = rule + content_type = None + else: + path, data, content_type = rule + + if isinstance(data, (list, tuple)): + data = contents(data) + + return path, data, content_type + + rules = map(process, filter(lambda rule: len(rule) == 2 or len(rule) == 3, rules)) + + self._intermediary_server = BaseHTTPServer.HTTPServer((host, port), lambda *args, **kwargs: IntermediaryServerHandler(rules, *args, **kwargs)) + + thread = threading.Thread(target=self._intermediary_server.serve_forever) + thread.daemon = True + thread.start() + + self._logger.debug("Intermediary server started") + + def _stop_intermediary_server(self): + if self._intermediary_server is None: + return + self._logger.debug("Shutting down intermediary server...") + self._intermediary_server.shutdown() + self._intermediary_server.server_close() + self._logger.debug("Intermediary server shut down") class LifecycleManager(object): def __init__(self, plugin_manager): diff --git a/src/octoprint/server/util/tornado.py b/src/octoprint/server/util/tornado.py index 30753f34..b013b861 100644 --- a/src/octoprint/server/util/tornado.py +++ b/src/octoprint/server/util/tornado.py @@ -927,6 +927,19 @@ class UrlProxyHandler(tornado.web.RequestHandler): return "%s%s" % (self._basename, extension) +class StaticDataHandler(tornado.web.RequestHandler): + def initialize(self, data="", content_type="text/plain"): + self.data = data + self.content_type = content_type + + def get(self, *args, **kwargs): + self.set_status(200) + self.set_header("Content-Type", self.content_type) + self.write(self.data) + self.flush() + self.finish() + + #~~ Factory method for creating Flask access validation wrappers from the Tornado request context diff --git a/src/octoprint/static/intermediary.html b/src/octoprint/static/intermediary.html new file mode 100644 index 00000000..7e28c6ad --- /dev/null +++ b/src/octoprint/static/intermediary.html @@ -0,0 +1,214 @@ + + + OctoPrint is still starting + + + + +
+
+
+
+
+
+

OctoPrint is still starting up, please wait...

+
+
+
+
+
+ +