Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handler subprotocol method fails to detect empty list #442

Closed
duytnguyendtn opened this issue Jan 22, 2024 · 1 comment · Fixed by #458
Closed

Handler subprotocol method fails to detect empty list #442

duytnguyendtn opened this issue Jan 22, 2024 · 1 comment · Fixed by #458
Labels

Comments

@duytnguyendtn
Copy link

duytnguyendtn commented Jan 22, 2024

Bug description

Hello JSP developers!

My colleague @zoghbi-a and I are attempting to proxy socket.io using JSP and have run into a subprotocol issue that I've started to track down. Please note I am coming as a JSP user with not much networking experience. I'd like to open a discussion about this issue and would be happy to be told I'm just not using JSP properly and corrected. I also didn't want to open a PR in the event I'm misunderstanding something. Thank you for your patience!

Effectively, we're receiving strange headers on our socket.io server that are resulting in a HTTP 400 Bad Request. Tracking down the HTTP response being sent by the proxy, I noticed the following fields being sent in the HTTP header:

> e:\heasarc\gitrepos\scieserver-js9\envjpyjs9\lib\site-packages\jupyter_server_proxy\handlers.py(503)start_websocket_connection()
(Pdb) print(request.headers)
Host: localhost:8888
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Accept-Language: en-US,en
Upgrade: websocket
Origin: http://localhost:8888
Sec-Websocket-Version: 13
Accept-Encoding: gzip
Cookie: username-localhost-8888="<irrelevant values>"
Sec-Websocket-Key: b'XXXXXXXXXXXXXXXXXXXX'
Sec-Websocket-Extensions: permessage-deflate; client_max_window_bits
Sec-Websocket-Protocol:

Specifically, I noticed that last line:

Sec-Websocket-Protocol:

As I understand it, when a websocket protocol is not specified, the Sec-Websocket-Protocol is supposed to be omitted, rather than sent without a value.

The following is my best attempt to understand how this condition is arising (all links are permalinked to the current main commit: a65437a):

The HTTP 400 error is originating from the following HTTPRequest in JSP in attempting to start a websocket connection:

self.ws = await pingable_ws_connect(
request=request,
on_message_callback=message_cb,
on_ping_callback=ping_cb,
subprotocols=self.subprotocols,
resolver=resolver,

The cause is the value of self.subprotocols. It's receiving an empty list []. This empty list is originating from:
https://github.com/tornadoweb/tornado/blob/65a9e48f8ce645f104e3e0aa772222e70b0376d9/tornado/websocket.py#L902
most likely due to a subprotocol header not being provided on our end. This harkens back to another user's issue: #47

Following the trace, the JSP handler's select_subprotocol appears to be processing the empty list properly:

def select_subprotocol(self, subprotocols):
"""Select a single Sec-WebSocket-Protocol during handshake."""
self.subprotocols = subprotocols
if isinstance(subprotocols, list) and subprotocols:
self.log.debug(f"Client sent subprotocols: {subprotocols}")
return subprotocols[0]
return super().select_subprotocol(subprotocols)

by properly falling back on Tornado's WebSocketHandler, which properly returns None:
https://github.com/tornadoweb/tornado/blob/65a9e48f8ce645f104e3e0aa772222e70b0376d9/tornado/websocket.py#L337-L360

However, select_subprotocol also caches the provided value:

self.subprotocols = subprotocols

which is what is used websocket connection method:

subprotocols=self.subprotocols,

thereby ignoring the "sanitized" subprotocol value provided by Tornado's WebSocketHandler.

TLDR solution:

I feel that either the jupyter_server_proxy.handlers.ProxyHandler.proxy_open should be directly using the sanitized result of jupyter_server_proxy.handlers.ProxyHandler.select_subprotocol:

self.ws = await pingable_ws_connect(
    request=request,
    on_message_callback=message_cb,
    on_ping_callback=ping_cb,
    subprotocols=self.select_subprotcol(self.subprotocols),
    resolver=resolver,
)

or, jupyter_server_proxy.handlers.ProxyHandler.select_subprotocol should instead use the Tornado WebSocketHandler's cached value of the final selected subprotocol: https://github.com/tornadoweb/tornado/blob/65a9e48f8ce645f104e3e0aa772222e70b0376d9/tornado/websocket.py#L903

self.ws = await pingable_ws_connect(
    request=request,
    on_message_callback=message_cb,
    on_ping_callback=ping_cb,
    subprotocols=self.selected_subprotcol,
    resolver=resolver,
)

Please let me know your thoughts! I appreciate your time and effort on this project :)

Copy link

welcome bot commented Jan 22, 2024

Thank you for opening your first issue in this project! Engagement like this is essential for open source projects! 🤗

If you haven't done so already, check out Jupyter's Code of Conduct. Also, please try to follow the issue template as it helps other other community members to contribute more effectively.
welcome
You can meet the other Jovyans by joining our Discourse forum. There is also an intro thread there where you can stop by and say Hi! 👋

Welcome to the Jupyter community! 🎉

benz0li added a commit to benz0li/jupyter-server-proxy that referenced this issue Feb 15, 2024
benz0li added a commit to benz0li/jupyter-server-proxy that referenced this issue Feb 16, 2024
benz0li added a commit to benz0li/jupyter-server-proxy that referenced this issue Feb 18, 2024
- Modify tests to use headers
- Remove SubprotocolWebSocket
- Add test for empty and no subprotocols
- Fix jupyterhub#442
- Fix jupyterhub#445
consideRatio pushed a commit to consideRatio/jupyter-server-proxy that referenced this issue Feb 21, 2024
- Modify tests to use headers
- Remove SubprotocolWebSocket
- Add test for empty and no subprotocols
- Fix jupyterhub#442
- Fix jupyterhub#445
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment