Skip to content

Commit 95ae0af

Browse files
authored
Merge pull request #96 from manics/config-port
Configure port for servers
2 parents 9c9e4af + 2e81c2a commit 95ae0af

File tree

5 files changed

+55
-4
lines changed

5 files changed

+55
-4
lines changed

docs/server-process.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ pairs.
8484
Defaults to *False*.
8585

8686

87+
#. **port**
88+
89+
Set the port that the service will listen on. The default is to
90+
automatically select an unused port.
91+
92+
8793
#. **launcher_entry**
8894

8995
A dictionary with options on if / how an entry in the classic Jupyter Notebook

jupyter_server_proxy/config.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from collections import namedtuple
1010
from .utils import call_with_asked_args
1111

12-
def _make_serverproxy_handler(name, command, environment, timeout, absolute_url):
12+
def _make_serverproxy_handler(name, command, environment, timeout, absolute_url, port):
1313
"""
1414
Create a SuperviseAndProxyHandler subclass with given parameters
1515
"""
@@ -20,6 +20,7 @@ def __init__(self, *args, **kwargs):
2020
self.name = name
2121
self.proxy_base = name
2222
self.absolute_url = absolute_url
23+
self.requested_port = port
2324

2425
@property
2526
def process_args(self):
@@ -80,6 +81,7 @@ def make_handlers(base_url, server_processes):
8081
sp.environment,
8182
sp.timeout,
8283
sp.absolute_url,
84+
sp.port,
8385
)
8486
handlers.append((
8587
ujoin(base_url, sp.name, r'(.*)'), handler, dict(state={}),
@@ -91,7 +93,7 @@ def make_handlers(base_url, server_processes):
9193

9294
LauncherEntry = namedtuple('LauncherEntry', ['enabled', 'icon_path', 'title'])
9395
ServerProcess = namedtuple('ServerProcess', [
94-
'name', 'command', 'environment', 'timeout', 'absolute_url', 'launcher_entry'])
96+
'name', 'command', 'environment', 'timeout', 'absolute_url', 'port', 'launcher_entry'])
9597

9698
def make_server_process(name, server_process_config):
9799
le = server_process_config.get('launcher_entry', {})
@@ -101,6 +103,7 @@ def make_server_process(name, server_process_config):
101103
environment=server_process_config.get('environment', {}),
102104
timeout=server_process_config.get('timeout', 5),
103105
absolute_url=server_process_config.get('absolute_url', False),
106+
port=server_process_config.get('port', 0),
104107
launcher_entry=LauncherEntry(
105108
enabled=le.get('enabled', True),
106109
icon_path=le.get('icon_path'),
@@ -138,6 +141,9 @@ class ServerProxy(Configurable):
138141
Proxy requests default to being rewritten to '/'. If this is True,
139142
the absolute URL will be sent to the backend instead.
140143
144+
port
145+
Set the port that the service will listen on. The default is to automatically select an unused port.
146+
141147
launcher_entry
142148
A dictionary of various options for entries in classic notebook / jupyterlab launchers.
143149

jupyter_server_proxy/handlers.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,10 @@ def select_subprotocol(self, subprotocols):
284284
class SuperviseAndProxyHandler(LocalProxyHandler):
285285
'''Manage a given process and requests to it '''
286286

287+
def __init__(self, *args, **kwargs):
288+
self.requested_port = 0
289+
super().__init__(*args, **kwargs)
290+
287291
def initialize(self, state):
288292
self.state = state
289293
if 'proc_lock' not in state:
@@ -294,11 +298,12 @@ def initialize(self, state):
294298
@property
295299
def port(self):
296300
"""
297-
Allocate a random empty port for use by application
301+
Allocate either the requested port or a random empty port for use by
302+
application
298303
"""
299304
if 'port' not in self.state:
300305
sock = socket.socket()
301-
sock.bind(('', 0))
306+
sock.bind(('', self.requested_port))
302307
self.state['port'] = sock.getsockname()[1]
303308
sock.close()
304309
return self.state['port']

tests/resources/jupyter_server_config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@
66
'command': ['python3', './tests/resources/httpinfo.py', '{port}'],
77
'absolute_url': True
88
},
9+
'python-http-port54321': {
10+
'command': ['python3', './tests/resources/httpinfo.py', '{port}'],
11+
'port': 54321,
12+
},
913
}
1014
#c.Application.log_level = 'DEBUG'

tests/test_proxies.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,33 @@ def test_server_proxy_absolute():
2727
assert s.startswith('GET /python-http-abs/def?token=')
2828
assert 'X-Forwarded-Context' not in s
2929
assert 'X-Proxycontextpath' not in s
30+
31+
32+
def test_server_proxy_requested_port():
33+
r = request_get(PORT, '/python-http-port54321/ghi', TOKEN)
34+
assert r.code == 200
35+
s = r.read().decode('ascii')
36+
assert s.startswith('GET /ghi?token=')
37+
assert 'X-Forwarded-Context: /python-http-port54321\n' in s
38+
assert 'X-Proxycontextpath: /python-http-port54321\n' in s
39+
40+
direct = request_get(54321, '/ghi', TOKEN)
41+
assert direct.code == 200
42+
43+
44+
def test_server_proxy_port_non_absolute():
45+
r = request_get(PORT, '/proxy/54321/jkl', TOKEN)
46+
assert r.code == 200
47+
s = r.read().decode('ascii')
48+
assert s.startswith('GET /jkl?token=')
49+
assert 'X-Forwarded-Context: /proxy/54321\n' in s
50+
assert 'X-Proxycontextpath: /proxy/54321\n' in s
51+
52+
53+
def test_server_proxy_port_absolute():
54+
r = request_get(PORT, '/proxy/absolute/54321/nmo', TOKEN)
55+
assert r.code == 200
56+
s = r.read().decode('ascii')
57+
assert s.startswith('GET /proxy/absolute/54321/nmo?token=')
58+
assert 'X-Forwarded-Context' not in s
59+
assert 'X-Proxycontextpath' not in s

0 commit comments

Comments
 (0)