Vercel and WebSockets
Vercel makes it easy to deploy and scale HTTP APIs using Serverless Functions. However, it’s not possible to use serverless functions to host WebSocket APIs. Does this mean you need to give up on your hopes and dreams and set up normal servers to handle your WebSockets? No way! Vercel can be combined with Fanout Cloud to handle WebSocket connections without having to literally host them.
By using Vercel and Fanout Cloud together, API logic can be kept in one place. Fanout Cloud can manage the WebSocket connections, and invoke functions whenever there is client activity. What’s great about this approach is there’s nothing extra to deploy, and it works at high scale too.
Example
Below is some code for a simple WebSocket chat API. Messages received from clients are broadcasted to all other connected clients. It uses the serve-grip library, which provides a pseudo-WebSocket connection object. “GRIP” is the name of the integration protocol Fanout uses with the backend server.
const { ServeGrip } = require( '@fanoutio/serve-grip' );
const { WebSocketMessageFormat } = require( '@fanoutio/grip' );
const serveGrip = new ServeGrip({grip: process.env.GRIP_URL});
module.exports = async (req, res) => {
await serveGrip.run(req, res);
const { wsContext } = req.grip;
if (wsContext == null) {
res.statusCode = 400;
res.end('Not a WebSocket-over-HTTP request\n');
return;
}
// if this is a new connection, accept it and subscribe it to a channel
if (wsContext.isOpening()) {
wsContext.accept();
wsContext.subscribe('all');
}
while (wsContext.canRecv()) {
const message = wsContext.recv();
if (message == null) {
// if return value is undefined then connection is closed
wsContext.close();
break;
}
// broadcast to other connections
const publisher = serveGrip.getPublisher();
await publisher.publishFormats(
'all',
new WebSocketMessageFormat(message)
);
}
res.end();
};
For demonstration purposes, the above code has been deployed and set up as follows:
- The code has been deployed to Vercel with base URL
https://vercel-websocket.vercel.app
. - The Fanout domain
bce4b2a0.fanoutcdn.com
has been set up withvercel-websocket.vercel.app:443
as its origin server. - The
GRIP_URL
environment variable has been set on the Vercel app, containing Fanout credentials.
It is possible to connect with a WebSocket client and send a message:
$ wscat -c wss://bce4b2a0.fanoutcdn.com/api/chat
connected (press CTRL+C to quit)
> hi
< hi
How it works
Fanout Cloud acts as a proxy between clients and an origin server, with some important abilities:
- WebSocket client traffic is converted into a series of HTTP requests sent to the origin server. This makes it possible for a plain HTTP backend to react to WebSocket traffic.
- The origin server can associate publish-subscribe channels with client connections, and then publish raw WebSocket messages to be injected into those client connections.
The serve-grip library provides a socket-like object called WebSocketContext
that handles the event marshalling over HTTP. The object contains methods like accept()
, send()
, recv()
, etc. What’s interesting is that these methods don’t operate directly on a real WebSocket. When recv()
is called, it simply iterates over the events received in the current HTTP request. When send()
is called, events are temporarily enqueued and a middleware serializes them at the end into the HTTP response. WebSocketContext
objects are not long-lived, and a fresh one is created for each handler invocation and destroyed afterwards.
The library also provides ways to subscribe connections to channels as well as to publish to channels. Notably, clients have no awareness of GRIP or channels. They just connect to WebSocket URLs and exchange arbitrary messages, the meaning of which is determined by your application.
For protocol details, see Generic Realtime Intermediary Protocol and WebSocket-over-HTTP.
Serverless WebSockets, today
Sign up for a free Fanout Cloud account and get a WebSocket API up and running in no time.
Recent posts
-
We've been acquired by Fastly
-
A cloud-native platform for push APIs
-
Rewriting Pushpin's connection manager in Rust
-
Let's Encrypt for custom domains