github.com/anycable/anycable-go@v1.5.1/docs/broadcasting.md (about) 1 # Broadcasting 2 3 Publishing messages from your application to connected clients (aka _broadcasting_) is an essential component of any real-time application. 4 5 AnyCable comes with multiple options on how to broadcast messages. We call them _broadcasters_. Currently, we support HTTP, Redis, and NATS-based broadcasters. 6 7 **NOTE:** The default broadcaster is Redis Pub/Sub for backward-compatibility reasons. This is going to change in v2. 8 9 ## HTTP 10 11 > Enable via `--broadcast_adapter=http` (or `ANYCABLE_BROADCAST_ADAPTER=http`). 12 13 HTTP broadcaster has zero-dependencies and, thus, allows you to quickly start using AnyCable, and it's good enough to keep using it at scale. 14 15 By default, HTTP broadcaster accepts publications as POST requests to the `/_broadcast` path of your server\*. The request body MUST contain the publication payload (see below to learn about [the format](#publication-format)). 16 17 Here is a basic cURL example: 18 19 ```bash 20 curl -X POST -H "Content-Type: application/json" -d '{"stream":"my_stream","data":"{\"text\":\"Hello, world!\"}"}' http://localhost:8090/_broadcast 21 ``` 22 23 \* If neither the broadcast key nor the application secret is specified, we configure HTTP broadcaster to use a different port by default (`:8090`) for security reasons. You can handle broadcast requests at the main AnyCable port by specifying it explicitly (via the `http_broadcast_port` option). If the broadcast key is specified or explicitly set to "none" or auto-generated from the application secret (see below), we run it on the main port. You will see the notice in the startup logs telling you how the HTTP broadcaster endpoint was configured: 24 25 ```sh 26 2024-03-06 10:35:39.297 INF Accept broadcast requests at http://localhost:8090/_broadcast (no authorization) nodeid=uE3mZ7 context=broadcast provider=http 27 28 # OR 29 2024-03-06 10:35:39.297 INF Accept broadcast requests at http://localhost:8080/_broadcast (authorization required) nodeid=uE3mZ7 context=broadcast provider=http 30 ``` 31 32 ### Securing HTTP endpoint 33 34 We automatically secure the HTTP broadcaster endpoint if the application broadcast key (`--broadcast_key`) is specified or inferred\* from the application secret (`--secret`) and the server is not running in the public mode (`--public`). 35 36 Every request MUST include an "Authorization" header with the `Bearer <broadcast-key>` value: 37 38 ```sh 39 # Run AnyCable 40 $ anycable-go --broadcast_key=my-secret-key 41 42 2024-03-06 10:35:39.296 INF Starting AnyCable 1.5.0-a7aa9b4 (with mruby 1.2.0 (2015-11-17)) (pid: 57260, open file limit: 122880, gomaxprocs: 8) nodeid=uE3mZ7 43 ... 44 2024-03-06 10:35:39.297 INF Accept broadcast requests at http://localhost:8080/_broadcast (authorization required) nodeid=uE3mZ7 context=broadcast provider=http 45 46 # Broadcast a message 47 $ curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer my-secret-key" -d '{"stream":"my_stream","data":"{\"text\":\"Hello, world!\"}"}' http://localhost:8080/_broadcast -w "%{http_code}" 48 49 201 50 ``` 51 52 \* When the broadcast key is missing but the application secret is present, we automatically generate a broadcast key using the following formula (in Ruby): 53 54 ```ruby 55 broadcast_key = OpenSSL::HMAC.hexdigest("SHA256", "<APPLICATION SECRET>", "broadcast-cable") 56 ``` 57 58 When using official AnyCable server libraries, you don't need to calculate it yourself (they all use the same inference mechanism). But if you want to publish broadcasts using a custom implementation, you can generate a broadcast key for your secret key as follows: 59 60 ```sh 61 echo -n 'broadcast-cable' | openssl dgst -sha256 -hmac '<your secret>' | awk '{print $2}' 62 ``` 63 64 ## Redis Pub/Sub 65 66 > Enable via `--broadcast_adapter=redis` (or `ANYCABLE_BROADCAST_ADAPTER=redis`). 67 68 This broadcaster uses Redis [Pub/Sub](https://redis.io/topics/pubsub) feature under the hood, and, thus, publications are delivered to all subscribed AnyCable servers simultaneously. 69 70 All broadcast messages are published to a single channel (configured via the `--redis_channel`, defaults to `__anycable__`) as follows: 71 72 ```sh 73 $ redis-cli PUBLISH __anycable__ '{"stream":"my_stream","data":"{\"text\":\"Hello, world!\"}"}' 74 75 (integer) 1 76 ``` 77 78 Note that since all AnyCable server receive each publication, we cannot use [broker](./broker.md) to provide stream history support when using Redis Pub/Sub. 79 80 See [configuration](./configuration.md#redis-configuration) for available Redis options. 81 82 ## Redis X 83 84 > Enable via `--broadcast_adapter=redisx` (or `ANYCABLE_BROADCAST_ADAPTER=redisx`). 85 86 **IMPORTANT:** Redis v6.2+ is required. 87 88 Redis X broadcaster uses [Redis Streams][redis-streams] instead of Publish/Subscribe to _consume_ publications from your application. That gives us the following benefits: 89 90 - **Broker compatibility**. This broadcaster uses a [broker](/anycable-go/broker.md) to store messages in a cache and distribute them within a cluster. This is possible due to the usage of Redis Streams consumer groups. 91 92 - **Better delivery guarantees**. Even if there is no AnyCable server available at the broadcast time, the message will be stored in Redis and delivered to an AnyCable server once it is available. In combination with the [broker feature](./broker.md), you can achieve at-least-once delivery guarantees (compared to at-most-once provided by Redis Pub/Sub). 93 94 To broadcast a message, you publish it to a dedicated Redis stream (configured via the `--redis_channel` option, defaults to `__anycable__`) with the publication JSON provided as the `payload` field value: 95 96 ```sh 97 $ redis-cli XADD __anycable__ "*" payload '{"stream":"my_stream","data":"{\"text\":\"Hello, world!\"}"}' 98 99 "1709754437079-0" 100 ``` 101 102 See [configuration](./configuration.md#redis-configuration) for available Redis options. 103 104 ## NATS Pub/Sub 105 106 > Enable via `--broadcast_adapter=nats` (or `ANYCABLE_BROADCAST_ADAPTER=nats`). 107 108 NATS broadcaster uses [NATS publish/subscribe](https://docs.nats.io/nats-concepts/core-nats/pubsub) functionality and supports cluster features out-of-the-box. It works to Redis Pub/Sub: distribute publications to all subscribed AnyCable servers. Thus, it's incompatible with [broker](./broker.md) (stream history support), too. 109 110 To broadcast a message, you publish it to a NATS stream (configured via the `--nats_channel` option, defaults to `__anycable__`) as follows: 111 112 ```sh 113 $ nats pub __anycable__ '{"stream":"my_stream","data":"{\"text\":\"Hello, world!\"}"}' 114 115 12:03:39 Published 60 bytes to "__anycable__" 116 ``` 117 118 NATS Pub/Sub is useful when you want to set up an AnyCable cluster using our [embedded NATS](./embedded_nats.md) feature, so you can avoid having additional infrastructure components. 119 120 See [configuration](./configuration.md#nats-configuration) for available NATS options. 121 122 ## Publication format 123 124 AnyCable accepts broadcast messages encoded as JSON and having the following properties: 125 126 ```js 127 { 128 "stream": "<publication stream name>", // string 129 "data": "<payload>", // string, usually a JSON-encoded object, but not necessarily 130 "meta": "{}" // object, publication metadata, optional 131 } 132 ``` 133 134 It's also possible to publish multiple messages at once. For that, you just send them as an array of publications: 135 136 ```js 137 [ 138 { 139 "stream": "...", 140 "data": "...", 141 }, 142 { 143 "stream": "...", 144 "data": "..." 145 } 146 ] 147 ``` 148 149 The `meta` field MAY contain additional instructions for servers on how to deliver the publication. Currently, the following fields are supported: 150 151 - `exclude_socket`: you can specify a unique client identifier (returned by the server in the `welcome` message as `sid`) to remove this client from the list of recipients. 152 153 All other meta fields are ignored for now. 154 155 Here is a JSON Schema describing this format: 156 157 ```json 158 { 159 "$schema": "http://json-schema.org/draft-07/schema", 160 "definitions": { 161 "publication": { 162 "type": "object", 163 "properties": { 164 "stream": { 165 "type": "string", 166 "description": "Publication stream name" 167 }, 168 "data": { 169 "type": "string", 170 "description": "Payload, usually a JSON-encoded object, but not necessarily" 171 }, 172 "meta": { 173 "type": "object", 174 "description": "Publication metadata, optional", 175 "properties": { 176 "exclude_socket": { 177 "type": "string", 178 "description": "Unique client identifier to remove this client from the list of recipients" 179 } 180 }, 181 "additionalProperties": true 182 } 183 }, 184 "required": ["stream", "data"] 185 } 186 }, 187 "anyOf": [ 188 { 189 "$ref": "#/definitions/publication" 190 }, 191 { 192 "type": "array", 193 "items":{"$ref": "#/definitions/publication"} 194 } 195 ] 196 } 197 ``` 198 199 [redis-streams]: https://redis.io/docs/data-types/streams-tutorial/