itty-durable: When trying to use websockets the connection hangs on trying to connect

I was attempting to use WebSockets and whenever using itty-durable the connection hangs on trying to connect. Below is a small little code sample of what I am using to test the WebSockets. When I use it outside the scope of itty-durable (just on a normal route) it works fine.

import { ThrowableRouter } from 'itty-router-extras'
import { createIttyDurable, withDurables } from 'itty-durable'

export class TestWebsocket extends createIttyDurable({ persistOnChange: false }) {
	constructor(state, env) {
		super(state, env)
	}

	websocket() {
		const [client, websocket] = Object.values(new WebSocketPair())

		websocket.accept()

		websocket.send(JSON.stringify({ connected: true }))

		websocket.addEventListener('message', ({ data }) => {
			websocket.send(JSON.stringify({ data }))
		})

		return new Response(null, { status: 101, webSocket: client })
	}
}

const router = ThrowableRouter()

router.all('*', withDurables())

// Doesnt work
router.get('/ws', ({ TestWebsocket }) => TestWebsocket.get('test').websocket())

// Works
router.get('/ws2', () => {
	const [client, websocket] = Object.values(new WebSocketPair())

	websocket.accept()

	websocket.send(JSON.stringify({ connected: true }))

	websocket.addEventListener('message', ({ data }) => {
		websocket.send(JSON.stringify({ data }))
	})

	return new Response(null, { status: 101, webSocket: client })
})

export default {
	fetch: router.handle
}

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Comments: 18 (7 by maintainers)

Most upvoted comments

Ok, so yesterday I successfully got itty-durable (modified version) to play nicely with, and return a websocket.

A few things to note:

  • Confirming: I stand corrected from the previous comment… I set up a simple test, and merely flipping the DO request to a GET (with adjustments to the internal itty-router to receive it and prevent the body from being attached) as opposed to a POST works. TLDR; GET requests to the DO appear to be required to respond with a socket.
  • I can also confirm that omitting the upgrade: websocket header from the DO request causes a failure. Original Worker request headers will now be passed along to the DO to remedy.
  • As a result, I’m switching up the internal router to only use GET (rather than adding dependent switching logic), attaching the content via a do-content header, rather than the body itself.
  • Downstream response/error handling code (catchErrors) within the proxy-durable had to be modified, as a 101 response from within the DO would trigger a !response.ok error.
  • The outside Worker code should NOT use the withDurables({ parse: true }) flag, allowing the Response from within the DO to be passed directly back to the original Worker request.

Would also like to contribute to make this happen, and before doing any work I’d like to summarize what (I think?) the challenges might be:

  • As mentioned above, the Upgrade: websocket header needs to be sent to the DO when making a proxy’d function fetch. In doing this, maybe it makes sense to forward all headers of the current request over to the DO? This is assuming users of itty-durable will write something like DurableObject.connect({ connectionData }), where connect is a user defined DO method that returns a Response with a webSocket property.
  • The proxy’d function fetch that you’d get from writing DurableObject.connect({ connectionData }), currently, is only ever a POST request. But, from what I can tell, WebSocket connections MUST be initiated with a GET request. It’s weird because on Miniflare (--local) a POST request works just fine, so long as I’ve got the header. On “non-local” dev mode with wrangler, using a POST request with this setup just crashes wrangler completely. This line in kj might have to do with it? Not sure, maybe I’m just not doing it right. Either way, it seems like it’d be good to use a GET request since that’s standard practice.

Going about implementing all this, forwarding all request headers through with the proxy’d function fetch request doesn’t seem too difficult IF that’s a sensible solution. Maybe forwarding all headers is a bad idea? I’m not sure.

With the POST vs GET issue, it seems like the proxy’d function fetches with POST are pretty ingrained in the system, so breaking out of that for WebSockets could be less pretty. Perhaps the proxyDurable function’s get handler could detect the Upgrade: websocket header, and thus perform a GET request instead of a POST? This wouldn’t support a request body though, which could instead be turned into URL params and be picked up by the itty-durable DO internal router.

Dug into this a bit and it would appear that there’s a header issue or maybe just a response issue where the webSocket: client isn’t being forwarded when responding. This is either before the pass back or actually in the pass in where the headers.Upgrade: 'websocket' isn’t being passed to the method call into the DO.