Code Monkey home page Code Monkey logo

workers-chat-demo's Introduction

Cloudflare Edge Chat Demo

This is a demo app written on Cloudflare Workers utilizing Durable Objects to implement real-time chat with stored history. This app runs 100% on Cloudflare's edge.

Try it here: https://edge-chat-demo.cloudflareworkers.com

The reason this demo is remarkable is because it deals with state. Before Durable Objects, Workers were stateless, and state had to be stored elsewhere. State can mean storage, but it also means the ability to coordinate. In a chat room, when one user sends a message, the app must somehow route that message to other users, via connections that those other users already had open. These connections are state, and coordinating them in a stateless framework is hard if not impossible.

How does it work?

This chat app uses a Durable Object to control each chat room. Users connect to the object using WebSockets. Messages from one user are broadcast to all the other users. The chat history is also stored in durable storage, but this is only for history. Real-time messages are relayed directly from one user to others without going through the storage layer.

Additionally, this demo uses Durable Objects for a second purpose: Applying a rate limit to messages from any particular IP. Each IP is assigned a Durable Object that tracks recent request frequency, so that users who send too many messages can be temporarily blocked -- even across multiple chat rooms. Interestingly, these objects don't actually store any durable state at all, because they only care about very recent history, and it's not a big deal if a rate limiter randomly resets on occasion. So, these rate limiter objects are an example of a pure coordination object with no storage.

This chat app is only a few hundred lines of code. The deployment configuration is only a few lines. Yet, it will scale seamlessly to any number of chat rooms, limited only by Cloudflare's available resources. Of course, any individual chat room's scalability has a limit, since each object is single-threaded. But, that limit is far beyond what a human participant could keep up with anyway.

For more details, take a look at the code! It is well-commented.

Updates

This example was originally written using the WebSocket API, but has since been modified to use the WebSocket Hibernation API, which is exclusive to Durable Objects.

Prior to switching to the Hibernation API, WebSockets connected to a chatroom would keep the Durable Object pinned to memory even if they were just idling. This meant that a Durable Object with an open WebSocket connection would incur duration charges so long as the WebSocket connection stayed open. By switching to the WebSocket Hibernation API, the Workers Runtime will evict inactive Durable Object instances from memory, but still retain all WebSocket connections to the Durable Object. When the WebSockets become active again, the runtime will recreate the Durable Object and deliver events to the appropriate WebSocket event handler.

Switching to the WebSocket Hibernation API reduces duration billing from the lifetime of the WebSocket connection to the amount of time when JavaScript is actively executing.

Learn More

Deploy it yourself

If you haven't already, enable Durable Objects by visiting the Cloudflare dashboard and navigating to "Workers" and then "Durable Objects".

Then, make sure you have Wrangler, the official Workers CLI, installed. Version 3.30.1 or newer is recommended for running this example.

After installing it, run wrangler login to connect it to your Cloudflare account.

Once you've enabled Durable Objects on your account and have Wrangler installed and authenticated, you can deploy the app for the first time by running:

wrangler deploy

If you get an error saying "Cannot create binding for class [...] because it is not currently configured to implement durable objects", you need to update your version of Wrangler.

This command will deploy the app to your account under the name edge-chat-demo.

What are the dependencies?

This demo code does not have any dependencies, aside from Cloudflare Workers (for the server side, chat.mjs) and a modern web browser (for the client side, chat.html). Deploying the code requires Wrangler.

How to uninstall

Modify wrangler.toml to remove the durable_objects bindings and add a deleted_classes migration. The bottom of your wrangler.toml should look like:

[durable_objects]
bindings = [
]

# Indicate that you want the ChatRoom and RateLimiter classes to be callable as Durable Objects.
[[migrations]]
tag = "v1" # Should be unique for each entry
new_classes = ["ChatRoom", "RateLimiter"]

[[migrations]]
tag = "v2"
deleted_classes = ["ChatRoom", "RateLimiter"]

Then run wrangler deploy, which will delete the Durable Objects and all data stored in them. To remove the Worker, go to dash.cloudflare.com and navigate to Workers -> Overview -> edge-chat-demo -> Manage Service -> Delete (bottom of page)

workers-chat-demo's People

Contributors

a-robinson avatar andrebruch avatar jsonxr avatar kentonv avatar mappum avatar mellowyarker avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

workers-chat-demo's Issues

How do duration limits work with websockets?

One thing I find very confusing about Cloudflare Workers and WebSockets in the documentation is that it is not clear how sustaining a websocket makes sense when there are hard limits on the worker duration. After reading many pages of the docs, this is what I think the model is:

  • Workers, unlike traditional FaaS, are priced by CPU Times, which is different from Wall Clock Duration.
  • CPU Time is strictly when a line of code executes. Wall Clock Duration is CPU Time + time spent waiting on async calls when CPU is idle
  • Because of this, 30s of CPU time (the Workers limit), typically, actually maps to significantly longer Wall Clock Duration, making duration limits a non-issue to sustain a websocket connection since each message will take ~1ms (is this the right magnitude?), giving you basically ~30,000 messages per request on the Unbound plan, but because there's a higher chance of eviction after 30s of Wall Clock Duration, in practice you'll get lower than 30,000 messsages per request.

My questions:

  • The marketing page seems to contradict the docs on pricing. The landing page says 30s wall time per request, but everywhere I look in the documentation seems to suggest either 50ms of CPU time for the Bundled plan, and unlimited Wall Clock Duration for the Unbounded plan?
  • What exactly is the model for eviction? Can my Worker get evicted mid execution? Can my Durable Object Worker get evicted while idle but holding a bunch of open connections? (Is that why there needs to be logic to rejoin on the client side? Because the Durable Object can get killed at any point?)
    image

How/Where is the fetch method of the rate limiter called?

Hi, I see nowhere that it's called.
I assume that it's automatically called with every websocket data received. That's correct right?

I didn't read anywhere in the Durable Object docs about that. Can someone give me a link where I can read more about that behavior when exactly fetch requests (of DOs within DOs) are fetched? because in the code i don't see that it is anzwhere attached, so it happens automatically in the background. this is information i want/have to know if i want to develop something with DOs.

async fetch(request) {

Demo with Next.js framework in serverless mode, hosted on Vercel

Next.js is the uprising React framework, it looks like a great candidate for Cloudflare Durable Objects demo because it has native Serverless support.

Would you consider building a similar demo (chat) using the Next.js framework? I'm sure it'd be a great visibility boost for this feature, and might even become the de-facto way to build real-time apps with Next.js.

How to properly use KV storage along side this new persistent storage

Appreciate the project! One thing that would be really helpful is if this demo included a portion that demonstrated usage of standard KV storage. The normal method of using KV storage seems to not work in this project so I'm assuming workarounds need to done or is that something that will be supported laster in this new way of using workers. If this was addressed in this demo it would be helpful!

console.log() in webSocketMessage()

Using:
npx wrangler deploy --env staging,
then:
npx wrangler tail --format=pretty,
i cannot see the the logs in the hibernating websocket methods.
Note: webSocket.send() is working fine.
Example:

async webSocketMessage(webSocket, message){
   console.log("message received"); //<-does not show in wrangler
   webSocket.send(JSON.stringify({ echo:message, })); //<- working fine
}

however, i can see the logs if i use the old way:

webSocket.addEventListener("message", async (evt) => { 
  console.log("message received"); //<-does show in wrangler
  webSocket.send(JSON.stringify({ echo:evt.data, })); //<- working fine
});

Perhaps i am doing something wrong?

Example pricing ?

Hi !

Would it be possible for somebody from Cloudflare @kentonv @a-robinson to confirm what would be the approximate pricing for such chat demo ?

For example if we had 1000 users each send about 50-60 messages per day for a month. I am very curious how well do Durable Object scale in terms of pricing versus traditional running VM.

Thank you !

Issue for "wrangler generate" when creating from GitHub project that has no master

Was attempting to use wrangler 1.15 rc2 to generate the project with an m1 MacBook where I modified the wrangler compile to use use the binary supplied for cargo-generate (fix described here: cloudflare/wrangler-legacy#1813 )

I try to following:
wrangler generate myproject-app https://github.com/cloudflare/workers-chat-demo

And I get the following error:
๐Ÿ”ง Creating project called delivery-websocket...
Error: โ›” Git Error: failed to find branch master
Error: tried running command:
/Users/username/.cargo/bin/cargo-generate generate --git https://github.com/cloudflare/workers-chat-demo --name delivery-websocket --force
exited with exit code: 1

Just to make it smoother for new people, would the solution be to add a master branch?

Cannot Publish

After adding my own account ID, and running wrangler publish, I get:

Error: error decoding response body: missing field `result` at line 1 column 76

Caused by:
    missing field `result` at line 1 column 76

Consistency Violation

Clever, but I believe the durable object still runs into a consistency violation if the clock does go backwards because the object does not persist lastTimeStamp

I could be wrong though, distributed systems are hard ๐Ÿคฏ๐Ÿค“

// Add timestamp. Here's where this.lastTimestamp comes in -- if we receive a bunch of
// messages at the same time (or if the clock somehow goes backwards????), we'll assign
// them sequential timestamps, so at least the ordering is maintained.
data.timestamp = Math.max(Date.now(), this.lastTimestamp + 1);
this.lastTimestamp = data.timestamp;

Persisting WebSocket connections

First of all, thank you of a well-documented demo! It really helped me to understand the concept of Durable Objects, which I find one of the most amazing things that the Workers platform has.

The demo and the official documentation mention that a durable object can be destroyed and then recreated again, I assume in order to migrate the object closer to the clients and to make it easier to handle deploys. This perfectly explains why there are essentially two ways of storing data: in-memory and storage. The memory acts as a cache: we can keep intermediate data in there that can be lost. In order to ensure data safety we sync that data with the storage, which will guarantee the presence of the data between the lifecycles of the object.

In the chat demo however we store a list of all current WebSocket clients in an instance variable this.sessions. And that made me wonder: what happens with these WebSocket connections when an object is destroyed? Do they get disconnected? And if they are not is there a way to persist a WebSocket object in the storage (since they aren't plain JS objects)?

error: Found argument '--new-class' which wasn't expected, or isn't valid in this context

i want to try this on my acc

when try to publish with command:
wrangler publish --new-class ChatRoom --new-class RateLimiter

an error message appear
error: Found argument '--new-class' which wasn't expected, or isn't valid in this context

already using lates wrangler v.1.15.0

can not publish

warngler is login and config:

wrangler.toml
wrangler

any insight what to do?
thanx for the help.

Time for a revisit and updated code?

What are the changes of this sample being updated and tested with latest wrangler and typescript updates.
So much has happened, and it seem that this is the only sample still referenced on the current documentation.

In code (dog) years, this is about 40 years behind the time. It would be really useful.
Unless I'm missing other examples and references?

"dependency "./chat.html": no matching module rules" when running with miniflare

I'd like to use miniflare so I can use breakpoints to step through the code, as I believe this isn't supported in wrangler currently. When I run:

npm run miniflare --modules --watch --debug

I get the error:

VMScriptRunnerError [ERR_MODULE_RULE]: Unable to resolve "src/chat.mjs" dependency "./chat.html": no matching module rules. If you're trying to import an npm package, you'll need to bundle your Worker first.

Commenting out lines 62 and 104 in chat.mjs prevents the error but means no web client is available. Does anyone know a workaround for this issue, or a related ticket?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.