Skip to content

Commit 5cae144

Browse files
committed
Set env & mappath via CLI, add Args for Unix Sockets
1 parent fa8621f commit 5cae144

File tree

2 files changed

+124
-93
lines changed

2 files changed

+124
-93
lines changed

jupyter_server_proxy/standalone/__init__.py

Lines changed: 98 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,63 +4,63 @@
44

55
from tornado import ioloop
66
from tornado.httpserver import HTTPServer
7-
from tornado.log import app_log as log
7+
from tornado.log import app_log as log, enable_pretty_logging
88

99
from .activity import start_activity_update
10-
from .proxy import configure_http_client, get_port_from_env, get_ssl_options, make_app
10+
from .proxy import configure_http_client, get_port_from_env, get_ssl_options, make_proxy_app
1111

1212

1313
def run(
14-
command,
15-
port,
16-
destport,
17-
ip,
18-
debug,
19-
logs,
20-
overwrite_authentication,
21-
timeout,
22-
activity_interval,
23-
progressive,
24-
websocket_max_message_size,
14+
command: list[str],
15+
port: int,
16+
address: str,
17+
server_port: int,
18+
socket_path: str | None,
19+
socket_auto: bool,
20+
environment: list[tuple[str, str]] | None,
21+
mappath: list[tuple[str, str]] | None,
22+
debug: bool,
23+
# logs: bool,
24+
overwrite_authentication: bool | None,
25+
timeout: int,
26+
activity_interval: int,
27+
# progressive: bool,
28+
websocket_max_message_size: int,
2529
):
26-
if port is None:
27-
get_port_from_env()
28-
30+
# Setup Logging
31+
enable_pretty_logging(logger=log)
2932
if debug:
3033
log.setLevel(logging.DEBUG)
31-
elif logs:
32-
log.setLevel(logging.INFO)
3334

34-
prefix = os.environ.get("JUPYTERHUB_SERVICE_PREFIX", "/")
35+
if not port:
36+
port = get_port_from_env()
3537

36-
if len(prefix) > 0 and prefix[-1] == "/":
37-
prefix = prefix[:-1]
38+
if overwrite_authentication is True:
39+
log.info("Enabling Authentication with JupyterHub")
3840

39-
configure_http_client()
41+
prefix = os.environ.get("JUPYTERHUB_SERVICE_PREFIX", "/")
4042

41-
app = make_app(
42-
destport,
43-
prefix,
44-
list(command),
45-
overwrite_authentication,
43+
app = make_proxy_app(
44+
command,
45+
prefix.removesuffix("/"),
46+
server_port,
47+
socket_path or socket_auto,
48+
dict(environment),
49+
dict(mappath),
4650
timeout,
51+
overwrite_authentication is True,
4752
debug,
48-
logs,
49-
progressive,
5053
websocket_max_message_size,
5154
)
5255

5356
ssl_options = get_ssl_options()
5457

5558
http_server = HTTPServer(app, ssl_options=ssl_options, xheaders=True)
59+
http_server.listen(port, address)
5660

57-
http_server.listen(port or get_port_from_env(), ip)
58-
59-
log.info(
60-
f"Starting standaloneproxy on {ip}:{port}, server is started on Port {destport}"
61-
)
62-
log.info(f"URL Prefix: {prefix}")
63-
log.info(f"Command: {command}")
61+
log.info(f"Starting standaloneproxy on '{address}:{port}'")
62+
log.info(f"URL Prefix: {prefix!r}")
63+
log.info(f"Command: {' '.join(command)!r}")
6464

6565
if activity_interval > 0:
6666
start_activity_update(activity_interval)
@@ -70,36 +70,81 @@ def run(
7070

7171
def main():
7272
parser = argparse.ArgumentParser(
73-
"jupyter-native-proxy",
74-
description="Wrap an arbitrary WebApp so it can be used in place of 'singleuser' in a JupyterHub setting",
73+
"jupyter-standalone-proxy",
74+
description="Wrap an arbitrary WebApp so it can be used in place of 'jupyterhub-singleuser' in a JupyterHub setting.",
7575
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
7676
)
7777

7878
parser.add_argument(
79+
"-p",
7980
"--port",
80-
default=None,
81+
default=0,
8182
type=int,
82-
help="Port for the proxy server to listen on. Defaults to JupyterHub default.",
83+
dest="port",
84+
help="Port for the proxy server to listen on (0 for JupyterHub default).",
85+
)
86+
parser.add_argument(
87+
"-a",
88+
"--address",
89+
default="localhost",
90+
type=str,
91+
dest="address",
92+
help="Address for the proxy server to listen on.",
8393
)
8494
parser.add_argument(
85-
"--destport",
95+
"-s",
96+
"--server-port",
8697
default=0,
8798
type=int,
88-
help="Port for the WebApp should end up running on. Leave at 0 for a random open port.",
99+
dest="server_port",
100+
help="Port for the WebApp should end up running on (0 for random open port).",
101+
)
102+
parser.add_argument(
103+
"--socket-path",
104+
type=str,
105+
default=None,
106+
help="Path to the Unix Socket to use for proxying. Takes precedence over '-s/--server_port' and '--socket-auto'.",
107+
)
108+
parser.add_argument(
109+
"--socket-auto",
110+
action="store_true",
111+
help="Use Unix Socket for proxying, but let Jupyter Server Proxy automatically create one.",
112+
)
113+
parser.add_argument(
114+
"--env",
115+
"--environment",
116+
type=lambda v: tuple(v.split(":")[:2]),
117+
default=[],
118+
action="append",
119+
dest="environment",
120+
help="Add an environment variable to the server process. Must be of the form <Name>:<Value>, e.g. --env=MY_VAR:42",
89121
)
90-
parser.add_argument("--ip", default="localhost", help="Address to listen on.")
91122
parser.add_argument(
92-
"--debug", action="store_true", default=False, help="Display debug level logs."
123+
"--mappath",
124+
type=lambda v: tuple(v.split(":")[:2]),
125+
default=[],
126+
action="append",
127+
help="Add an path mapping to the proxy. Any requests received under <Source> will be redirected to <Target>. "
128+
"Must be of the form <Source>:<Target>, e.g. --mappath=/:/index.html",
93129
)
94130
parser.add_argument(
95-
"--logs",
131+
"-d",
132+
"--debug",
96133
action="store_true",
97-
default=True,
98-
help="Display logs generated by the subprocess.",
134+
default=False,
135+
dest="debug",
136+
help="Display debug level logs.",
99137
)
138+
# parser.add_argument(
139+
# "--logs",
140+
# action="store_true",
141+
# default=True,
142+
# help="Display logs generated by the subprocess.",
143+
# )
100144
parser.add_argument(
101145
"--overwrite-authentication",
102146
default=None,
147+
type=lambda v: None if v is None else bool(v),
103148
help="Forcefully enable/disable authentication with JupyterHub.",
104149
)
105150
parser.add_argument(
@@ -114,12 +159,12 @@ def main():
114159
type=int,
115160
help="Frequency to notify Hub that the WebApp is still running (In seconds, 0 for never).",
116161
)
117-
parser.add_argument(
118-
"--progressive",
119-
action="store_true",
120-
default=False,
121-
help="Progressively flush responses as they arrive (good for Voila).",
122-
)
162+
# parser.add_argument(
163+
# "--progressive",
164+
# action="store_true",
165+
# default=False,
166+
# help="Progressively flush responses as they arrive (good for Voila).",
167+
# )
123168
parser.add_argument(
124169
"--websocket-max-message-size",
125170
default=0,

jupyter_server_proxy/standalone/proxy.py

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from jupyterhub.services.auth import HubOAuthCallbackHandler, HubOAuthenticated
77
from jupyterhub.utils import make_ssl_context
88
from tornado import httpclient, web
9+
from tornado.log import app_log
910
from tornado.web import Application, RedirectHandler
1011
from tornado.websocket import WebSocketHandler
1112

@@ -78,57 +79,41 @@ def set_default_headers(self):
7879
self.set_header("X-JupyterHub-Version", __jh_version__)
7980

8081

81-
def _make_native_proxy_handler(
82-
command, port, mappath, overwrite_authentication, environment, timeout
82+
def make_proxy_app(
83+
command: list[str],
84+
prefix: str,
85+
port: int,
86+
unix_socket: bool | str,
87+
environment: dict[str, str],
88+
mappath: dict[str, str],
89+
timeout: int,
90+
skip_authentication: bool,
91+
debug: bool,
92+
# progressive: bool,
93+
websocket_max_message_size: int,
8394
):
8495
"""
8596
Create a StandaloneHubProxyHandler subclass with given parameters
8697
"""
98+
app_log.debug(f"Process will use {port = }")
99+
app_log.debug(f"Process will use {unix_socket = }")
100+
app_log.debug(f"Process environment: {environment}")
101+
app_log.debug(f"Proxy mappath: {mappath}")
87102

88-
# Try to determine if we are launched by a JupyterHub via environment variables.
89-
# See jupyterhub/spawner.py#L1035
90-
if overwrite_authentication is not None:
91-
base = (
92-
StandaloneHubProxyHandler
93-
if overwrite_authentication is True
94-
else StandaloneProxyHandler
95-
)
96-
elif "JUPYTERHUB_API_TOKEN" in os.environ and "JUPYTERHUB_API_URL" in os.environ:
97-
base = StandaloneHubProxyHandler
98-
else:
99-
base = StandaloneProxyHandler
100-
101-
class _Proxy(base):
103+
base = StandaloneProxyHandler if skip_authentication else StandaloneHubProxyHandler
104+
class Proxy(base):
102105
def __init__(self, *args, **kwargs):
103106
super().__init__(*args, **kwargs)
104-
self.name = command[0]
107+
self.name = f"{command[0]!r} Process"
105108
self.proxy_base = command[0]
106109
self.requested_port = port
110+
self.requested_unix_socket = unix_socket
107111
self.mappath = mappath
108112
self.command = command
109113
self.environment = environment
110114
self.timeout = timeout
111115

112-
return _Proxy
113-
114-
115-
def make_app(
116-
destport,
117-
prefix,
118-
command,
119-
authtype,
120-
timeout,
121-
debug,
122-
logs,
123-
progressive,
124-
websocket_max_message_size,
125-
):
126-
# ToDo: Environment
127-
proxy_handler = _make_native_proxy_handler(
128-
command, destport, {}, authtype, {}, timeout
129-
)
130-
131-
options = dict(
116+
settings = dict(
132117
debug=debug,
133118
# Required for JupyterHub Authentication
134119
hub_user=os.environ.get("JUPYTERHUB_USER", ""),
@@ -137,7 +122,8 @@ def make_app(
137122
)
138123

139124
if websocket_max_message_size:
140-
options["websocket_max_message_size"] = websocket_max_message_size
125+
app_log.debug(f"Restricting WebSocket Messages to {websocket_max_message_size}")
126+
settings["websocket_max_message_size"] = websocket_max_message_size
141127

142128
return Application(
143129
[
@@ -147,7 +133,7 @@ def make_app(
147133
),
148134
(
149135
r"^" + re.escape(prefix) + r"/(.*)",
150-
proxy_handler,
136+
Proxy,
151137
dict(
152138
state={},
153139
# ToDo: progressive=progressive
@@ -159,7 +145,7 @@ def make_app(
159145
dict(url=prefix + "/{0}"),
160146
),
161147
],
162-
**options,
148+
**settings,
163149
)
164150

165151

0 commit comments

Comments
 (0)