mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Further cleanup of the cable guide
This commit is contained in:
parent
4c43a10acc
commit
2b5d784583
1 changed files with 52 additions and 101 deletions
|
@ -24,7 +24,8 @@ What is Pub/Sub
|
|||
|
||||
Pub/Sub, or Publish-Subscribe, refers to a message queue paradigm whereby senders
|
||||
of information (publishers), send data to an abstract class of recipients (subscribers),
|
||||
without specifying individual recipients.
|
||||
without specifying individual recipients. Action Cable uses this approach to communicate
|
||||
between the server and many clients.
|
||||
|
||||
What is Action Cable
|
||||
--------------------
|
||||
|
@ -35,19 +36,20 @@ client-server connection instance established per WebSocket connection.
|
|||
## Server-Side Components
|
||||
|
||||
### Connections
|
||||
|
||||
Connections form the foundation of the client-server relationship. For every WebSocket
|
||||
the cable server is accepting, a Connection object will be instantiated. This instance
|
||||
becomes the parent of all the channel subscriptions that are created from there on.
|
||||
the cable server is accepting, a Connection object will be instantiated on the server side.
|
||||
This instance becomes the parent of all the channel subscriptions that are created from there on.
|
||||
The Connection itself does not deal with any specific application logic beyond authentication
|
||||
and authorization. The client of a WebSocket connection is called the consumer.
|
||||
A single consumer may have multiple WebSockets open to your application if they
|
||||
use multiple browser tabs or devices.
|
||||
and authorization. The client of a WebSocket connection is called a consumer. An individual
|
||||
user will create one consumer-connection pair per browser tab, window, or device they have open.
|
||||
|
||||
Connections are instantiated via the `ApplicationCable::Connection` class in Ruby.
|
||||
In this class, you authorize the incoming connection, and proceed to establish it
|
||||
if the user can be identified.
|
||||
|
||||
#### Connection Setup
|
||||
|
||||
```ruby
|
||||
# app/channels/application_cable/connection.rb
|
||||
module ApplicationCable
|
||||
|
@ -69,26 +71,29 @@ module ApplicationCable
|
|||
end
|
||||
end
|
||||
```
|
||||
Here `identified_by` is a connection identifier that can be used to find the
|
||||
specific connection again or later.
|
||||
Note that anything marked as an identifier will automatically create a delegate
|
||||
by the same name on any channel instances created off the connection.
|
||||
|
||||
This relies on the fact that you will already have handled authentication of the user,
|
||||
and that a successful authentication sets a signed cookie with the `user_id`.
|
||||
This cookie is then automatically sent to the connection instance when a new connection
|
||||
Here `identified_by` is a connection identifier that can be used to find the
|
||||
specific connection later. Note that anything marked as an identifier will automatically
|
||||
create a delegate by the same name on any channel instances created off the connection.
|
||||
|
||||
This example relies on the fact that you will already have handled authentication of the user
|
||||
somewhere else in your application, and that a successful authentication sets a signed
|
||||
cookie with the `user_id`.
|
||||
|
||||
The cookie is then automatically sent to the connection instance when a new connection
|
||||
is attempted, and you use that to set the `current_user`. By identifying the connection
|
||||
by this same current_user, you're also ensuring that you can later retrieve all open
|
||||
connections by a given user (and potentially disconnect them all if the user is deleted
|
||||
or deauthorized).
|
||||
|
||||
### Channels
|
||||
|
||||
A channel encapsulates a logical unit of work, similar to what a controller does in a
|
||||
regular MVC setup.
|
||||
By default, Rails creates a parent `ApplicationCable::Channel` class for encapsulating
|
||||
shared logic between your channels.
|
||||
regular MVC setup. By default, Rails creates a parent `ApplicationCable::Channel` class
|
||||
for encapsulating shared logic between your channels.
|
||||
|
||||
#### Parent Channel Setup
|
||||
|
||||
```ruby
|
||||
# app/channels/application_cable/channel.rb
|
||||
module ApplicationCable
|
||||
|
@ -96,7 +101,8 @@ module ApplicationCable
|
|||
end
|
||||
end
|
||||
```
|
||||
Then you would create child channel classes. For example, you could have a
|
||||
|
||||
Then you would create your own channel classes. For example, you could have a
|
||||
**ChatChannel** and an **AppearanceChannel**:
|
||||
|
||||
```ruby
|
||||
|
@ -109,7 +115,7 @@ class AppearanceChannel < ApplicationCable::Channel
|
|||
end
|
||||
```
|
||||
|
||||
A consumer could then be subscribed to either or both of these channels.
|
||||
A consumer could then be subscribed to either or both of these channels.
|
||||
|
||||
#### Subscriptions
|
||||
|
||||
|
@ -121,42 +127,47 @@ an identifier sent by the cable consumer.
|
|||
```ruby
|
||||
# app/channels/application_cable/chat_channel.rb
|
||||
class ChatChannel < ApplicationCable::Channel
|
||||
# Called when the consumer has successfully become a subscriber of this channel
|
||||
def subscribed
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Client-Side Components
|
||||
|
||||
### Connections
|
||||
|
||||
Consumers require an instance of the connection on their side. This can be
|
||||
established using the following Javascript:
|
||||
established using the following Javascript, which is generated by default in Rails:
|
||||
|
||||
#### Connect Consumer
|
||||
|
||||
```coffeescript
|
||||
# app/assets/javascripts/cable.coffee
|
||||
#= require action_cable
|
||||
|
||||
@App = {}
|
||||
App.cable = ActionCable.createConsumer("ws://cable.example.com")
|
||||
App.cable = ActionCable.createConsumer()
|
||||
```
|
||||
The `ws://cable.example.com` address must point to your set of Action Cable servers, and it
|
||||
must share a cookie namespace with the rest of the application (which may live under http://example.com).
|
||||
This ensures that the signed cookie will be correctly sent.
|
||||
|
||||
This will ready a consumer that'll connect against /cable on your server by default.
|
||||
The connection won't be established until you've also specified at least one subscription
|
||||
you're interested in having.
|
||||
|
||||
#### Subscriber
|
||||
|
||||
When a consumer is subscribed to a channel, they act as a subscriber. A
|
||||
consumer can act as a subscriber to a given channel any number of times.
|
||||
For example, a consumer could subscribe to multiple chat rooms at the same time.
|
||||
(remember that a physical user may have multiple consumers, one per tab/device open to your connection).
|
||||
|
||||
A consumer becomes a subscriber, by creating a subscription to a given channel:
|
||||
|
||||
```coffeescript
|
||||
# app/assets/javascripts/cable/subscriptions/chat.coffee
|
||||
# Assumes you've already requested the right to send web notifications
|
||||
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }
|
||||
|
||||
# app/assets/javascripts/cable/subscriptions/appearance.coffee
|
||||
# Assumes you've already requested the right to send web notifications
|
||||
App.cable.subscriptions.create { channel: "AppearanceChannel" }
|
||||
```
|
||||
|
||||
|
@ -166,6 +177,7 @@ received data will be described later on.
|
|||
## Client-Server Interactions
|
||||
|
||||
### Streams
|
||||
|
||||
Streams provide the mechanism by which channels route published content
|
||||
(broadcasts) to its subscribers.
|
||||
|
||||
|
@ -190,8 +202,8 @@ class CommentsChannel < ApplicationCable::Channel
|
|||
end
|
||||
end
|
||||
```
|
||||
You can then broadcast to this channel using:
|
||||
`CommentsChannel.broadcast_to(@post, @comment)`
|
||||
|
||||
You can then broadcast to this channel using: `CommentsChannel.broadcast_to(@post, @comment)`
|
||||
|
||||
### Broadcastings
|
||||
|
||||
|
@ -261,7 +273,6 @@ will become your params hash in your cable channel. The keyword `channel` is req
|
|||
|
||||
```coffeescript
|
||||
# app/assets/javascripts/cable/subscriptions/chat.coffee
|
||||
# Client-side, which assumes you've already requested the right to send web notifications
|
||||
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
|
||||
received: (data) ->
|
||||
@appendLine(data)
|
||||
|
@ -281,7 +292,7 @@ App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
|
|||
|
||||
```ruby
|
||||
# Somewhere in your app this is called, perhaps from a NewCommentJob
|
||||
ChatChannel.broadcast_to chat_#{room}, sent_by: 'Paul', body: 'This is a cool chat app.'
|
||||
ChatChannel.broadcast_to "chat_#{room}", sent_by: 'Paul', body: 'This is a cool chat app.'
|
||||
```
|
||||
|
||||
|
||||
|
@ -305,7 +316,6 @@ end
|
|||
|
||||
```coffeescript
|
||||
# app/assets/javascripts/cable/subscriptions/chat.coffee
|
||||
# Client-side which assumes you've already requested the right to send web notifications
|
||||
App.chatChannel = App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
|
||||
received: (data) ->
|
||||
# data => { sent_by: "Paul", body: "This is a cool chat app." }
|
||||
|
@ -425,7 +435,7 @@ web notifications when you broadcast to the right streams:
|
|||
# app/channels/web_notifications_channel.rb
|
||||
class WebNotificationsChannel < ApplicationCable::Channel
|
||||
def subscribed
|
||||
stream_from "web_notifications_#{current_user.id}"
|
||||
stream_for current_user
|
||||
end
|
||||
end
|
||||
```
|
||||
|
@ -450,6 +460,7 @@ The `WebNotificationsChannel.broadcast_to` call places a message in the current
|
|||
subscription adapter (Redis by default)'s pubsub queue under a separate
|
||||
broadcasting name for each user. For a user with an ID of 1, the broadcasting
|
||||
name would be `web_notifications_1`.
|
||||
|
||||
The channel has been instructed to stream everything that arrives at
|
||||
`web_notifications_1` directly to the client by invoking the `#received(data)`
|
||||
callback. The data is the hash sent as the second parameter to the server-side
|
||||
|
@ -463,8 +474,7 @@ repository for a full example of how to setup Action Cable in a Rails app and ad
|
|||
|
||||
## Configuration
|
||||
|
||||
Action Cable has three required configurations: a subscription adapter,
|
||||
allowed request origins, and the cable server URL.
|
||||
Action Cable has two required configurations: a subscription adapter and allowed request origins.
|
||||
|
||||
### Subscription Adapter
|
||||
|
||||
|
@ -475,10 +485,10 @@ additional information on adapters.
|
|||
|
||||
```yaml
|
||||
production: &production
|
||||
adapter: redis # Optional as default is redis
|
||||
adapter: redis
|
||||
url: redis://10.10.3.153:6381
|
||||
development: &development
|
||||
url: redis://localhost:6379
|
||||
adapter: async
|
||||
test: *development
|
||||
```
|
||||
|
||||
|
@ -512,41 +522,8 @@ in the development environment.
|
|||
|
||||
### Consumer Configuration
|
||||
|
||||
Once you have decided how to run your cable server (see below), you must provide
|
||||
the server url (or path) to your client-side setup. There are two ways you can do this.
|
||||
|
||||
The first is to simply pass it in when creating your consumer. For a standalone server,
|
||||
this would be something like: `App.cable = ActionCable.createConsumer("ws://example.com:28080")`,
|
||||
and for an in-app server, something like: `App.cable = ActionCable.createConsumer("/cable")`.
|
||||
|
||||
The second option is to pass the server url through the `action_cable_meta_tag` in your layout.
|
||||
This uses a url or path typically set via `config.action_cable.url`
|
||||
in the environment configuration files, or defaults to "/cable".
|
||||
|
||||
This method is especially useful if your WebSocket url might change
|
||||
between environments. If you host your production server via https,
|
||||
you will need to use the wss scheme for your ActionCable server, but
|
||||
development might remain http and use the ws scheme. You might use localhost
|
||||
in development and your domain in production.
|
||||
|
||||
In any case, to vary the WebSocket url between environments, add the following
|
||||
configuration to each environment:
|
||||
|
||||
```ruby
|
||||
config.action_cable.url = "ws://example.com:28080"
|
||||
```
|
||||
|
||||
Then add the following line to your layout before your JavaScript tag:
|
||||
|
||||
```erb
|
||||
<%= action_cable_meta_tag %>
|
||||
```
|
||||
|
||||
And finally, create your consumer like so:
|
||||
|
||||
```coffeescript
|
||||
App.cable = ActionCable.createConsumer()
|
||||
```
|
||||
To configure the URL, add a call to `action_cable_meta_tag` in your HTML layout HEAD.
|
||||
This uses a url or path typically set via `config.action_cable.url` in the environment configuration files.
|
||||
|
||||
### Other Configurations
|
||||
|
||||
|
@ -560,29 +537,6 @@ Rails.application.config.action_cable.log_tags = [
|
|||
]
|
||||
```
|
||||
|
||||
Your WebSocket URL might change between environments. If you host your
|
||||
production server via https, you will need to use the wss scheme
|
||||
for your ActionCable server, but development might remain http and
|
||||
use the ws scheme. You might use localhost in development and your
|
||||
domain in production. In any case, to vary the WebSocket URL between
|
||||
environments, add the following configuration to each environment:
|
||||
|
||||
```ruby
|
||||
config.action_cable.url = "ws://example.com:28080"
|
||||
```
|
||||
|
||||
Then add the following line to your layout before your JavaScript tag:
|
||||
|
||||
```erb
|
||||
<%= action_cable_meta_tag %>
|
||||
```
|
||||
|
||||
And finally, create your consumer like so:
|
||||
|
||||
```coffeescript
|
||||
App.cable = Cable.createConsumer()
|
||||
```
|
||||
|
||||
For a full list of all configuration options, see the `ActionCable::Server::Configuration` class.
|
||||
|
||||
Also note that your server must provide at least the same number of
|
||||
|
@ -590,8 +544,9 @@ database connections as you have workers. The default worker pool is
|
|||
set to 100, so that means you have to make at least that available.
|
||||
You can change that in `config/database.yml` through the `pool` attribute.
|
||||
|
||||
## Running the cable server
|
||||
## Running standalone cable servers
|
||||
|
||||
<<<<<<< HEAD
|
||||
### In App
|
||||
|
||||
Action Cable can run alongside your Rails application. For example, to
|
||||
|
@ -615,6 +570,9 @@ but the use of Redis keeps messages synced across connections.
|
|||
|
||||
### Standalone
|
||||
The cable server(s) can be separated from your normal application server.
|
||||
=======
|
||||
The cable servers can be separated from your normal application server.
|
||||
>>>>>>> Further cleanup of the cable guide
|
||||
It's still a Rack application, but it is its own Rack application.
|
||||
The recommended basic setup is as follows:
|
||||
|
||||
|
@ -636,13 +594,6 @@ The above will start a cable server on port 28080.
|
|||
|
||||
### Notes
|
||||
|
||||
Beware that currently the cable server will _not_ auto-reload any
|
||||
changes in the framework. As we've discussed, long-running cable
|
||||
connections mean long-running objects. We don't yet have a way of
|
||||
reloading the classes of those objects in a safe manner. So when
|
||||
you change your channels, or the model your channels use, you must
|
||||
restart the cable server.
|
||||
|
||||
The WebSocket server doesn't have access to the session, but it has
|
||||
access to the cookies. This can be used when you need to handle
|
||||
authentication. You can see one way of doing that with Devise in this [article](http://www.rubytutorial.io/actioncable-devise-authentication).
|
||||
|
|
Loading…
Reference in a new issue