Merge branch 'fix/intermediaryStartupPage' into devel

Conflicts:
	src/octoprint/server/__init__.py
This commit is contained in:
Gina Häußge 2015-12-08 13:35:23 +01:00
commit ed6834ac0b
3 changed files with 326 additions and 2 deletions

View file

@ -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):

View file

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

View 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>