WebSockets
Functions accept WebSocket upgrades directly. Connection lifetime, per-Function ceilings, and keeping a client on one target.
A Function that runs its own HTTP server accepts WebSocket upgrades with no adapter and no separate service. Gigadrive Network terminates the client socket at the edge, opens a socket to your Function, and copies frames in both directions until one side closes.
Which Functions can accept one
An upgrade is recognised when the request carries Connection: upgrade and Upgrade: websocket, which covers socket.io's WebSocket transport as well as plain ws clients.
Your Function has to be server-style: something that creates an http.Server, such as Express, Fastify, NestJS or ws on top of node:http. The upgrade is tunnelled through, so your server performs the real handshake and sees the original request. A handler-style Function, meaning one that exports fetch or a (event, context) function, cannot hold a socket and refuses the upgrade with 426. PHP Functions are served through the same handler path and refuse it for the same reason.
Sec-WebSocket-Protocol is not forwarded, so subprotocol negotiation does not reach your server.
Lifetime
| Bound | Value |
|---|---|
| Connection lifetime | max_duration, capped at 4 hours |
| Idle close | 15 minutes with no bytes in either direction |
| Connections per copy | 250 per 256 MB of the Function's memory |
A connection is one invocation, so max duration is what closes it, and its 30 second default is far too short for a socket. Set it deliberately.
A Function holding open connections is never scaled to zero underneath them, even when no frames are flowing. The connection itself keeps the Function alive, so a chat room with idle members stays up. Once a copy is holding 50 connections per 256 MB of memory, another copy starts for new connections; existing ones stay where they are.
A refused connection gets an honest handshake status rather than a hanging socket, because admission runs before the upgrade:
| Status | When |
|---|---|
| 429 | The plan's Concurrency limit is fully in use |
| 503 | No capacity, or the organization is at its compute limit |
| 502 | The Function could not be reached |
| 504 | The Function timed out |
| 403 | The sticky session URL is invalid or expired |
Writing one
There is no platform API to call. You write an ordinary WebSocket server, declare the file as a Function, and point a route at it.
// api/socket.js
import http from 'node:http';
import { WebSocket, WebSocketServer } from 'ws';
const server = http.createServer((req, res) => {
res.writeHead(200, { 'content-type': 'text/plain' });
res.end('ok');
});
const sockets = new WebSocketServer({ server });
sockets.on('connection', (socket, request) => {
const room = new URL(request.url, 'http://localhost').searchParams.get('room') ?? 'lobby';
socket.send(JSON.stringify({ type: 'joined', room }));
socket.on('message', (data) => {
const payload = JSON.stringify({ type: 'message', room, text: data.toString() });
for (const peer of sockets.clients) {
if (peer !== socket && peer.readyState === WebSocket.OPEN) peer.send(payload);
}
});
});
server.listen(3000);# gigadrive.yaml
version: 4
functions:
api/socket.js:
runtime: node-22
memory: 512
max_duration: 3600
routes:
- source: ^/socket$
destination: /api/socket.jsBoth runtimes want a node:http server, whether your code creates it directly as in the examples or a framework creates it for you. That is the one shape the platform reads as server-style, and it is what ws attaches to.
The port is decoration. The platform captures the server your code creates while the entry file loads and gives it a loopback port of its own, so 3000 is never bound. ws is an ordinary dependency, traced from the entry file and packaged with the Function.
A bun-1 upgrade arrives without the client's headers
On bun-1 the upgrade is terminated in front of your server and dialled again from there, so the request your
connection handler receives keeps the path and the query string but loses the cookies, Authorization and
User-Agent the browser sent. A Node runtime replays the original request head verbatim and those headers survive.
Authenticate a Bun socket from the query string.
Connecting a client
The deployment's hostname is the one GIGADRIVE_URL carries, which System variables covers. Swap https for wss and open the route:
const socket = new WebSocket('wss://my-app-k3n8q2wp.gigadrive.app/socket?room=lobby');
socket.addEventListener('open', () => socket.send('hello'));
socket.addEventListener('message', (event) => console.log(event.data));
socket.addEventListener('close', (event) => console.log('closed', event.code));Once the handshake succeeds there is no status left to send, so every ending reaches the client as a close event: your own server closing, the duration ceiling, or the idle timeout. Handle it and reconnect. A protocol that can fall silent for 15 minutes needs a heartbeat message of its own, because the idle timer counts bytes rather than application activity.
Keeping a client on one copy
Each connection is admitted independently, so two sockets from the same browser can land on different copies of a Function. When your application keeps per-connection state in memory, such as the members of a room, pin them: Sticky sessions covers how a Function mints a signed URL that sends every connection sharing a key to one copy, and what that pin does and does not guarantee.
