Broadcast Adapters

Broadcast adapter is used to handle messages published by your application to WebSocket server which in its turn delivers the messages to connected clients (see architecture).

That is, when you call ActionCable.server.broadcast, AnyCable first pushes the message to WebSocket server via a broadcast adapter, and the actual broadcasting is happening within a WS server (or servers).

AnyCable ships with three broadcast adapters by default: Redis (default), NATS, and HTTP.

NOTE: The broadcast adapters fall into two categories: legacy fan-out (distributed) and singular.

Broadcasting API

To publish a message to a stream via AnyCable, you can use the following API:

AnyCable.broadcast("my_stream", {text: "hoi"})

# or directly via the singleton broadcast adapter instance
AnyCable.broadcast_adapter.broadcast("my_stream", {text: "hoi"})

Batching

AnyCable-Go v1.4.5+ supports publishing broadcast messages in batches. This is especially useful if you want to guarantee the order of delivered messages to clients (to be the same as the broadcasts order). To batch-broadcast messages, wrap your code with the .batching method of the broadcast adapter:

AnyCable.broadcast_adapter.batching do
  AnyCable.broadcast("my_stream", {text: "hoi"})
  AnyCable.broadcast("my_stream", {text: "wereld"})
end #=> the actual publishing happens as we exit the block

The .batching method supports nesting, if you need to broadcast some messages immediately:

AnyCable.broadcast_adapter.batching do
  AnyCable.broadcast("my_stream", {text: "hoi"}) # added to the current batch

  AnyCable.broadcast_adapter.batching(false) do
    AnyCable.broadcast("another_stream", {text: "some other story"}) #=> publish immediately

    AnyCable.broadcast_adapter.batching do
      AnyCable.broadcast("my_stream", {text: "wereld"}) # added to the current batch
    end
  end
end #=> the current batch is published

Broadcast options

AnyCable-Go v1.4.5+ supports additional broadcast options. You can pass them as the third argument to the AnyCable.broadcast method:

AnyCable.broadcast("my_stream", {text: "hoi"}, {exclude_socket: "some-socket-id"})

The following options are supported:

  • exclude_socket: pass an AnyCable socket ID to exclude it from the broadcast recipients list. Useful if you want to broadcast to all clients except the one that initiated the broadcast.

HTTP adapter

HTTP adapter has zero-dependencies and, thus, allows you to quickly start using AnyCable.

Since v1.4, HTTP adapter can also be considered for production (thanks to the new pub/sub component in AnyCable-Go). Moreover, it can be used with the new broker feature of AnyCable-Go.

To use HTTP adapter specify broadcast_adapter configuration parameter (--broadcast-adapter=http or ANYCABLE_BROADCAST_ADAPTER=http or set in the code/YML) and make sure your AnyCable WebSocket server supports it. An URL to broadcast to could be specified via http_broadcast_url parameter (defaults to http://localhost:8090/_broadcast, which corresponds to the AnyCable-Go default).

NOTE: For SSL connections, we use the SSL_VERIFY_NONE mode.

Example cURL command to publish a message:

curl -X POST -H "Content-Type: application/json" -d '{"stream":"my_stream","data":"{\"text\":\"Hello, world!\"}"}' http://localhost:8090/_broadcast

Securing HTTP endpoint

If your broadcasting HTTP endpoint is open to public, we recommend to protect it via a simple authorization via a header check.

You must configure both Ruby RPC server and a WebSocket server to use the same http_broadcast_secret (which will we passed via Authorization: Bearer %secret%).

Redis X

Redis X adapter uses Redis Streams instead of Publish/Subscribe to deliver broadcasting messages from your application to WebSocket servers. That gives us the following benefits:

  • Better delivery guarantees. Even if there is no WebSocket server available at the broadcast time, the message will be stored in Redis and delivered to the server once it is available. In combination with the broker feature, you can achieve at-least-once delivery guarantees (compared to at-most-once provided by Redis pub/sub).

  • Broker compatibility. Using a broker (or streams history) requires handling each broadcasted message by a single node in the cluster (so it can be registered in a cache). With Redis X adapter, we achieve this by using consumer groups for the Redis stream.

Configuration options are the same as for the Redis adapter. The redis_channel option is treated as a stream name.

IMPORTANT: Redis v6.2+ is required.

See configuration for available Redis options.

Redis adapter (legacy)

It's a default adapter for AnyCable. It uses Redis Pub/Sub feature under the hood. Thus, all the messages delivered to all WebSocket servers at once.

NOTE: To use Redis adapter, you must ensure that it is present in your Gemfile; AnyCable gem doesn't have redis as a dependency.

See configuration for available Redis options.

Redis Sentinel support

AnyCable could be used with Redis Sentinel out-of-the-box. For that, you should configure it the following way:

  • redis_url must contain a master name (e.g., ANYCABLE_REDIS_URL=redis://mymaster)
  • redis_sentinels must contain a comma separated list of sentinel hosts (e.g., ANYCABLE_REDIS_SENTINELS=my.redis.sentinel.first:26380,my.redis.sentinel.second:26380).

If your sentinels are protected with passwords, use the following format: :password1@my.redis.sentinel.first:26380,:password2@my.redis.sentinel.second:26380.

See the demo of using Redis with Sentinels in a local Docker dev environment.

NATS adapter (legacy)

NOTE: Make sure you added nats-pure gem to your Gemfile.

NATS adapter uses NATS publish/subscribe functionality and supports cluster features out-of-the-box.

With embedded NATS feature of AnyCable-Go, you can minimize the number of required components to deploy an AnyCable-backed application.

See configuration for available NATS options.

Custom adapters

AnyCable allows you to use custom broadcasting adapters:

# Specify by name (tries to load `AnyCable::BroadcastAdapters::MyAdapter` from
# "anycable/broadcast_adapters/my_adapter")
AnyCable.broadcast_adapter = :my_adapter, {option: "value"}
# or provide an instance (should respond_to #broadcast)
AnyCable.broadcast_adapter = MyAdapter.new

Want to have a different adapter out-of-the-box? Join the discussion.