Merge branch 'fix/intermediaryStartupPage' into devel
Conflicts: src/octoprint/server/__init__.py
This commit is contained in:
commit
ed6834ac0b
3 changed files with 326 additions and 2 deletions
|
|
@ -23,6 +23,7 @@ import logging
|
|||
import logging.config
|
||||
import atexit
|
||||
import signal
|
||||
import base64
|
||||
|
||||
SUCCESS = {}
|
||||
NO_CONTENT = ("", 204)
|
||||
|
|
@ -126,6 +127,8 @@ class Server():
|
|||
|
||||
self._template_searchpaths = []
|
||||
|
||||
self._intermediary_server = None
|
||||
|
||||
def run(self):
|
||||
if not self._allow_root:
|
||||
self._check_for_root()
|
||||
|
|
@ -175,7 +178,10 @@ class Server():
|
|||
logging.getLogger("SERIAL").setLevel(logging.DEBUG)
|
||||
logging.getLogger("SERIAL").debug("Enabling serial logging")
|
||||
|
||||
# load plugins
|
||||
# start the intermediary server
|
||||
self._start_intermediary_server()
|
||||
|
||||
# then initialize the plugin manager
|
||||
pluginManager.reload_plugins(startup=True, initialize_implementations=False)
|
||||
|
||||
printerProfileManager = PrinterProfileManager()
|
||||
|
|
@ -372,7 +378,12 @@ class Server():
|
|||
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(self._settings.getBaseFolder("generated"), "webassets")))
|
||||
(r"/static/webassets/(.*)", util.tornado.LargeResponseHandler, dict(path=os.path.join(self._settings.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:
|
||||
|
|
@ -425,6 +436,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=self._settings.getInt(["server", "maxSize"]))
|
||||
self._server.listen(self._port, address=self._host)
|
||||
|
||||
|
|
@ -980,6 +993,90 @@ class Server():
|
|||
assets.register("css_app", css_app_bundle)
|
||||
assets.register("less_app", all_less_bundle)
|
||||
|
||||
def _start_intermediary_server(self):
|
||||
import BaseHTTPServer
|
||||
import SimpleHTTPServer
|
||||
import threading
|
||||
|
||||
host = self._host
|
||||
port = self._port
|
||||
if host is None:
|
||||
host = self._settings.get(["server", "host"])
|
||||
if port is None:
|
||||
port = self._settings.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):
|
||||
|
|
|
|||
|
|
@ -931,6 +931,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
|
||||
|
||||
|
||||
|
|
|
|||
214
src/octoprint/static/intermediary.html
Normal file
214
src/octoprint/static/intermediary.html
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>OctoPrint is still starting</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #333333;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
#startup-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 10000;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.wrapper .outer {
|
||||
display: table;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.wrapper .outer .inner {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.wrapper .outer .inner .content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.green {
|
||||
color: #169300;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: #990000;
|
||||
}
|
||||
|
||||
.pulsate3 {
|
||||
-webkit-animation: pulsate 3s ease-out;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.pulsate1 {
|
||||
-webkit-animation: pulsate 1s ease-out;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@-webkit-keyframes pulsate {
|
||||
0% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
opacity: 1.0;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
if (!String.prototype.endsWith) {
|
||||
String.prototype.endsWith = function(searchString, position) {
|
||||
var subjectString = this.toString();
|
||||
if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) {
|
||||
position = subjectString.length;
|
||||
}
|
||||
position -= searchString.length;
|
||||
var lastIndex = subjectString.indexOf(searchString, position);
|
||||
return lastIndex !== -1 && lastIndex === position;
|
||||
};
|
||||
}
|
||||
|
||||
function ping(url, timeout, callback) {
|
||||
var img = new Image();
|
||||
var calledBack = false;
|
||||
|
||||
var urlToUse = url;
|
||||
var postfix = "_=" + Date.now();
|
||||
if (url.indexOf("?") > -1) {
|
||||
urlToUse += "&" + postfix;
|
||||
} else {
|
||||
urlToUse += "?" + postfix;
|
||||
}
|
||||
|
||||
img.onload = function() {
|
||||
callback("load");
|
||||
calledBack = true;
|
||||
};
|
||||
img.onerror = function() {
|
||||
if (!calledBack) {
|
||||
callback("error");
|
||||
calledBack = true;
|
||||
}
|
||||
};
|
||||
img.src = urlToUse;
|
||||
setTimeout(function() {
|
||||
if (!calledBack) {
|
||||
callback("timeout");
|
||||
calledBack = true;
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
var intervals = [1, 1, 2, 3, 5, 8];
|
||||
var timeout = 1500;
|
||||
|
||||
var baseUrl = window.location.href;
|
||||
if (baseUrl.indexOf("/static") > -1) {
|
||||
baseUrl = baseUrl.substring(0, baseUrl.indexOf("/static")) + "/";
|
||||
}
|
||||
var serverOnlineUrl = baseUrl + "online.gif";
|
||||
var backendOnlineUrl = baseUrl + "intermediary.gif";
|
||||
|
||||
var serverTimeout;
|
||||
|
||||
var message = window.document.getElementById("message");
|
||||
|
||||
var serverIsOnline = false;
|
||||
var serverOnlineCallback = function(result) {
|
||||
if (result == "load") {
|
||||
// our online.gif loaded, so the server is up, let's reload
|
||||
serverIsOnline = true;
|
||||
message.className = "pulsate1 green";
|
||||
message.innerText = "OctoPrint server online, reloading page...";
|
||||
window.location = baseUrl;
|
||||
} else {
|
||||
// online.gif still not available, let's look at
|
||||
var interval = 15;
|
||||
if (intervals.length) {
|
||||
interval = intervals.shift();
|
||||
}
|
||||
serverTimeout = setTimeout(function() {
|
||||
console.log("Pinging " + serverOnlineUrl);
|
||||
ping(serverOnlineUrl, timeout, serverOnlineCallback);
|
||||
}, interval * 1000)
|
||||
}
|
||||
};
|
||||
|
||||
var backendOfflineCounter = 0;
|
||||
var backendOnlineCallback = function(result) {
|
||||
if (serverIsOnline) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == "load") {
|
||||
setTimeout(function() {
|
||||
ping(backendOnlineUrl, timeout, backendOnlineCallback);
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
backendOfflineCounter++;
|
||||
if (backendOfflineCounter > 5) {
|
||||
if (serverTimeout) {
|
||||
window.clearTimeout(serverTimeout);
|
||||
}
|
||||
|
||||
message.className = "red";
|
||||
message.innerHTML = "Looks like something went wrong during startup, the server is gone again. You should check <code>octoprint.log</code>.";
|
||||
} else {
|
||||
setTimeout(function() {
|
||||
ping(backendOnlineUrl, timeout, backendOnlineCallback);
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
ping(backendOnlineUrl, timeout, backendOnlineCallback);
|
||||
ping(serverOnlineUrl, timeout, serverOnlineCallback);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="startup-overlay">
|
||||
<div class="background"></div>
|
||||
<div class="wrapper">
|
||||
<div class="outer">
|
||||
<div class="inner">
|
||||
<div class="content">
|
||||
<h1 id="message" class="pulsate3">OctoPrint is still starting up, please wait...</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue