Cloudflare Workers & WebSockets. High speed, realtime messaging at the edge.

Mar 10, 2021

Hey, my name is Cerulean and I am the developer behind cfw-easy-utils. The library that makes developing for Workers an absolute breeze. This post is all about WebSockets and how to use them on Cloudflare's edge platform "Workers".

Workers has only recently added support for WebSockets and so there are many questions such as how to use them, what limits to watch out for, and what products can use WebSockets. Workers is based on the ServiceWorker framework, which doesn't specify a standard implementation, so the CF Workers team had to make their own. As this is a largely undocumented API, this post will go into detail on how you can make your own WebSocket server at the edge.

While you can do all of this on your own, I would highly recommend using a library such as cfw-easy-utils that handles all of this for you. It has a lot of helper functions designed to make writing Workers far easier. It now has support for both WebSocket clients and servers while keeping developer-side code easy to read.

WebSocket clients

This one is actually undocumented on Workers however it is avalible to everyone on the platform. Below is a full example of how it works, I will break it down in the next section but it should be self explanatory:

import { Websocket } from 'cfw-easy-utils'

var socket = new Websocket('wss://yourdomain.com')

socket.on('message', (msg) => {
    // handle an incoming message
})
cfw-easy-utils makes creating Websockets as easy as standard JavaScript.

If you cant use Webpack or NPM modules (which makes life so much easier), you can use this code that is 100% dependant-free:

var resp = await fetch('https://yourdomain.com', { headers: { 'upgrade': 'websocket' } })

var socket = resp.webSocket

socket.accept()

socket.addEventListener('message', (evt) => {
    var msg = evt.data
})
Full code example

Explaining what just happened.

var resp = await fetch('https://yourdomain.com', { headers: { 'upgrade': 'websocket' } })

To get started, we need to fetch a url that hosts a WebSocket. This is because WebSockets start out as normal HTTP connections but get "upgraded" to WebSockets by the browser. We need to indicate to Cloudflare that we want to upgrade the connection to a WebSocket by setting the header "upgrade" to "websocket". This will automagically set all of the required headers.

We must use a http or https protocol (the start of the uri) as Cloudflare will kill any connection not using one of these protocols.

Once the handshake has been completed, we will be given a Response object with a new attribute called "webSocket". We then must call .accept() on this object before it can be used. Once it has been activated, you can do all of the normal things you want to do with a standard WebSocket. For example, if you wanted to listen for events, you would need to add an event listener.


WebSocket servers

Sending data to the end user in realtime is not too complex on Cloudflare Workers however there is a bit of leg work required.

var pair = new WebSocketPair()

pair[1].accept()

pair[1].addEventListener('message', (event) => {
    var msg = event.data // event.data is just a String.
    pair[1].send(msg) // send back the data we got.
})

return new Response(null, { status: 101, webSocket: pair[0] })
How to create a Websocket server without external libraries
import { response, WebsocketResponse } from 'cfw-easy-utils'

var server = new WebsocketResponse()

server.on('message', (msg) => {
    // If the message is JSON-encoded, cfw-eu will try to parse it for you.
    // cfw-eu supports sending raw objects straight to the client.
    server.send({ echoMessage: msg })
})

return response.websocket(server)
cfw-easy-utils makes this process far easier to read.

Explaining the code sample

First we must create a "WebsocketPair", this is an array of 2 basic WebSocket objects. The first one you must pass to the end user in the response but the second is your end of the WebSocket connection. Just like in the client, you must accept your end of the connection before you can use it. Same as with the client, you can then listen for events such as "message".


Ok, awesome! But what are the limits?

WebSocket responses add a bit of weirdness into how you would normally execute a Worker. They actually remove the limits for timers, for example setInterval and setTimeout both work as expected. new Date() also returns the current time as it only ticks over when I/O is happening. Since you are constantly waiting for the next message, the time is unlocked until you do something (such as processing the message you just got).

However there are other limits that need to be watched for. The 50 subrequest limit is not lifted. If your Worker does more than 50 requests, your connection will be terminated. The client's connection counts as a subrequest however messages do not count towards this 50 request limit. The CPU time limit is also kept. This means if you process a lot of data in the Worker before sending it down the pipe, it will eat into the CPU time allowed. If you are just doing a simple WebSocket to WebSocket proxy, the CPU time used to translate shouldnt be too high but it is still something to be aware of. This makes reconnecting WebSockets more useful.

🦄 cfw-easy-utils

🐦 My Twitter

Cerulean

CEO & Full-stack engineer @ Sponsus. 4 years industry experience with a speciality in realtime and data analytics.