Skip to content

Commit b689a4d

Browse files
authored
Merge pull request #497 from takluyver/doc/callable-param-unix-sock
Document unix_socket as a parameter for callables in config
2 parents 44b5405 + f8651ac commit b689a4d

File tree

6 files changed

+60
-43
lines changed

6 files changed

+60
-43
lines changed

docs/source/server-process.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ For example, RStudio uses the term _frame origin_ and require the flag
152152
One of:
153153

154154
- A dictionary of strings that are passed in as HTTP headers to the proxy
155-
request. The strings `{port}` and `{base_url}` will be replaced as
156-
for **command**.
155+
request. The strings `{port}`, `{unix_socket}` and `{base_url}` will be
156+
replaced as for **command**.
157157
- A callable that takes any {ref}`callable arguments <server-process:callable-arguments>`,
158158
and returns a dictionary of strings that are used & treated same as above.
159159

@@ -181,9 +181,11 @@ server as raw stream data. This is similar to running a
181181
[websockify](https://github.com/novnc/websockify) wrapper.
182182
All other HTTP requests return 405.
183183

184-
#### Callable arguments
184+
### Callable arguments
185185

186-
Any time you specify a callable in the config, it can ask for any arguments it needs
186+
Certain config options accept callables, as documented above. This should return
187+
the same type of object that the option normally expects.
188+
When you use a callable this way, it can ask for any arguments it needs
187189
by simply declaring it - only arguments the callable asks for will be passed to it.
188190

189191
For example, with the following config:
@@ -213,13 +215,18 @@ The `port` argument will be passed to the callable. This is a simple form of dep
213215
injection that helps us add more parameters in the future without breaking backwards
214216
compatibility.
215217

216-
##### Available arguments
218+
#### Available arguments
217219

218-
Currently, the following arguments are available:
220+
Unless otherwise documented for specific options, the arguments available for
221+
callables are:
219222

220223
1. **port**
221-
The port the command should listen on
222-
2. **base_url**
224+
The TCP port on which the server should listen, or is listening.
225+
This is 0 if a Unix socket is used instead of TCP.
226+
2. **unix_socket**
227+
The path of a Unix socket on which the server should listen, or is listening.
228+
This is an empty string if a TCP socket is used.
229+
3. **base_url**
223230
The base URL of the notebook
224231

225232
If any of the returned strings, lists or dictionaries contain strings

jupyter_server_proxy/config.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ def _make_proxy_handler(sp: ServerProcess):
5454
Create an appropriate handler with given parameters
5555
"""
5656
if sp.command:
57-
cls = SuperviseAndRawSocketHandler if sp.raw_socket_proxy else SuperviseAndProxyHandler
57+
cls = (
58+
SuperviseAndRawSocketHandler
59+
if sp.raw_socket_proxy
60+
else SuperviseAndProxyHandler
61+
)
5862
args = dict(state={})
5963
elif not (sp.port or isinstance(sp.unix_socket, str)):
6064
warn(
@@ -122,13 +126,7 @@ def make_handlers(base_url, server_processes):
122126
handler = _make_proxy_handler(sp)
123127
if not handler:
124128
continue
125-
handlers.append(
126-
(
127-
ujoin(base_url, sp.name, r"(.*)"),
128-
handler,
129-
handler.kwargs
130-
)
131-
)
129+
handlers.append((ujoin(base_url, sp.name, r"(.*)"), handler, handler.kwargs))
132130
handlers.append((ujoin(base_url, sp.name), AddSlashHandler))
133131
return handlers
134132

@@ -159,9 +157,7 @@ def make_server_process(name, server_process_config, serverproxy_config):
159157
"rewrite_response",
160158
tuple(),
161159
),
162-
update_last_activity=server_process_config.get(
163-
"update_last_activity", True
164-
),
160+
update_last_activity=server_process_config.get("update_last_activity", True),
165161
raw_socket_proxy=server_process_config.get("raw_socket_proxy", False),
166162
)
167163

jupyter_server_proxy/rawsocket.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@
99

1010
import asyncio
1111

12-
from .handlers import NamedLocalProxyHandler, SuperviseAndProxyHandler
1312
from tornado import web
1413

14+
from .handlers import NamedLocalProxyHandler, SuperviseAndProxyHandler
15+
1516

1617
class RawSocketProtocol(asyncio.Protocol):
1718
"""
1819
A protocol handler for the proxied stream connection.
1920
Sends any received blocks directly as websocket messages.
2021
"""
22+
2123
def __init__(self, handler):
2224
self.handler = handler
2325

@@ -30,14 +32,18 @@ def data_received(self, data):
3032

3133
def connection_lost(self, exc):
3234
"Close the websocket connection."
33-
self.handler.log.info(f"Raw websocket {self.handler.name} connection lost: {exc}")
35+
self.handler.log.info(
36+
f"Raw websocket {self.handler.name} connection lost: {exc}"
37+
)
3438
self.handler.close()
3539

40+
3641
class RawSocketHandler(NamedLocalProxyHandler):
3742
"""
3843
HTTP handler that proxies websocket connections into a backend stream.
3944
All other HTTP requests return 405.
4045
"""
46+
4147
def _create_ws_connection(self, proto: asyncio.BaseProtocol):
4248
"Create the appropriate backend asyncio connection"
4349
loop = asyncio.get_running_loop()
@@ -46,17 +52,21 @@ def _create_ws_connection(self, proto: asyncio.BaseProtocol):
4652
return loop.create_unix_connection(proto, self.unix_socket)
4753
else:
4854
self.log.info(f"RawSocket {self.name} connecting to port {self.port}")
49-
return loop.create_connection(proto, 'localhost', self.port)
55+
return loop.create_connection(proto, "localhost", self.port)
5056

5157
async def proxy(self, port, path):
52-
raise web.HTTPError(405, "this raw_socket_proxy backend only supports websocket connections")
58+
raise web.HTTPError(
59+
405, "this raw_socket_proxy backend only supports websocket connections"
60+
)
5361

5462
async def proxy_open(self, host, port, proxied_path=""):
5563
"""
5664
Open the backend connection. host and port are ignored (as they are in
5765
the parent for unix sockets) since they are always passed known values.
5866
"""
59-
transp, proto = await self._create_ws_connection(lambda: RawSocketProtocol(self))
67+
transp, proto = await self._create_ws_connection(
68+
lambda: RawSocketProtocol(self)
69+
)
6070
self.ws_transp = transp
6171
self.ws_proto = proto
6272
self._record_activity()
@@ -66,8 +76,10 @@ def on_message(self, message):
6676
"Send websocket messages as stream writes, encoding if necessary."
6777
self._record_activity()
6878
if isinstance(message, str):
69-
message = message.encode('utf-8')
70-
self.ws_transp.write(message) # buffered non-blocking. should block (needs new enough tornado)
79+
message = message.encode("utf-8")
80+
self.ws_transp.write(
81+
message
82+
) # buffered non-blocking. should block (needs new enough tornado)
7183

7284
def on_ping(self, message):
7385
"No-op"
@@ -79,6 +91,7 @@ def on_close(self):
7991
if hasattr(self, "ws_transp"):
8092
self.ws_transp.close()
8193

94+
8295
class SuperviseAndRawSocketHandler(SuperviseAndProxyHandler, RawSocketHandler):
8396
async def _http_ready_func(self, p):
8497
# not really HTTP here, just try an empty connection

tests/resources/jupyter_server_config.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ def cats_only(response, path):
4242
response.code = 403
4343
response.body = b"dogs not allowed"
4444

45+
4546
def my_env():
46-
return {
47-
"MYVAR": "String with escaped {{var}}"
48-
}
47+
return {"MYVAR": "String with escaped {{var}}"}
48+
4949

5050
c.ServerProxy.servers = {
5151
"python-http": {
@@ -129,12 +129,12 @@ def my_env():
129129
"python-proxyto54321-no-command": {"port": 54321},
130130
"python-rawsocket-tcp": {
131131
"command": [sys.executable, "./tests/resources/rawsocket.py", "{port}"],
132-
"raw_socket_proxy": True
132+
"raw_socket_proxy": True,
133133
},
134134
"python-rawsocket-unix": {
135135
"command": [sys.executable, "./tests/resources/rawsocket.py", "{unix_socket}"],
136136
"unix_socket": True,
137-
"raw_socket_proxy": True
137+
"raw_socket_proxy": True,
138138
},
139139
}
140140

tests/resources/rawsocket.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#!/usr/bin/env python
22

3-
import os
43
import socket
54
import sys
65

@@ -11,7 +10,7 @@
1110
try:
1211
port = int(where)
1312
family = socket.AF_INET
14-
addr = ('localhost', port)
13+
addr = ("localhost", port)
1514
except ValueError:
1615
family = socket.AF_UNIX
1716
addr = where

tests/test_proxies.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -471,18 +471,20 @@ def test_callable_environment_formatting(
471471
assert r.code == 200
472472

473473

474-
@pytest.mark.parametrize("rawsocket_type", [
475-
"tcp",
476-
pytest.param(
477-
"unix",
478-
marks=pytest.mark.skipif(
479-
sys.platform == "win32", reason="Unix socket not supported on Windows"
474+
@pytest.mark.parametrize(
475+
"rawsocket_type",
476+
[
477+
"tcp",
478+
pytest.param(
479+
"unix",
480+
marks=pytest.mark.skipif(
481+
sys.platform == "win32", reason="Unix socket not supported on Windows"
482+
),
480483
),
481-
),
482-
])
484+
],
485+
)
483486
async def test_server_proxy_rawsocket(
484-
rawsocket_type: str,
485-
a_server_port_and_token: Tuple[int, str]
487+
rawsocket_type: str, a_server_port_and_token: Tuple[int, str]
486488
) -> None:
487489
PORT, TOKEN = a_server_port_and_token
488490
url = f"ws://{LOCALHOST}:{PORT}/python-rawsocket-{rawsocket_type}/?token={TOKEN}"

0 commit comments

Comments
 (0)