Skip to content

fix(server): don't 404 non-websocket upgrade probes like h2c - #410

Open
dennisoelkers wants to merge 4 commits into
CopilotKit:mainfrom
dennisoelkers:fix/ollama-tags-h2c-upgrade-probe
Open

fix(server): don't 404 non-websocket upgrade probes like h2c#410
dennisoelkers wants to merge 4 commits into
CopilotKit:mainfrom
dennisoelkers:fix/ollama-tags-h2c-upgrade-probe

Conversation

@dennisoelkers

@dennisoelkers dennisoelkers commented Sep 3, 2026

Copy link
Copy Markdown

Node fires the "upgrade" event (not "request") for any request with a Connection: Upgrade header, regardless of the target protocol. langchain4j speculatively sends Upgrade: h2c on plain requests to probe for HTTP/2 cleartext, which made GET /api/tags and POST /api/chat (for ollama) land in the WebSocket-only upgrade handler and get an unconditional 404.

Only treat the request as a WebSocket upgrade when Upgrade: websocket is actually present; otherwise fall through to the normal HTTP pipeline so routes like /api/tags still work.

Node fires the "upgrade" event (not "request") for any request with a
Connection: Upgrade header, regardless of the target protocol. OkHttp
(used by langchain4j) speculatively sends Upgrade: h2c on plain
requests to probe for HTTP/2 cleartext, which made GET /api/tags land
in the WebSocket-only upgrade handler and get an unconditional 404.

Only treat the request as a WebSocket upgrade when Upgrade: websocket
is actually present; otherwise fall through to the normal HTTP
pipeline so routes like /api/tags still work.
Node fires the "upgrade" event (not "request") for any request with a
Connection: Upgrade header, regardless of the target protocol. OkHttp
(used by langchain4j) speculatively sends Upgrade: h2c on plain
requests to probe for HTTP/2 cleartext, which made GET /api/tags land
in the WebSocket-only upgrade handler and get an unconditional 404.

Only treat the request as a WebSocket upgrade when Upgrade: websocket
is actually present; otherwise fall through to the normal HTTP
pipeline so routes like /api/tags still work.
The earlier h2c-probe fix (rebuilding a ServerResponse via assignSocket)
only worked for bodyless requests like GET /api/tags. Node detaches its
HTTP parser from the socket the moment "upgrade" fires, so any body
bytes land in the `head` buffer instead of `req`'s stream; unshifting
them back onto the socket never reconnects them to `req`, so
readBody(req) resolved empty. A POST like /api/chat then failed JSON
parsing ("Unexpected end of JSON input") instead of surfacing the real
validation error for the request that was actually sent.

Rebuild the request line and headers with Upgrade/Connection dropped,
replay them plus `head` on the socket, and let Node re-parse the
connection from scratch via server.emit("connection", socket). This
reuses Node's own parser for the body (Content-Length or chunked)
instead of hand-rolling body buffering.
…-upgrade-probe

# Conflicts:
#	src/__tests__/ollama.test.ts
#	src/server.ts
@dennisoelkers
dennisoelkers marked this pull request as draft September 3, 2026 12:50
@dennisoelkers
dennisoelkers marked this pull request as ready for review September 3, 2026 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant