Introducing WebSockets Support in Cloudflare Workers

Introducing WebSockets Support in Cloudflare Workers

Introducing WebSockets Support in Cloudflare Workers

Today, we’re releasing support for WebSockets in Cloudflare Workers.

WebSockets unlock powerful use-cases in your serverless applications — live-updating, interactive experiences that bridge the gap between your users and Workers’ powerful network and runtime APIs.

In this blog post, we’ll walk you through the basics of using WebSockets with Workers. That being said, WebSockets on their own aren’t immediately useful — to power the interactivity, you need to coordinate a storage layer to go with them. With the addition of Durable Objects as a solution to building coordinated state on the edge, you can combine the power of interactive compute (WebSockets) with coordinated state (Durable Objects), and build incredible real-time applications. Durable Objects was released in open beta at the end of last month, and later in this blog post, we’ll explore how Durable Objects are well-suited towards building with WebSockets.

Getting started with WebSockets in Workers

WebSockets allow clients to open a connection back to a server (or in our case, Cloudflare Workers) that can receive and send messages. Instead of polling a server continuously for new information, a single WebSocket connection can constantly receive data and unlock the kind of live use-cases that we’re used to in modern applications: things like live scores for sports events, real-time chat, and more.

Let’s dig into how to use WebSockets in Workers so you can understand how easy they are to pick up and start using in your application.

Instantiating WebSocketPairs in Cloudflare Workers

Workers respond to HTTP requests sent from clients. A Request comes in, and a Response comes back. You can begin using WebSockets in your Workers code by looking for an Upgrade header in any incoming requests: this is an indication from the client that it’s looking to set up a new WebSocket:

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  const upgradeHeader = request.headers.get("Upgrade")
  if (upgradeHeader !== "websocket") {
    return new Response("Expected websocket", { status: 400 })
  }

  // Set up WebSocket
}

If the Upgrade header is present, we set up a new instance of WebSocketPair — a set of two WebSockets, one for the client, and one for the server. In our code, we’ll use the server WebSocket to set up our server-side logic, and return the client socket back to the client. Note the usage of the 101 status code (“Switching Protocols”) and the new webSocket property on Response:

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  const upgradeHeader = request.headers.get("Upgrade")
  if (upgradeHeader !== "websocket") {
    return new Response("Expected websocket", { status: 400 })
  }

  const [client, server] = Object.values(new WebSocketPair())
  await handleSession(server)

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

In our handleSession function, we call the accept function on our WebSocket. This tells the Workers runtime that it will be responsible for this server WebSocket from the WebSocketPair. We can then handle events on the WebSocket, by using addEventListener and providing callback functions, particularly for message events (when new data comes in), and close events, when the WebSocket connection closes:

async function handleSession(websocket) {
  websocket.accept()
  websocket.addEventListener("message", async message => {
    console.log(message)
  })

  websocket.addEventListener("close", async evt => {
    // Handle when a client closes the WebSocket connection
    console.log(evt)
  })
}

Connecting to WebSockets in a Browser Client

With WebSockets support added in the Workers function, we can now connect to it from a client. Workers’ WebSocket support works directly with the browser-default WebSocket class, meaning that you can connect to it directly in vanilla JavaScript without any additional libraries.

In fact, this connection process is so simple that it almost explains itself by just looking at the code. Just pass the WebSocket URL to a new instance of WebSocket, and then watch for new events on the WebSocket itself, like open (when the WebSocket connection opens), message (when new data comes in), and close (when the WebSocket connection closes):

let websocket = new WebSocket(url)
if (!websocket) {
  throw new Error("Server didn't accept WebSocket")
}

websocket.addEventListener("open", () => {
  console.log('Opened websocket')
})

websocket.addEventListener("message", message => {
  console.log(message)
})

websocket.addEventListener(“close”, message => {
  console.log(‘Closed websocket’)
})

websocket.addEventListener(“error”, message => {
  console.log(‘Something went wrong with the WebSocket’)

  // Potentially reconnect the WebSocket connection, by instantiating a
  // new WebSocket as seen above, and connecting new events
  // websocket = new WebSocket(url)
  // websocket.addEventListener(...)
})

// Close WebSocket connection at a later point
const closeConnection = () => websocket.close()

Durable Objects and WebSockets

WebSockets are a powerful addition to the Workers toolkit, but you’ll notice that in the above example, your WebSocket connections are effectively stateless. I can click the “Click me” button a hundred times, send data back and forth in my WebSocket connection, but as soon as I refresh the page, all of that information is gone. How do we provide state for our WebSocket and for our application in general?

Our solution for this is Durable Objects. Instead of using external state solutions and making requests to origin servers for a database or API provider, Durable Objects provide simple APIs for accessing and updating stateful data directly at the edge, right alongside your serverless functions.

Durable Objects complements WebSockets perfectly, providing the stateful primitives needed to make WebSockets at the edge uniquely powerful and performant. When we initially announced the Durable Objects private beta, we also previewed WebSockets in Workers for the first time, as part of our live chat demo. That demo, available here, still serves as a great example of a more complex application that can be built entirely on Workers, Durable Objects, and WebSockets.

Additional resources

With the release of WebSocket support in Workers, we’re providing two resources to help you get started.

First, a new websocket-template that will help you get started with WebSockets on Workers. The template includes a simple HTML page that shows you how to connect to a Workers-based WebSocket, send and receive messages, as well as how to close the connection and handle any unknown messages or data. This template is the logical extension of the code that I shared above, and could be extended for most use-cases with WebSockets in your application.

You can see a demo version of the project here.

Introducing WebSockets Support in Cloudflare Workers

We’ve also released documentation for WebSocket usage in Workers. This includes a new Learning page on working with WebSockets, as well as a collection of reference documentation for understanding the new WebSocketPair API, and changes to the Response class to allow WebSocket upgrade responses, as seen in the code above.

Pricing considerations

Today, WebSockets incur a request charge when the connection is first established, followed by the underlying Worker’s duration charge as long as the WebSocket is active. There is no per-message fee for WebSockets.

This means that if you create a Worker on the Workers Unbound plan and then pass-through a WebSocket connection to another server or to a Durable Object, you’ll be billed wall-clock duration for the entire time the connection is open. This may be surprising, since the Worker itself is not participating in the request.

This is currently due to a limitation in the platform, where the Worker passing the WebSocket through remains active for the duration of the WebSocket connection. We’re working to make it so that you will not be charged duration time for proxying a WebSocket in the near future.

Until we make this fix, if you want to use WebSockets with Durable Objects, we recommend using Workers Bundled rather than Unbound to pass the WebSocket connection to the Durable Object to avoid surprise charges. You should not use Workers Unbound on a Worker that passes on a WebSocket connection to Durable Objects.

Today, while in beta, Durable Objects are not being billed, so there is no cost for terminating the WebSocket connection in a Durable Object.

We’re currently working on the best way to price WebSockets that are terminated in a Durable Object.

Our current thinking is that when using WebSockets, you’ll be charged for wall clock time whenever a message is being processed by the Durable Object on any WebSocket connection – this charge would be shared across all WebSockets connected to a given Durable Object. When there is no CPU activity on a Durable Object, but any number of WebSocket connections remain established, you’ll be billed a much lower active connection charge, per second.

We want your feedback on how this pricing would affect your usage of Workers! Send the Workers Product team your thoughts on how we could improve your WebSockets experience, particularly on pricing.

Source:: CloudFlare