diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 57e9cfa9..16e48321 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -153,7 +153,13 @@ class Server(): except AttributeError, e: logger.exception("Could not instantiate user manager %s, will run with accessControl disabled!" % userManagerName) - app.wsgi_app = util.ReverseProxied(app.wsgi_app) + app.wsgi_app = util.ReverseProxied( + app.wsgi_app, + settings().get(["server", "reverseProxy", "prefixHeader"]), + settings().get(["server", "reverseProxy", "schemeHeader"]), + settings().get(["server", "reverseProxy", "prefixFallback"]), + settings().get(["server", "reverseProxy", "prefixScheme"]) + ) app.secret_key = "k3PuVYgtxNm8DXKKTw2nWmFQQun9qceV" loginManager = LoginManager() diff --git a/src/octoprint/server/util/__init__.py b/src/octoprint/server/util/__init__.py index 66e67301..4e964e3f 100644 --- a/src/octoprint/server/util/__init__.py +++ b/src/octoprint/server/util/__init__.py @@ -72,29 +72,43 @@ class ReverseProxied(object): :param app: the WSGI application :param header_script_name: the HTTP header in the wsgi environment from which to determine the prefix :param header_scheme: the HTTP header in the wsgi environment from which to determine the scheme + :param base_url: the prefix to use as fallback if headers are not set """ - def __init__(self, app, header_script_name="HTTP_X_SCRIPT_NAME", header_scheme="HTTP_X_SCHEME"): + def __init__(self, app, header_prefix="x-script-name", header_scheme="x-scheme", base_url="", scheme=""): self.app = app - self._header_script_name = header_script_name - self._header_scheme = header_scheme + + # headers for prefix & scheme, converted to conform to WSGI format + to_wsgi_format = lambda header: "HTTP_" + header.upper().replace("-", "_") + self._header_prefix = to_wsgi_format(header_prefix) + self._header_scheme = to_wsgi_format(header_scheme) + + # fallback prefix & scheme from config + self._fallback_prefix = base_url + self._fallback_scheme = scheme def __call__(self, environ, start_response): - script_name = environ.get(self._header_script_name, '') - if not script_name: - script_name = settings().get(["server", "baseUrl"]) + # determine prefix + prefix = environ.get(self._header_prefix, "") + if not prefix: + prefix = self._fallback_prefix - if script_name: - environ['SCRIPT_NAME'] = script_name - path_info = environ['PATH_INFO'] - if path_info.startswith(script_name): - environ['PATH_INFO'] = path_info[len(script_name):] + # rewrite SCRIPT_NAME and if necessary also PATH_INFO based on prefix + if prefix: + environ["SCRIPT_NAME"] = prefix + path_info = environ["PATH_INFO"] + if path_info.startswith(prefix): + environ["PATH_INFO"] = path_info[len(prefix):] - scheme = environ.get(self._header_scheme, '') + # determine scheme + scheme = environ.get(self._header_scheme, "") if not scheme: - scheme = settings().get(["server", "scheme"]) + scheme = self._fallback_scheme + # rewrite wsgi.url_scheme based on scheme if scheme: - environ['wsgi.url_scheme'] = scheme + environ["wsgi.url_scheme"] = scheme + + # call wrapped app with rewritten environment return self.app(environ, start_response) diff --git a/src/octoprint/settings.py b/src/octoprint/settings.py index a31b0780..b607f97e 100644 --- a/src/octoprint/settings.py +++ b/src/octoprint/settings.py @@ -41,8 +41,12 @@ default_settings = { "host": "0.0.0.0", "port": 5000, "firstRun": True, - "baseUrl": "", - "scheme": "", + "reverseProxy": { + "prefixHeader": "X-Script-Name", + "schemeHeader": "X-Scheme", + "prefixFallback": "", + "schemeFallback": "" + }, "uploads": { "maxSize": 1 * 1024 * 1024 * 1024, # 1GB "nameSuffix": ".name", @@ -198,7 +202,7 @@ class Settings(object): if os.path.exists(self._configfile) and os.path.isfile(self._configfile): with open(self._configfile, "r") as f: self._config = yaml.safe_load(f) - # chamged from else to handle cases where the file exists, but is empty / 0 bytes + # changed from else to handle cases where the file exists, but is empty / 0 bytes if not self._config: self._config = {} @@ -209,6 +213,36 @@ class Settings(object): if not self._config: return + dirty = False + for migrate in (self._migrate_event_config, self._migrate_reverse_proxy_config): + dirty = migrate() or dirty + if dirty: + self.save(force=True) + + def _migrate_reverse_proxy_config(self): + if "server" in self._config.keys() and ("baseUrl" in self._config["server"] or "scheme" in self._config["server"]): + prefix = "" + if "baseUrl" in self._config["server"]: + prefix = self._config["server"]["baseUrl"] + del self._config["server"]["baseUrl"] + + scheme = "" + if "scheme" in self._config["server"]: + scheme = self._config["server"]["scheme"] + del self._config["server"]["scheme"] + + if not "reverseProxy" in self._config["server"] or not isinstance(self._config["server"]["reverseProxy"], dict): + self._config["server"]["reverseProxy"] = dict() + if prefix: + self._config["server"]["reverseProxy"]["prefixFallback"] = prefix + if scheme: + self._config["server"]["reverseProxy"]["schemeFallback"] = scheme + self._logger.info("Migrated reverse proxy configuration to new structure") + return True + else: + return False + + def _migrate_event_config(self): if "events" in self._config.keys() and ("gcodeCommandTrigger" in self._config["events"] or "systemCommandTrigger" in self._config["events"]): self._logger.info("Migrating config (event subscriptions)...") @@ -290,8 +324,10 @@ class Settings(object): newEvents["subscriptions"].append(newTrigger) self._config["events"] = newEvents - self.save(force=True) self._logger.info("Migrated %d event subscriptions to new format and structure" % len(newEvents["subscriptions"])) + return True + else: + return False def save(self, force=False): if not self._dirty and not force: diff --git a/src/octoprint/templates/index.jinja2 b/src/octoprint/templates/index.jinja2 index a7508680..54b75b0d 100644 --- a/src/octoprint/templates/index.jinja2 +++ b/src/octoprint/templates/index.jinja2 @@ -37,7 +37,7 @@ var CONFIG_GCODE_SIZE_THRESHOLD = {{ gcodeThreshold }}; var CONFIG_GCODE_MOBILE_SIZE_THRESHOLD = {{ gcodeMobileThreshold }}; - var SOCKJS_URI = window.location.protocol.slice(0, -1) + "://" + (window.document ? window.document.domain : window.location.hostname) + ":" + window.location.port + "/sockjs"; + var SOCKJS_URI = window.location.protocol.slice(0, -1) + "://" + (window.document ? window.document.domain : window.location.hostname) + ":" + window.location.port + BASEURL + "sockjs"; var SOCKJS_DEBUG = {% if debug -%} true; {% else %} false; {%- endif %} var UI_API_KEY = "{{ uiApiKey }}";