提交 d3f0aa36 编写于 作者: R Rafael França

Merge pull request #23668 from maclover7/cable-docs

Full Action Cable documentation read through
......@@ -39,7 +39,7 @@ reflections of each unit.
### A full-stack example
The first thing you must do is define your `ApplicationCable::Connection` class in Ruby. This
is the place where you authorize the incoming connection, and proceed to establish it
is the place where you authorize the incoming connection, and proceed to establish it,
if all is well. Here's the simplest example starting with the server-side connection class:
```ruby
......@@ -73,7 +73,7 @@ use that to set the `current_user`. By identifying the connection by this same c
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).
Then you should define your `ApplicationCable::Channel` class in Ruby. This is the place where you put
Next, you should define your `ApplicationCable::Channel` class in Ruby. This is the place where you put
shared logic between your channels.
```ruby
......@@ -94,7 +94,7 @@ The client-side needs to setup a consumer instance of this connection. That's do
App.cable = ActionCable.createConsumer("ws://cable.example.com")
```
The ws://cable.example.com address must point to your set of Action Cable servers, and it
The `ws://cable.example.com` address must point to your Action Cable server(s), 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.
......@@ -105,8 +105,8 @@ is defined by declaring channels on the server and allowing the consumer to subs
### Channel example 1: User appearances
Here's a simple example of a channel that tracks whether a user is online or not and what page they're on.
(This is useful for creating presence features like showing a green dot next to a user name if they're online).
Here's a simple example of a channel that tracks whether a user is online or not, and also what page they are currently on.
(This is useful for creating presence features like showing a green dot next to a user's name if they're online).
First you declare the server-side channel:
......@@ -180,7 +180,7 @@ App.cable.subscriptions.create "AppearanceChannel",
Simply calling `App.cable.subscriptions.create` will setup the subscription, which will call `AppearanceChannel#subscribed`,
which in turn is linked to original `App.cable` -> `ApplicationCable::Connection` instances.
We then link the client-side `appear` method to `AppearanceChannel#appear(data)`. This is possible because the server-side
Next, we link the client-side `appear` method to `AppearanceChannel#appear(data)`. This is possible because the server-side
channel instance will automatically expose the public methods declared on the class (minus the callbacks), so that these
can be reached as remote procedure calls via a subscription's `perform` method.
......@@ -215,7 +215,7 @@ ActionCable.server.broadcast \
"web_notifications_#{current_user.id}", { title: 'New things!', body: 'All the news that is fit to print' }
```
The `ActionCable.server.broadcast` call places a message in the Redis' 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 `ActionCable.server.broadcast` call places a message in the Action Cable 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 broadcast call, JSON encoded for the trip
across the wire, and unpacked for the data argument arriving to `#received`.
......@@ -234,7 +234,7 @@ class ChatChannel < ApplicationCable::Channel
end
```
Pass an object as the first argument to `subscriptions.create`, and that object will become your params hash in your cable channel. The keyword `channel` is required.
If you pass an object as the first argument to `subscriptions.create`, that object will become the params hash in your cable channel. The keyword `channel` is required.
```coffeescript
# Client-side, which assumes you've already requested the right to send web notifications
......@@ -293,7 +293,7 @@ The rebroadcast will be received by all connected clients, _including_ the clien
### More complete examples
See the [rails/actioncable-examples](http://github.com/rails/actioncable-examples) repository for a full example of how to setup Action Cable in a Rails app and adding channels.
See the [rails/actioncable-examples](http://github.com/rails/actioncable-examples) repository for a full example of how to setup Action Cable in a Rails app, and how to add channels.
## Configuration
......@@ -349,11 +349,11 @@ 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
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:
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"
......@@ -440,7 +440,7 @@ The Ruby side of things is built on top of [websocket-driver](https://github.com
## Deployment
Action Cable is powered by a combination of websockets and threads. All of the
Action Cable is powered by a combination of WebSockets and threads. All of the
connection management is handled internally by utilizing Ruby’s native thread
support, which means you can use all your regular Rails models with no problems
as long as you haven’t committed any thread-safety sins.
......
......@@ -32,8 +32,8 @@ module Channel
#
# == Action processing
#
# Unlike subclasses of ActionController::Base, channels do not follow a REST
# constraint form for their actions. Instead, ActionCable operates through a
# Unlike subclasses of ActionController::Base, channels do not follow a RESTful
# constraint form for their actions. Instead, Action Cable operates through a
# remote-procedure call model. You can declare any public method on the
# channel (optionally taking a <tt>data</tt> argument), and this method is
# automatically exposed as callable to the client.
......@@ -63,10 +63,10 @@ module Channel
# end
# end
#
# In this example, subscribed/unsubscribed are not callable methods, as they
# In this example, the subscribed and unsubscribed methods are not callable methods, as they
# were already declared in ActionCable::Channel::Base, but <tt>#appear</tt>
# and <tt>#away</tt> are. <tt>#generate_connection_token</tt> is also not
# callable as it's a private method. You'll see that appear accepts a data
# callable, since it's a private method. You'll see that appear accepts a data
# parameter, which it then uses as part of its model call. <tt>#away</tt>
# does not, since it's simply a trigger action.
#
......@@ -125,7 +125,7 @@ def action_methods
protected
# action_methods are cached and there is sometimes need to refresh
# them. ::clear_action_methods! allows you to do that, so next time
# you run action_methods, they will be recalculated
# you run action_methods, they will be recalculated.
def clear_action_methods!
@action_methods = nil
end
......@@ -166,9 +166,9 @@ def perform_action(data)
end
end
# Called by the cable connection when its cut so the channel has a chance to cleanup with callbacks.
# Called by the cable connection when its cut, so the channel has a chance to cleanup with callbacks.
# This method is not intended to be called directly by the user. Instead, overwrite the #unsubscribed callback.
def unsubscribe_from_channel
def unsubscribe_from_channel # :nodoc:
run_callbacks :unsubscribe do
unsubscribed
end
......@@ -183,7 +183,7 @@ def subscribed
end
# Called once a consumer has cut its cable connection. Can be used for cleaning up connections or marking
# people as offline or the like.
# users as offline or the like.
def unsubscribed
# Override in subclasses
end
......@@ -224,7 +224,6 @@ def delegate_connection_identifiers
end
end
def subscribe_to_channel
run_callbacks :subscribe do
subscribed
......@@ -237,7 +236,6 @@ def subscribe_to_channel
end
end
def extract_action(data)
(data['action'].presence || :receive).to_sym
end
......
module ActionCable
module Channel
# Streams allow channels to route broadcastings to the subscriber. A broadcasting is, as discussed elsewhere, a pub/sub queue where any data
# put into it is automatically sent to the clients that are connected at that time. It's purely an online queue, though. If you're not
# streaming a broadcasting at the very moment it sends out an update, you'll not get that update when connecting later.
# placed into it is automatically sent to the clients that are connected at that time. It's purely an online queue, though. If you're not
# streaming a broadcasting at the very moment it sends out an update, you will not get that update, if you connect after it has been sent.
#
# Most commonly, the streamed broadcast is sent straight to the subscriber on the client-side. The channel just acts as a connector between
# the two parties (the broadcaster and the channel subscriber). Here's an example of a channel that allows subscribers to get all new
......@@ -18,8 +18,10 @@ module Channel
# end
# end
#
# So the subscribers of this channel will get whatever data is put into the, let's say, `comments_for_45` broadcasting as soon as it's put there.
# That looks like so from that side of things:
# Based on the above example, the subscribers of this channel will get whatever data is put into the,
# let's say, `comments_for_45` broadcasting as soon as it's put there.
#
# An example broadcasting for this channel looks like so:
#
# ActionCable.server.broadcast "comments_for_45", author: 'DHH', content: 'Rails is just swell'
#
......@@ -37,8 +39,8 @@ module Channel
#
# CommentsChannel.broadcast_to(@post, @comment)
#
# If you don't just want to parlay the broadcast unfiltered to the subscriber, you can supply a callback that lets you alter what goes out.
# Example below shows how you can use this to provide performance introspection in the process:
# If you don't just want to parlay the broadcast unfiltered to the subscriber, you can also supply a callback that lets you alter what is sent out.
# The below example shows how you can use this to provide performance introspection in the process:
#
# class ChatChannel < ApplicationCable::Channel
# def subscribed
......@@ -70,7 +72,7 @@ module Streams
# Start streaming from the named <tt>broadcasting</tt> pubsub queue. Optionally, you can pass a <tt>callback</tt> that'll be used
# instead of the default of just transmitting the updates straight to the subscriber.
def stream_from(broadcasting, callback = nil)
# Hold off the confirmation until pubsub#subscribe is successful
# Don't send the confirmation until pubsub#subscribe is successful
defer_subscription_confirmation!
callback ||= default_stream_callback(broadcasting)
......
......@@ -2,9 +2,9 @@
module ActionCable
module Connection
# 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. Incoming messages are then routed to these channel subscriptions
# based on an identifier sent by the cable consumer. The Connection itself does not deal with any specific application logic beyond
# For every WebSocket the Action Cable server accepts, a Connection object will be instantiated. This instance becomes the parent
# of all of the channel subscriptions that are created from there on. Incoming messages are then routed to these channel subscriptions
# based on an identifier sent by the Action Cable consumer. The Connection itself does not deal with any specific application logic beyond
# authentication and authorization.
#
# Here's a basic example:
......@@ -33,8 +33,8 @@ module Connection
# end
# end
#
# First, we declare that this connection can be identified by its current_user. This allows us later to be able to find all connections
# established for that current_user (and potentially disconnect them if the user was removed from an account). You can declare as many
# First, we declare that this connection can be identified by its current_user. This allows us to later be able to find all connections
# established for that current_user (and potentially disconnect them). You can declare as many
# identification indexes as you like. Declaring an identification means that an attr_accessor is automatically set for that key.
#
# Second, we rely on the fact that the WebSocket connection is established with the cookies from the domain being sent along. This makes
......@@ -65,8 +65,8 @@ def initialize(server, env)
end
# Called by the server when a new WebSocket connection is established. This configures the callbacks intended for overwriting by the user.
# This method should not be called directly. Rely on the #connect (and #disconnect) callback instead.
def process
# This method should not be called directly -- instead rely upon on the #connect (and #disconnect) callbacks.
def process # :nodoc:
logger.info started_request_message
if websocket.possible? && allow_request_origin?
......@@ -76,7 +76,7 @@ def process
end
end
# Data received over the cable is handled by this method. It's expected that everything inbound is JSON encoded.
# Data received over the WebSocket connection is handled by this method. It's expected that everything inbound is JSON encoded.
# The data is routed to the proper channel that the connection has subscribed to.
def receive(data_in_json)
if websocket.alive?
......@@ -88,7 +88,7 @@ def receive(data_in_json)
# Send raw data straight back down the WebSocket. This is not intended to be called directly. Use the #transmit available on the
# Channel instead, as that'll automatically address the correct subscriber and wrap the message in JSON.
def transmit(data)
def transmit(data) # :nodoc:
websocket.transmit data
end
......
......@@ -12,7 +12,7 @@ module Identification
class_methods do
# Mark a key as being a connection identifier index that can then be used to find the specific connection again later.
# Common identifiers are current_user and current_account, but could be anything really.
# Common identifiers are current_user and current_account, but could be anything, really.
#
# Note that anything marked as an identifier will automatically create a delegate by the same name on any
# channel instances created off the connection.
......
module ActionCable
module Connection
# Allows us to buffer messages received from the WebSocket before the Connection has been fully initialized and is ready to receive them.
# Entirely internal operation and should not be used directly by the user.
class MessageBuffer
# Allows us to buffer messages received from the WebSocket before the Connection has been fully initialized, and is ready to receive them.
class MessageBuffer # :nodoc:
def initialize(connection)
@connection = connection
@buffered_messages = []
......
......@@ -3,8 +3,8 @@
module ActionCable
module Connection
# Collection class for all the channel subscriptions established on a given connection. Responsible for routing incoming commands that arrive on
# the connection to the proper channel. Should not be used directly by the user.
class Subscriptions
# the connection to the proper channel.
class Subscriptions # :nodoc:
def initialize(connection)
@connection = connection
@subscriptions = {}
......
......@@ -9,7 +9,7 @@ module ActionCableHelper
# <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
# </head>
#
# This is then used by ActionCable to determine the url of your websocket server.
# This is then used by Action Cable to determine the url of your WebSocket server.
# Your CoffeeScript can then connect to the server without needing to specify the
# url directly:
#
......
......@@ -13,8 +13,8 @@ module ActionCable
# ActionCable.server.remote_connections.where(current_user: User.find(1)).disconnect
#
# This will disconnect all the connections established for
# <tt>User.find(1)</tt> across all servers running on all machines, because
# it uses the internal channel that all these servers are subscribed to.
# <tt>User.find(1)</tt>, across all servers running on all machines, because
# it uses the internal channel that all of these servers are subscribed to.
class RemoteConnections
attr_reader :server
......
......@@ -2,10 +2,10 @@
module ActionCable
module Server
# A singleton ActionCable::Server instance is available via ActionCable.server. It's used by the rack process that starts the cable server, but
# also by the user to reach the RemoteConnections instead for finding and disconnecting connections across all servers.
# A singleton ActionCable::Server instance is available via ActionCable.server. It's used by the Rack process that starts the Action Cable server, but
# is also used by the user to reach the RemoteConnections object, which is used for finding and disconnecting connections across all servers.
#
# Also, this is the server instance used for broadcasting. See Broadcasting for details.
# Also, this is the server instance used for broadcasting. See Broadcasting for more information.
class Base
include ActionCable::Server::Broadcasting
include ActionCable::Server::Connections
......@@ -19,11 +19,10 @@ def self.logger; config.logger; end
def initialize
@mutex = Mutex.new
@remote_connections = @stream_event_loop = @worker_pool = @channel_classes = @pubsub = nil
end
# Called by rack to setup the server.
# Called by Rack to setup the server.
def call(env)
setup_heartbeat_timer
config.connection_class.new(self, env).process
......@@ -48,7 +47,7 @@ def worker_pool
@worker_pool || @mutex.synchronize { @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size) }
end
# Requires and returns a hash of all the channel class constants keyed by name.
# Requires and returns a hash of all of the channel class constants, which are keyed by name.
def channel_classes
@channel_classes || @mutex.synchronize do
@channel_classes ||= begin
......@@ -63,7 +62,7 @@ def pubsub
@pubsub || @mutex.synchronize { @pubsub ||= config.pubsub_adapter.new(self) }
end
# All the identifiers applied to the connection class associated with this server.
# All of the identifiers applied to the connection class associated with this server.
def connection_identifiers
config.connection_class.identifiers
end
......
module ActionCable
module Server
# Broadcasting is how other parts of your application can send messages to the channel subscribers. As explained in Channel, most of the time, these
# Broadcasting is how other parts of your application can send messages to a channel's subscribers. As explained in Channel, most of the time, these
# broadcastings are streamed directly to the clients subscribed to the named broadcasting. Let's explain with a full-stack example:
#
# class WebNotificationsChannel < ApplicationCable::Channel
......@@ -9,16 +9,16 @@ module Server
# end
# end
#
# # Somewhere in your app this is called, perhaps from a NewCommentJob
# # Somewhere in your app this is called, perhaps from a NewCommentJob:
# ActionCable.server.broadcast \
# "web_notifications_1", { title: "New things!", body: "All that's fit for print" }
#
# # Client-side CoffeeScript, which assumes you've already requested the right to send web notifications
# # Client-side CoffeeScript, which assumes you've already requested the right to send web notifications:
# App.cable.subscriptions.create "WebNotificationsChannel",
# received: (data) ->
# new Notification data['title'], body: data['body']
module Broadcasting
# Broadcast a hash directly to a named <tt>broadcasting</tt>. It'll automatically be JSON encoded.
# Broadcast a hash directly to a named <tt>broadcasting</tt>. This will later be JSON encoded.
def broadcast(broadcasting, message)
broadcaster_for(broadcasting).broadcast(message)
end
......
module ActionCable
module Server
# An instance of this configuration object is available via ActionCable.server.config, which allows you to tweak the configuration points
# An instance of this configuration object is available via ActionCable.server.config, which allows you to tweak Action Cable configuration
# in a Rails config initializer.
class Configuration
attr_accessor :logger, :log_tags
......
module ActionCable
module Server
# Collection class for all the connections that's been established on this specific server. Remember, usually you'll run many cable servers, so
# you can't use this collection as an full list of all the connections established against your application. Use RemoteConnections for that.
# As such, this is primarily for internal use.
module Connections
# Collection class for all the connections that have been established on this specific server. Remember, usually you'll run many Action Cable servers, so
# you can't use this collection as a full list of all of the connections established against your application. Instead, use RemoteConnections for that.
module Connections # :nodoc:
BEAT_INTERVAL = 3
def connections
......@@ -19,7 +18,7 @@ def remove_connection(connection)
end
# WebSocket connection implementations differ on when they'll mark a connection as stale. We basically never want a connection to go stale, as you
# then can't rely on being able to receive and send to it. So there's a 3 second heartbeat running on all connections. If the beat fails, we automatically
# then can't rely on being able to communicate with the connection. To solve this, a 3 second heartbeat runs on all connections. If the beat fails, we automatically
# disconnect.
def setup_heartbeat_timer
@heartbeat_timer ||= Concurrent::TimerTask.new(execution_interval: BEAT_INTERVAL) do
......
......@@ -4,8 +4,8 @@
module ActionCable
module Server
# Worker used by Server.send_async to do connection work in threads. Only for internal use.
class Worker
# Worker used by Server.send_async to do connection work in threads.
class Worker # :nodoc:
include ActiveSupport::Callbacks
thread_mattr_accessor :connection
......
module ActionCable
module Server
class Worker
# Clear active connections between units of work so the long-running channel or connection processes do not hoard connections.
# Clear active connections between units of work so that way long-running channels or connection processes do not hoard connections.
module ActiveRecordConnectionManagement
extend ActiveSupport::Concern
......@@ -19,4 +19,4 @@ def with_database_connections
end
end
end
end
\ No newline at end of file
end
......@@ -13,11 +13,11 @@ module SubscriptionAdapter
class EventedRedis < Base # :nodoc:
@@mutex = Mutex.new
# Overwrite this factory method for EventMachine redis connections if you want to use a different Redis library than EM::Hiredis.
# Overwrite this factory method for EventMachine Redis connections if you want to use a different Redis connection library than EM::Hiredis.
# This is needed, for example, when using Makara proxies for distributed Redis.
cattr_accessor(:em_redis_connector) { ->(config) { EM::Hiredis.connect(config[:url]) } }
# Overwrite this factory method for redis connections if you want to use a different Redis library than Redis.
# Overwrite this factory method for Redis connections if you want to use a different Redis connection library than Redis.
# This is needed, for example, when using Makara proxies for distributed Redis.
cattr_accessor(:redis_connector) { ->(config) { ::Redis.new(url: config[:url]) } }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册