diff --git a/CHANGES.txt b/CHANGES.txt index 167e062d5..451abab22 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -51,6 +51,10 @@ - Files included via the `[include]` section are read in sorted order. In past versions, the order was undefined. Patch by Ionel Cristian Mărieș. +- Added ``base_path`` option in the `[inet_http_server]` section. This + allows to put the web interface behind a reverse proxy (i.e. web server). + The default value is ``/``. + - Fixed a bug introduced in 3.1.0 where ``supervisord`` could crash when attempting to display a resource limit error. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index a2b709589..5fa0f689f 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -48,3 +48,5 @@ Contributors - Márk Sági-Kazár, 2013-12-16 - Gülşah Köse, 2014-07-17 + +- Seyeong Jeong, 2015-03-19 diff --git a/TODO.txt b/TODO.txt index c29ca78e6..d13687d83 100644 --- a/TODO.txt +++ b/TODO.txt @@ -69,8 +69,6 @@ - Option to automatically refresh the status page (issue #73). - - Better support for use with proxy servers (issue #29) - - Expat error on Jens' system running slapd as root after reload. - Command-line arg tests. diff --git a/docs/configuration.rst b/docs/configuration.rst index ab30f7768..56fa578b2 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -175,6 +175,18 @@ configuration values are as follows. *Introduced*: 3.0 +``base_path`` + + Specify the base path for the web interface. For example, + ``base_path=/supervisor/`` yields the web interface accessible at + ``http://localhost:4567/supervisor/``. + + *Default*: ``/`` + + *Required*: No. + + *Introduced*: 4.0 + ``[inet_http_server]`` Section Example ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/supervisor/http.py b/supervisor/http.py index 759424b3a..989dc64d1 100644 --- a/supervisor/http.py +++ b/supervisor/http.py @@ -420,6 +420,11 @@ def found_terminator (self): command, uri, version = http_server.crack_request (request) header = http_server.join_headers (lines[1:]) + # strip the ``base_path`` so that the handlers don't need to know + # about the ``base_path``. + if uri.startswith(self.server.base_path): + uri = '/' + uri[len(self.server.base_path):] + # unquote path if necessary (thanks to Skip Montanaro for pointing # out that we must unquote in piecemeal fashion). rpath, rquery = http_server.splitquery(uri) @@ -522,12 +527,13 @@ def log_info(self, message, type='info'): class supervisor_af_inet_http_server(supervisor_http_server): """ AF_INET version of supervisor HTTP server """ - def __init__(self, ip, port, logger_object): + def __init__(self, ip, port, logger_object, base_path='/'): self.ip = ip self.port = port sock = text_socket.text_socket(socket.AF_INET, socket.SOCK_STREAM) self.prebind(sock, logger_object) self.bind((ip, port)) + self.base_path = base_path if not ip: self.log_info('Computing default hostname', 'warning') @@ -799,8 +805,10 @@ def log(self, msg): if family == socket.AF_INET: host, port = config['host'], config['port'] + base_path = config.get('base_path', '/') hs = supervisor_af_inet_http_server(host, port, - logger_object=wrapper) + logger_object=wrapper, + base_path=base_path) elif family == socket.AF_UNIX: socketname = config['file'] sockchmod = config['chmod'] diff --git a/supervisor/options.py b/supervisor/options.py index 32c3b26f5..292f1e3db 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -1025,6 +1025,15 @@ def server_configs_from_parser(self, parser): config['host'] = host config['port'] = port config['section'] = section + + # sanitize base_path + base_path = get(section, 'base_path', '/') + if not base_path.endswith('/'): + base_path += '/' + if not base_path.startswith('/'): + base_path = '/' + base_path + config['base_path'] = base_path + configs.append(config) unix_serverdefs = self._parse_servernames(parser, 'unix_http_server') diff --git a/supervisor/skel/sample.conf b/supervisor/skel/sample.conf index 1ae42bd62..938c49480 100644 --- a/supervisor/skel/sample.conf +++ b/supervisor/skel/sample.conf @@ -19,6 +19,7 @@ file=/tmp/supervisor.sock ; (the path to the socket file) ;port=127.0.0.1:9001 ; (ip_address:port specifier, *:port for all iface) ;username=user ; (default is no username (open server)) ;password=123 ; (default is no password (open server)) +;base_path=/supervisor/ ; (default is /) [supervisord] logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log) diff --git a/supervisor/tests/base.py b/supervisor/tests/base.py index a06e957f6..15d662f84 100644 --- a/supervisor/tests/base.py +++ b/supervisor/tests/base.py @@ -604,6 +604,7 @@ def log(self, category, msg): class DummyMedusaServer: def __init__(self): self.logger = DummyMedusaServerLogger() + self.base_path = '/' class DummyMedusaChannel: def __init__(self): diff --git a/supervisor/ui/status.html b/supervisor/ui/status.html index bfef1e00e..2341ca76d 100644 --- a/supervisor/ui/status.html +++ b/supervisor/ui/status.html @@ -6,6 +6,7 @@ Supervisor Status +
diff --git a/supervisor/web.py b/supervisor/web.py index 5b751e67e..640d5d5a1 100644 --- a/supervisor/web.py +++ b/supervisor/web.py @@ -185,6 +185,10 @@ def __call__(self): response['body'] = as_string(body) return response + @property + def base_path(self): + return self.context.request.channel.server.base_path + def render(self): pass @@ -429,9 +433,7 @@ def render(self): if message is NOT_DONE_YET: return NOT_DONE_YET if message is not None: - server_url = form['SERVER_URL'] - location = server_url + '?message=%s' % urllib.quote( - message) + location = '%s?message=%s' % (self.base_path, urllib.quote(message)) response['headers']['Location'] = location supervisord = self.context.supervisord @@ -511,6 +513,7 @@ def render(self): root.findmeld('supervisor_version').content(VERSION) copyright_year = str(datetime.date.today().year) root.findmeld('copyright_date').content(copyright_year) + root.findmeld('base_path').attrib['href'] = self.base_path return as_string(root.write_xhtmlstring()) @@ -556,9 +559,7 @@ def match(self, request): if not path: path = 'index.html' - for viewname in VIEWS.keys(): - if viewname == path: - return True + return path in VIEWS.keys() def handle_request(self, request): if request.command == 'POST':