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 @@ + +
+