Skip to content

Support Deno.upgradeWebSocket with node:http upgrade events #33220

@bartlomieju

Description

@bartlomieju

Summary

Deno.upgradeWebSocket() does not work with requests originating from node:http server upgrade events. This makes it impossible to handle WebSocket upgrades in frameworks that use Node's http.Server under the hood (e.g. Vite's dev server).

Context

Fresh (Deno's web framework) uses Vite for its dev server. Vite creates a Node.js http.Server internally. When a WebSocket upgrade request comes in, Vite fires the upgrade event on the http.Server — but this never reaches Connect middleware, and there's no way to bridge it to Deno.upgradeWebSocket().

The user's Fresh handler code looks like:

export const handler = {
  GET(ctx: FreshContext) {
    const { socket, response } = Deno.upgradeWebSocket(ctx.req);
    socket.addEventListener("message", (event) => {
      socket.send(`echo: ${event.data}`);
    });
    return response;
  },
};

This works in production (via Deno.serve()) but fails in dev because the request comes through node:http.

Current behavior

Calling Deno.upgradeWebSocket() with a request from a node:http upgrade event fails with upgrade unavailable. Even constructing a new Request() manually and passing it to Deno.upgradeWebSocket() returns a 101 response and WebSocket object, but the WebSocket never connects (readyState stays at 0) because there's no underlying transport.

Expected behavior

Deno.upgradeWebSocket() should be able to work with WebSocket upgrade requests from node:http servers, either by:

  • Supporting binding the returned WebSocket to the raw net.Socket from the upgrade event
  • Having Deno's node:http compat layer automatically bridge upgrade events to Deno's WebSocket infrastructure
  • Providing an API like Deno.upgradeWebSocket(req, { socket }) that accepts a Node socket

Impact

This affects any Deno project using a Node-based HTTP server that needs WebSocket support:

Current workarounds

The only working workaround is to spin up a second Deno.serve() on a random port and proxy upgrade requests to it via raw TCP socket forwarding (freshframework/fresh#3737), which is heavy-handed.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions