PartyKit vs WebSocket server with WebSocket Hibernation

Totally new to setting up a websocket server / client and have a general architecture question. Wondering how to best implement a web socket server and if it should still be behind my API gateway. The gateway is built using Kono. 1. Is it possible to proxy the initial call so that it does the 101 Upgrade for websockets in the gateway and then calls the downstream websocket server using RPC? 2. I am using partykit with currently with just the generic import { WebSocket } from "partysocket"; this works fine directly to a Websocket server direct on the worker but I have issues pushing this through the gateway to do the upgrade and everything. Here is the excalidraw https://link.excalidraw.com/readonly/rMHltKc25dPL0AQFvkGf
No description
1 Reply
Alex Patterson
Alex PattersonOP4d ago
Maybe another way of looking at this, in this example from the docs https://developers.cloudflare.com/durable-objects/examples/websocket-hibernation-server/
import { DurableObject } from "cloudflare:workers";

// Worker
export default {
async fetch(request, env, ctx) {
if (request.url.endsWith("/websocket")) {
// Expect to receive a WebSocket Upgrade request.
// If there is one, accept the request and return a WebSocket Response.
const upgradeHeader = request.headers.get('Upgrade');
if (!upgradeHeader || upgradeHeader !== 'websocket') {
return new Response('Durable Object expected Upgrade: websocket', { status: 426 });
}

// This example will refer to the same Durable Object,
// since the name "foo" is hardcoded.
let id = env.WEBSOCKET_HIBERNATION_SERVER.idFromName("foo");
let stub = env.WEBSOCKET_HIBERNATION_SERVER.get(id);

return stub.fetch(request);
}

return new Response(null, {
status: 400,
statusText: 'Bad Request',
headers: {
'Content-Type': 'text/plain',
},
});
}
};

// Durable Object
export class WebSocketHibernationServer extends DurableObject {

async fetch(request) {
// Creates two ends of a WebSocket connection.
const webSocketPair = new WebSocketPair();
const [client, server] = Object.values(webSocketPair);

// Calling `acceptWebSocket()` informs the runtime that this WebSocket is to begin terminating
// request within the Durable Object. It has the effect of "accepting" the connection,
// and allowing the WebSocket to send and receive messages.
// Unlike `ws.accept()`, `state.acceptWebSocket(ws)` informs the Workers Runtime that the WebSocket
// is "hibernatable", so the runtime does not need to pin this Durable Object to memory while
// the connection is open. During periods of inactivity, the Durable Object can be evicted
// from memory, but the WebSocket connection will remain open. If at some later point the
// WebSocket receives a message, the runtime will recreate the Durable Object
// (run the `constructor`) and deliver the message to the appropriate handler.
this.ctx.acceptWebSocket(server);

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

async webSocketMessage(ws, message) {
// Upon receiving a message from the client, reply with the same message,
// but will prefix the message with "[Durable Object]: " and return the
// total number of connections.
ws.send(`[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`);
}

async webSocketClose(ws, code, reason, wasClean) {
// If the client closes the connection, the runtime will invoke the webSocketClose() handler.
ws.close(code, "Durable Object is closing WebSocket");
}
}
import { DurableObject } from "cloudflare:workers";

// Worker
export default {
async fetch(request, env, ctx) {
if (request.url.endsWith("/websocket")) {
// Expect to receive a WebSocket Upgrade request.
// If there is one, accept the request and return a WebSocket Response.
const upgradeHeader = request.headers.get('Upgrade');
if (!upgradeHeader || upgradeHeader !== 'websocket') {
return new Response('Durable Object expected Upgrade: websocket', { status: 426 });
}

// This example will refer to the same Durable Object,
// since the name "foo" is hardcoded.
let id = env.WEBSOCKET_HIBERNATION_SERVER.idFromName("foo");
let stub = env.WEBSOCKET_HIBERNATION_SERVER.get(id);

return stub.fetch(request);
}

return new Response(null, {
status: 400,
statusText: 'Bad Request',
headers: {
'Content-Type': 'text/plain',
},
});
}
};

// Durable Object
export class WebSocketHibernationServer extends DurableObject {

async fetch(request) {
// Creates two ends of a WebSocket connection.
const webSocketPair = new WebSocketPair();
const [client, server] = Object.values(webSocketPair);

// Calling `acceptWebSocket()` informs the runtime that this WebSocket is to begin terminating
// request within the Durable Object. It has the effect of "accepting" the connection,
// and allowing the WebSocket to send and receive messages.
// Unlike `ws.accept()`, `state.acceptWebSocket(ws)` informs the Workers Runtime that the WebSocket
// is "hibernatable", so the runtime does not need to pin this Durable Object to memory while
// the connection is open. During periods of inactivity, the Durable Object can be evicted
// from memory, but the WebSocket connection will remain open. If at some later point the
// WebSocket receives a message, the runtime will recreate the Durable Object
// (run the `constructor`) and deliver the message to the appropriate handler.
this.ctx.acceptWebSocket(server);

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

async webSocketMessage(ws, message) {
// Upon receiving a message from the client, reply with the same message,
// but will prefix the message with "[Durable Object]: " and return the
// total number of connections.
ws.send(`[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`);
}

async webSocketClose(ws, code, reason, wasClean) {
// If the client closes the connection, the runtime will invoke the webSocketClose() handler.
ws.close(code, "Durable Object is closing WebSocket");
}
}
Can I move this part to the Gateway, then just call an RPC?
// Worker
export default {
async fetch(request, env, ctx) {
if (request.url.endsWith("/websocket")) {
// Expect to receive a WebSocket Upgrade request.
// If there is one, accept the request and return a WebSocket Response.
const upgradeHeader = request.headers.get('Upgrade');
if (!upgradeHeader || upgradeHeader !== 'websocket') {
return new Response('Durable Object expected Upgrade: websocket', { status: 426 });
}

// This example will refer to the same Durable Object,
// since the name "foo" is hardcoded.
let id = env.WEBSOCKET_HIBERNATION_SERVER.idFromName("foo");
let stub = env.WEBSOCKET_HIBERNATION_SERVER.get(id);

return stub.fetch(request);
}

return new Response(null, {
status: 400,
statusText: 'Bad Request',
headers: {
'Content-Type': 'text/plain',
},
});
}
};
// Worker
export default {
async fetch(request, env, ctx) {
if (request.url.endsWith("/websocket")) {
// Expect to receive a WebSocket Upgrade request.
// If there is one, accept the request and return a WebSocket Response.
const upgradeHeader = request.headers.get('Upgrade');
if (!upgradeHeader || upgradeHeader !== 'websocket') {
return new Response('Durable Object expected Upgrade: websocket', { status: 426 });
}

// This example will refer to the same Durable Object,
// since the name "foo" is hardcoded.
let id = env.WEBSOCKET_HIBERNATION_SERVER.idFromName("foo");
let stub = env.WEBSOCKET_HIBERNATION_SERVER.get(id);

return stub.fetch(request);
}

return new Response(null, {
status: 400,
statusText: 'Bad Request',
headers: {
'Content-Type': 'text/plain',
},
});
}
};
Cloudflare Docs
Build a WebSocket server with WebSocket Hibernation · Cloudflare Du...
Build a WebSocket server using WebSocket Hibernation on Durable Objects and Workers.

Did you find this page helpful?