Skip to content

Commit 15447ba

Browse files
authored
Merge pull request #339 from ryshoooo/main
Accept an unset `command` to proxy to an already started process (unmanaged process)
2 parents e7858bd + d6fbbd8 commit 15447ba

File tree

5 files changed

+37
-5
lines changed

5 files changed

+37
-5
lines changed

docs/source/server-process.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ pairs.
3636
* A callable that takes any :ref:`callable arguments <server-process/callable-arguments>`,
3737
and returns a list of strings that are used & treated same as above.
3838

39-
This key is required.
39+
If the command is not specified or is an empty list, the server process is
40+
assumed to be started ahead of time and already available to be proxied to.
4041

4142
``timeout``
4243
^^^^^^^^^^^

jupyter_server_proxy/config.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class _Proxy(SuperviseAndProxyHandler):
2626
def __init__(self, *args, **kwargs):
2727
super().__init__(*args, **kwargs)
2828
self.name = name
29+
self.command = command
2930
self.proxy_base = name
3031
self.absolute_url = absolute_url
3132
self.requested_port = port
@@ -62,7 +63,7 @@ def _realize_rendered_template(self, attribute):
6263
return self._render_template(attribute)
6364

6465
def get_cmd(self):
65-
return self._realize_rendered_template(command)
66+
return self._realize_rendered_template(self.command)
6667

6768
def get_env(self):
6869
return self._realize_rendered_template(environment)
@@ -121,7 +122,7 @@ def make_server_process(name, server_process_config, serverproxy_config):
121122
le = server_process_config.get('launcher_entry', {})
122123
return ServerProcess(
123124
name=name,
124-
command=server_process_config['command'],
125+
command=server_process_config.get('command', list()),
125126
environment=server_process_config.get('environment', {}),
126127
timeout=server_process_config.get('timeout', 5),
127128
absolute_url=server_process_config.get('absolute_url', False),
@@ -152,12 +153,16 @@ class ServerProxy(Configurable):
152153
153154
Value should be a dictionary with the following keys:
154155
command
155-
A list of strings that should be the full command to be executed.
156+
An optional list of strings that should be the full command to be executed.
156157
The optional template arguments {{port}} and {{base_url}} will be substituted with the
157158
port the process should listen on and the base-url of the notebook.
158159
159160
Could also be a callable. It should return a list.
160161
162+
If the command is not specified or is an empty list, the server
163+
process is assumed to be started ahead of time and already available
164+
to be proxied to.
165+
161166
environment
162167
A dictionary of environment variable mappings. As with the command
163168
traitlet, {{port}} and {{base_url}} will be substituted.

jupyter_server_proxy/handlers.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ class SuperviseAndProxyHandler(LocalProxyHandler):
573573
def __init__(self, *args, **kwargs):
574574
self.requested_port = 0
575575
self.mappath = {}
576+
self.command = list()
576577
super().__init__(*args, **kwargs)
577578

578579
def initialize(self, state):
@@ -588,11 +589,14 @@ def port(self):
588589
Allocate either the requested port or a random empty port for use by
589590
application
590591
"""
591-
if 'port' not in self.state:
592+
if 'port' not in self.state and self.command:
592593
sock = socket.socket()
593594
sock.bind(('', self.requested_port))
594595
self.state['port'] = sock.getsockname()[1]
595596
sock.close()
597+
elif 'port' not in self.state:
598+
self.state['port'] = self.requested_port
599+
596600
return self.state['port']
597601

598602
def get_cwd(self):
@@ -639,7 +643,14 @@ async def ensure_process(self):
639643
if 'proc' not in self.state:
640644
# FIXME: Prevent races here
641645
# FIXME: Handle graceful exits of spawned processes here
646+
647+
# When command option isn't truthy, it means its a process not
648+
# to be managed/started by jupyter-server-proxy. This means we
649+
# won't await its readiness or similar either.
642650
cmd = self.get_cmd()
651+
if not cmd:
652+
self.state['proc'] = "process not managed by jupyter-server-proxy"
653+
return
643654

644655
# Set up extra environment variables for process
645656
server_env = os.environ.copy()

tests/resources/jupyter_server_config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ def cats_only(response, path):
8282
'command': ['python3', './tests/resources/httpinfo.py', '{port}'],
8383
'rewrite_response': [cats_only, dog_to_cat],
8484
},
85+
'python-proxyto54321-no-command': {
86+
'port': 54321
87+
}
8588
}
8689

8790
c.ServerProxy.non_service_rewrite_response = hello_to_foo

tests/test_proxies.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,18 @@ def test_server_proxy_requested_port():
150150
assert direct.code == 200
151151

152152

153+
def test_server_proxy_on_requested_port_no_command():
154+
r = request_get(PORT, '/python-proxyto54321-no-command/ghi', TOKEN)
155+
assert r.code == 200
156+
s = r.read().decode('ascii')
157+
assert s.startswith('GET /ghi?token=')
158+
assert 'X-Forwarded-Context: /python-proxyto54321-no-command\n' in s
159+
assert 'X-Proxycontextpath: /python-proxyto54321-no-command\n' in s
160+
161+
direct = request_get(54321, '/ghi', TOKEN)
162+
assert direct.code == 200
163+
164+
153165
def test_server_proxy_port_non_absolute():
154166
r = request_get(PORT, '/proxy/54321/jkl', TOKEN)
155167
assert r.code == 200

0 commit comments

Comments
 (0)