Skip to content

GatewayClient doesn't correctly handle multiple cookies returned from gateway server #1557

@kevin-bates

Description

@kevin-bates

Description

When configuring a Gateway Server with session affinity, it is important to set the accept cookies configurable option so that session-specific cookies are maintained within the GatewayClient and used on subsequent requests. This handling was introduced in #969 but does not account for the case when multiple entries are located in the Set-Cookie key of the response headers.

When multiple cookies are present, response.headers.get("Set-Cookie") returns a comma-separated string of cookies. This breaks the parsing logic on the subsequently called SimpleCookie.load() method.

I have a proposed fix and will provide a PR soon.

Reproduce

  1. Configure a Gateway Server (I used JupyterKernelGateway) that configures session affinity.
  2. Configure the gateway client to accept cookies via --GatewayClient.accept_cookies=true
  3. Start lab with the appropriate --gateway-url option, along with accept_cookies.

When the server starts and issues the /api/kernelspecs to fetch kernelspecs from the gateway, the following stack trace is produced:

Stack trace when multiple cookies are returned...
[E 2025-08-25 17:13:31.233 ServerApp] Uncaught exception GET /api/kernelspecs?1756167210712 (::1)
    HTTPServerRequest(protocol='http', host='localhost:8888', method='GET', uri='/api/kernelspecs?1756167210712', version='HTTP/1.1', remote_ip='::1')
    Traceback (most recent call last):
      File "/Users/kbates/dev/playground/jupyter/.venv/lib/python3.12/site-packages/tornado/web.py", line 1848, in _execute
        result = await result
                 ^^^^^^^^^^^^
      File "/Users/kbates/dev/playground/jupyter/.venv/lib/python3.12/site-packages/jupyter_server/auth/decorator.py", line 73, in inner
        return await out
               ^^^^^^^^^
      File "/Users/kbates/dev/playground/jupyter/.venv/lib/python3.12/site-packages/jupyter_server/services/kernelspecs/handlers.py", line 73, in get
        kspecs = await ensure_async(ksm.get_all_specs())
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/Users/kbates/dev/playground/jupyter/.venv/lib/python3.12/site-packages/jupyter_core/utils/__init__.py", line 197, in ensure_async
        result = await obj
                 ^^^^^^^^^
      File "/Users/kbates/dev/playground/jupyter/.venv/lib/python3.12/site-packages/jupyter_server/gateway/managers.py", line 277, in get_all_specs
        fetched_kspecs = await self.list_kernel_specs()
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/Users/kbates/dev/playground/jupyter/.venv/lib/python3.12/site-packages/jupyter_server/gateway/managers.py", line 301, in list_kernel_specs
        response = await gateway_request(kernel_spec_url, method="GET")
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/Users/kbates/dev/playground/jupyter/.venv/lib/python3.12/site-packages/jupyter_server/gateway/gateway_client.py", line 828, in gateway_request
        cookie.load(cookie_values)
      File "/Users/kbates/.local/share/uv/python/cpython-3.12.11-macos-aarch64-none/lib/python3.12/http/cookies.py", line 516, in load
        self.__parse_string(rawdata)
      File "/Users/kbates/.local/share/uv/python/cpython-3.12.11-macos-aarch64-none/lib/python3.12/http/cookies.py", line 580, in __parse_string
        self.__set(key, rval, cval)
      File "/Users/kbates/.local/share/uv/python/cpython-3.12.11-macos-aarch64-none/lib/python3.12/http/cookies.py", line 472, in __set
        M.set(key, real_value, coded_value)
      File "/Users/kbates/.local/share/uv/python/cpython-3.12.11-macos-aarch64-none/lib/python3.12/http/cookies.py", line 335, in set
        raise CookieError('Illegal key %r' % (key,))
    http.cookies.CookieError: Illegal key 'HttpOnly,SERVERSESSION'
[W 2025-08-25 17:13:31.237 ServerApp] wrote error: 'Unhandled error'
    Traceback (most recent call last):
      File "/Users/kbates/dev/playground/jupyter/.venv/lib/python3.12/site-packages/tornado/web.py", line 1848, in _execute
        result = await result
                 ^^^^^^^^^^^^
      File "/Users/kbates/dev/playground/jupyter/.venv/lib/python3.12/site-packages/jupyter_server/auth/decorator.py", line 73, in inner
        return await out
               ^^^^^^^^^
      File "/Users/kbates/dev/playground/jupyter/.venv/lib/python3.12/site-packages/jupyter_server/services/kernelspecs/handlers.py", line 73, in get
        kspecs = await ensure_async(ksm.get_all_specs())
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/Users/kbates/dev/playground/jupyter/.venv/lib/python3.12/site-packages/jupyter_core/utils/__init__.py", line 197, in ensure_async
        result = await obj
                 ^^^^^^^^^
      File "/Users/kbates/dev/playground/jupyter/.venv/lib/python3.12/site-packages/jupyter_server/gateway/managers.py", line 277, in get_all_specs
        fetched_kspecs = await self.list_kernel_specs()
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/Users/kbates/dev/playground/jupyter/.venv/lib/python3.12/site-packages/jupyter_server/gateway/managers.py", line 301, in list_kernel_specs
        response = await gateway_request(kernel_spec_url, method="GET")
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/Users/kbates/dev/playground/jupyter/.venv/lib/python3.12/site-packages/jupyter_server/gateway/gateway_client.py", line 828, in gateway_request
        cookie.load(cookie_values)
      File "/Users/kbates/.local/share/uv/python/cpython-3.12.11-macos-aarch64-none/lib/python3.12/http/cookies.py", line 516, in load
        self.__parse_string(rawdata)
      File "/Users/kbates/.local/share/uv/python/cpython-3.12.11-macos-aarch64-none/lib/python3.12/http/cookies.py", line 580, in __parse_string
        self.__set(key, rval, cval)
      File "/Users/kbates/.local/share/uv/python/cpython-3.12.11-macos-aarch64-none/lib/python3.12/http/cookies.py", line 472, in __set
        M.set(key, real_value, coded_value)
      File "/Users/kbates/.local/share/uv/python/cpython-3.12.11-macos-aarch64-none/lib/python3.12/http/cookies.py", line 335, in set
        raise CookieError('Illegal key %r' % (key,))
    http.cookies.CookieError: Illegal key 'HttpOnly,SERVERSESSION'
[E 2025-08-25 17:13:31.238 ServerApp] {
      "Host": "localhost:8888",
      "Accept": "*/*",
      "Referer": "http://localhost:8888/lab",
      "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
    }

Here's an example of the string returned from response.headers.get("Set-Cookie"):

SERVERSESSION=016723f57acc1447927bd43584d16dd6|12345678; Max-Age=172800; Path=/; Secure; HttpOnly,SERVERSESSION=a46923feb0ab14538230061651140472|12345678; Max-Age=172800; Path=/; Secure; HttpOnly,SERVERSESSION=4826c0e5c56a89c84c3906ba6ffd3abe|12345678; Max-Age=172800; Path=/; Secure; HttpOnly,username-ce-gateway-my-server=2|1:0|10:1756250589|35:username-gateway-server|144:123abc789|87654321; expires=Thu, 25 Sep 2025 23:23:09 GMT; HttpOnly; Path=/

Expected behavior

GatewayClient should record each cookie in its state.

Context

  • Operating System and version: Linux, Kuberenetes
  • Browser and version: n/a
  • Jupyter Server version: Latest
Troubleshoot Output
Paste the output from running `jupyter troubleshoot` from the command line here.
You may want to sanitize the paths in the output.
Command Line Output
Paste the output from your command line running `jupyter lab` here, use `--debug` if possible.
Browser Output
Paste the output from your browser Javascript console here, if applicable.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions