hono icon indicating copy to clipboard operation
hono copied to clipboard

Deno `upgradeWebSocket` missing `Sec-WebSocket-Protocol` header in response

Open NathanFlurry opened this issue 3 months ago • 1 comments

What version of Hono are you using?

4.9.8

What runtime/platform is your app running on? (with version if possible)

Deno 2.5.2

What steps can reproduce the bug?

  1. Create a WebSocket server using Deno's upgradeWebSocket
  2. Open a WebSocket with a protocol (e.g. new WebSocket("...", ["foobar"]))
  3. Attempt to connect to the WebSocket from Chrome

What is the expected behavior?

The WebSocket should work

What do you see instead?

Chrome gives a nondescript WebSocket error and disconnects immediately

Additional information

This seems to be specific to Deno. The root issue is that Sec-WebSocket-Protocol is not being set in the response.

Firefox does not return an error, this issue is specific to Chrome.

Here is the output comparing the Node upgradeWebSocket and the Deno upgradeWebSocket:

=== NODE===

> GET /connect/websocket HTTP/1.1
> Host: localhost:6420
> Accept: */*
> Upgrade: websocket
> Origin: http://localhost:5173
> Cache-Control: no-cache
> Accept-Language: en-US,en;q=0.9
> Pragma: no-cache
> Connection: Upgrade
> Sec-WebSocket-Key: sixpk706F01Oc4x9UXezjA==
> User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
> Sec-WebSocket-Version: 13
> Sec-WebSocket-Protocol: rivet, rivet_target.actor, rivet_actor.19c4f9038947cdcb, rivet_encoding.bare
> Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
>
* Request completely sent off
< HTTP/1.1 101 Switching Protocols
< Upgrade: websocket
< Connection: Upgrade
< Sec-WebSocket-Accept: OHBDEZ0/KVHZeTpQcokFETWongc=
< Sec-WebSocket-Protocol: rivet
<


=== DENO ===

> GET /connect/websocket HTTP/1.1
> Host: localhost:8080
> Accept: */*
> Upgrade: websocket
> Origin: http://localhost:5173
> Cache-Control: no-cache
> Accept-Language: en-US,en;q=0.9
> Pragma: no-cache
> Connection: Upgrade
> Sec-WebSocket-Key: sixpk706F01Oc4x9UXezjA==
> User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
> Sec-WebSocket-Version: 13
> Sec-WebSocket-Protocol: rivet, rivet_target.actor, rivet_actor.19c4f9038947cdcb, rivet_encoding.bare
> Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
>
* Request completely sent off
< HTTP/1.1 101 Switching Protocols
< connection: Upgrade
< sec-websocket-accept: OHBDEZ0/KVHZeTpQcokFETWongc=
< upgrade: websocket
< access-control-allow-credentials: true
< access-control-allow-origin: http://localhost:5173
< vary: Origin
< date: Fri, 03 Oct 2025 18:55:06 GMT
<

We're using this middleware to workaround this issue for now:

	router.use(
		"*",
		createMiddleware(async (c, next) => {
			const upgrade = c.req.header("upgrade");
			const isWebSocket = upgrade?.toLowerCase() === "websocket";
			const isGet = c.req.method === "GET";

			if (isGet && isWebSocket) {
				c.header("Sec-WebSocket-Protocol", "YOUR PROTOCOL");
			}

			await next();
		}),
	);

I have not investigated other WebSocket drivers. We implement a similar fix to above in Cloudflare Workers, so I suspect Deno is not the only driver affected.

NathanFlurry avatar Oct 03 '25 20:10 NathanFlurry

Hi @NathanFlurry

Is this Hono-specific problem? Can you try it without Hono by referring to the link?

https://docs.deno.com/examples/http_server_websocket/

I tried it quickly, but the behavior is the same for both with Hono and without Hono.

yusukebe avatar Oct 08 '25 07:10 yusukebe