Scottish Technology Club Logo Scottish Technology Club

LinkedIn · Bluesky · X · Community Discord

Realtime with Cloudflare

Samuel Macleod
22nd October 2024 · via EdinburghJS

Transcript

I’m Samuel. I work at Cloudflare, as it says very hopefully there. I work on the Workers Developer Platform on the authoring experience. And today I’m going to be talking about real-time applications, as you can see on the screen.

So what do I mean when I say real-time? So real-time applications are really about presence. So when you go to a normal web application, like a newspaper or something like that, kind of a more traditional one, you load the newspaper, you see the web page, but nothing changes after that. A real-time application, you get changes after that. So when you go to a messenger application, if you go to WhatsApp,(…) you see your friends come online, offline. You see messages come in. So you see things happen as you’re interacting with the application, without buttons, without having to refresh the screen, that type of thing. And so this kind of becomes problematic when you compare it to traditional web applications and how we implement it.

So a normal web application, like a newspaper,(…) the client always initiates every interaction. So the client says to the server, I want a newspaper article, I want a newspaper article, and the server says, OK, sure, here you go. And then the client loads it and shows it to the user. And that works fine. And when you navigate, you get the next article, et cetera, et cetera.

But with something like a real-time application, where your friends are coming on and offline for a messaging application, you’re having messages coming in, the server is the one that knows when there’s new information. It’s not the client. The client wants to know when there’s a new message coming in, but it doesn’t know when that happens. And so we need some way for the server to tell the client things. And there are a couple different ways to do that.

The kind of most naive way to do that is to just infinitely load the page and have data come in slowly, drip by drip as it comes.(…) You’ll end up with a browser continuously loading as you have the page open. This does work. There are various demos out there of how you got this working in 20 years ago on the web. And with newer frameworks, like I think solid and quick, you’re actually seeing that coming into the form more with hydration and that type of thing. But it’s not super efficient.

So there’s kind of two main methods. Long polling is the first one, where the client opens a request to the server, and the server just kind of hangs. It doesn’t resolve that request until it has data to give to the client. And then once the server says, OK, I’ve got some data, the client opens another request. The server waits till it has new data and gives it back to the client. So there’s always a pending request for the server to resolve with new data, which kind of resolves the issue.

But a newer method is WebSockets. I say newer. It’s been around in the web platform for 5, 10 years. It’s pretty widespread. I think 96% of browsers or something like that support it. So it’s pretty safe to use. And this opens a properly bidirectional channel between the client and the server, which makes this much easier.

So with that, I’m going to try and do some live coding, which will inevitably go terribly wrong. And so I deeply apologize in advance. So I have this silly little app here that lets you do some pixel art drawing, which you can hopefully see. I realize that’s quite small. You can change colors, et cetera. I’m going to assume you can see those little dots. Now, two problems with this. One is if I reload the page, all my wonderful artistic drawings go away. And two, if I open this app in a different tab,(…) different browser, different phone, I don’t get to see what I’ve drawn. So we have a very, very simple— ignore the commented code just in case I get this wrong—(…) we have a very, very simple implementation of how this is going to work in the browser.

So this is how it works today with just the locally working. So you have Draw Events, a class that takes a constructor on Draw Event and Draw. So the ways that this is working is that when the app wants to send a Draw Event, when the user has clicked to draw something, it calls the Draw Method. And then when this state class wants to tell the app, OK, draw something on the square, it calls this on Draw Event constructor method. And so you can see this working fairly simply here, where the app says, I want to draw on this square. And the state says, OK, draw on that square, which is not super useful when you’re just talking about this local state. This is pretty much a no-op. But we can try and make this a bit more interesting with WebSockets.

So I’m going to add a WebSocket, not WebAssembly. And I’m going to connect it in the constructor. (Typing) So this is just going to create a new instance of the WebSocket class,(…) connect it to this handily— oh, dear— handily running WebSocket server, which we’ll get to in a second, and just assign it. So this isn’t going to do anything right now.(…) So the first thing we want to do is listen to messages on the WebSocket, so any messages coming from the server.(…) And this is a fairly familiar API if you’re familiar with the web platform. So each message is going to have a data property, and also a bunch of other things, which you can look into at depth if you want to. A data property, which has the actual data of the message. The other properties are things about how the message was transmitted and that type of thing.(…) We’re going to assume that the server is being nice and sending us lovely JSON. (…) In a real application, you probably want to validate this slightly more. Otherwise, this will blow up. And we can. So if the server is sending us something like addEvent, then we can call the draw method. So we’re saying here that we assume the server is going to send us events with some type, with the color and the position of where that’s been drawn on a different tab. This isn’t going to do anything right now, because we’re not sending any events to the server. But if we now send events to the server in the draw method, we should be able to get sync and going.

So now in the draw method, when the application calls this draw method, we’re going to send to the server saying, this client has decided to draw. And also, I want to draw on my own canvas.(…) And then whenever the server sends us a message saying, there has been a draw event somewhere, then we say, OK, I want to draw that in the canvas too. Now, this should ideally mean that we have syncing between multiple tabs. So if I reload these two tabs and start drawing, then hopefully with any luck, excellent. It does, in fact, sync between tabs.

First time.

And you can see that works from either tab. Not all that pretty, but it gets the job done.

This still has a slight problem in that if you reload the page, everything will be cleared. We will get to the server code, but we’re going to make an assumption that the server is going to send us an event when it starts up with all of the kind of draw events that have happened on the canvas so far. (Typing) So ideally, this should say if there’s an event with type all,(…) then we’re just going to iterate through all the events in that event and draw them on the canvas. And excellent. It has, in fact, worked with all of the testing data from earlier today. But you can see it still syncs. And then if I reload the page, then I can still see that persisted data. Ta-da.

(Applause)

So it’s really kind of as simple as that. You don’t really need all that much code just to start up a web socket connection. Start up a web socket connection,(…) send data, listen to it, and kind of have that real time syncing across tabs, across devices, et cetera. This is not deployed, but if it was deployed, then if you could go to this and also draw on this pixel art canvas. Now, the server code for this is obviously slightly more complicated, but it’s not really all that much more complicated.(…) So for this, I’m going to use something called durable objects.

(…) Hands up if you’ve heard of durable objects. I will take that as a win. Usually, it’s no one. So durable objects are a—(…) actually, hands up if you’ve heard of Cloudflare workers. Much better. OK. So Cloudflare workers are— is Cloudflare’s serverless development platform. And durable objects are a kind of stateful compute primitive. And basically, it’s a JavaScript class in the cloud. So you can run compute, and you can also have state there. I’m not going to turn this into an advert for Cloudflare. You can do this with any other platforms, but this is what I’m most familiar with. So we’ll go with that for now. So basically, what you do with a durable object is you get a fetch request. Your fetch function is called whenever a request comes in. And so in this case, the request is the web socket connection be initiated. And we are going to ignore all this. So we’re going to start a web socket connection.

(Typing) So we’re going to start a new web socket connection.(…) And we are going to turn it into— we’ll leave it server client or client server. So the requests come in. We’re going to get this client and server connection parts of a web socket. And then we’re going to send the client part back to the user. We’re going to keep the server part around ourselves to do stuff with. So let’s send the client part back. So the status code for resolving a web socket— you know, connecting to a web socket is 101. And there is a slightly non-standard extension in Cloudflare to send back an actual web socket instance. This varies depending on what server platform you’re using. Node has its own stuff— et cetera. So just go look at the docs to figure out how that works. And then we are going to do stuff with server.

So at this point, we have sent back the client web socket connection to the client. So the client is connected. And at this point, we can send events to the client using the server. So we have a .send method. So the initial message we want to send is that all type thing that we had, all of the events. And so let’s just store the events somewhere. Not there. (Inaudible) Where you get these events from is obviously going to depend on the application. In this case, I’m just going to get it from Cloudflare’s storage. But that will purely be an implementation detail.

(Inaudible) I’m just going to call events two in case I have a bunch of test data in there. So that should, in theory, work to send the initial message. Now, what we’re also going to need to do is send the rest of the messages as they come through. So we need to accept the web socket connection. (Click)

So we need to persist the server web socket connection so we can keep it around and do stuff with it. And then once we’ve done this, we can have this web socket message callback, which will be called every time a web socket message comes in. And we can do stuff with it, send back responses, et cetera. For this, because I’m conscious of time, I’m just going to copy a snippet of code, which will hopefully mean that this actually does work. And then talk through it. So what we’re going to do here is get the data from the web socket message. Again, we’re assuming the client is being nice and sending us a good message. If it isn’t, this will blow up.(…) We’re going to get the events from our data store. And if the client has said, hey, I’ve got a new event, then we’re going to store it in our data store.(…) And we are going to— for every connected web socket, we’re going to send it as a new event. So whenever one client sends a message up saying, I’ve got a draw event, it will send it out to every other client saying, there has been a draw event somewhere. And so in theory, this should continue to work.

If I reload with both of these. Excellent. It has not. Very good. (Inaudible) That’s a very good point. Very good. We’ll get there. Right.(…) You should read the error messages from your server. There we go. (Audio Out)

Amazing. That is now working, I think. Yes? Very good. I can barely see on that screen, but it seems to be working on the screen. Cool. So that is not very much code. That is about 20, 30, 40 lines of code. And we have two tabs synced. It will work across device, et cetera. So yeah, that’s kind of it. It’s really simple as that. And it’s very, very easy to add real time to the applications. It does make the experience a lot better for users. And we’ll try it. That’s kind of where I’m at.

(Applause)