add rescue_with support to ActionCable::Connection::Base
and update ActionCable guide to describe exception handling usage # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # On branch master # Your branch is behind 'origin/master' by 5 commits, and can be fast-forwarded. # # Changes to be committed: # modified: actioncable/CHANGELOG.md # modified: actioncable/lib/action_cable/connection/base.rb # modified: actioncable/lib/action_cable/connection/subscriptions.rb # modified: actioncable/test/connection/subscriptions_test.rb # modified: guides/source/action_cable_overview.md #
This commit is contained in:
parent
5df9b4584c
commit
d2571e560c
|
@ -1,3 +1,9 @@
|
|||
* `ActionCable::Connection::Base` now allows intercepting unhandled exceptions
|
||||
with `rescue_from` before they are logged, which is useful for error reporting
|
||||
tools and other integrations.
|
||||
|
||||
*Justin Talbott*
|
||||
|
||||
* Add `ActionCable::Channel#stream_or_reject_for` to stream if record is present, otherwise reject the connection
|
||||
|
||||
*Atul Bhosale*
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "action_dispatch"
|
||||
require "active_support/rescuable"
|
||||
|
||||
module ActionCable
|
||||
module Connection
|
||||
|
@ -46,6 +47,7 @@ module ActionCable
|
|||
include Identification
|
||||
include InternalChannel
|
||||
include Authorization
|
||||
include ActiveSupport::Rescuable
|
||||
|
||||
attr_reader :server, :env, :subscriptions, :logger, :worker_pool, :protocol
|
||||
delegate :event_loop, :pubsub, to: :server
|
||||
|
|
|
@ -21,6 +21,7 @@ module ActionCable
|
|||
logger.error "Received unrecognized command in #{data.inspect}"
|
||||
end
|
||||
rescue Exception => e
|
||||
@connection.rescue_with_handler(e)
|
||||
logger.error "Could not execute command from (#{data.inspect}) [#{e.class} - #{e.message}]: #{e.backtrace.first(5).join(" | ")}"
|
||||
end
|
||||
|
||||
|
|
|
@ -3,12 +3,25 @@
|
|||
require "test_helper"
|
||||
|
||||
class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase
|
||||
class ChatChannelError < Exception; end
|
||||
|
||||
class Connection < ActionCable::Connection::Base
|
||||
attr_reader :websocket
|
||||
attr_reader :websocket, :exceptions
|
||||
|
||||
rescue_from ChatChannelError, with: :error_handler
|
||||
|
||||
def initialize(*)
|
||||
super
|
||||
@exceptions = []
|
||||
end
|
||||
|
||||
def send_async(method, *args)
|
||||
send method, *args
|
||||
end
|
||||
|
||||
def error_handler(e)
|
||||
@exceptions << e
|
||||
end
|
||||
end
|
||||
|
||||
class ChatChannel < ActionCable::Channel::Base
|
||||
|
@ -22,6 +35,10 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase
|
|||
def speak(data)
|
||||
@lines << data
|
||||
end
|
||||
|
||||
def throw_exception(_data)
|
||||
raise ChatChannelError.new("Uh Oh")
|
||||
end
|
||||
end
|
||||
|
||||
setup do
|
||||
|
@ -85,6 +102,19 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
test "accessing exceptions thrown during command execution" do
|
||||
run_in_eventmachine do
|
||||
setup_connection
|
||||
subscribe_to_chat_channel
|
||||
|
||||
data = { "content" => "Hello World!", "action" => "throw_exception" }
|
||||
@subscriptions.execute_command "command" => "message", "identifier" => @chat_identifier, "data" => ActiveSupport::JSON.encode(data)
|
||||
|
||||
exception = @connection.exceptions.first
|
||||
assert_kind_of ChatChannelError, exception
|
||||
end
|
||||
end
|
||||
|
||||
test "unsubscribe from all" do
|
||||
run_in_eventmachine do
|
||||
setup_connection
|
||||
|
|
|
@ -128,6 +128,27 @@ can use this approach:
|
|||
verified_user = User.find_by(id: cookies.encrypted['_session']['user_id'])
|
||||
```
|
||||
|
||||
#### Exception Handling
|
||||
|
||||
By default, unhandled exceptions are caught and logged to Rails' logger. If you would like to
|
||||
globally intercept these exceptions and report them to an external bug tracking service, for
|
||||
example, you can do so with `rescue_with`.
|
||||
|
||||
```ruby
|
||||
# app/channels/application_cable/connection.rb
|
||||
module ApplicationCable
|
||||
class Connection < ActionCable::Connection::Base
|
||||
rescue_from StandardError, with: :report_error
|
||||
|
||||
private
|
||||
|
||||
def report_error(e)
|
||||
SomeExternalBugtrackingService.notify(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Channels
|
||||
|
||||
A *channel* encapsulates a logical unit of work, similar to what a controller does in a
|
||||
|
@ -175,6 +196,25 @@ class ChatChannel < ApplicationCable::Channel
|
|||
end
|
||||
```
|
||||
|
||||
#### Exception Handling
|
||||
|
||||
As with `ActionCable::Connection::Base`, you can also use
|
||||
[`rescue_with`](https://api.rubyonrails.org/classes/ActiveSupport/Rescuable/ClassMethods.html)
|
||||
on a specific channel to handle raised exceptions:
|
||||
|
||||
```ruby
|
||||
# app/channels/chat_channel.rb
|
||||
class ChatChannel < ApplicationCable::Channel
|
||||
rescue_from 'MyError', with: :deliver_error_message
|
||||
|
||||
private
|
||||
|
||||
def deliver_error_message(e)
|
||||
broadcast_to(...)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Client-Side Components
|
||||
|
||||
### Connections
|
||||
|
|
Loading…
Reference in New Issue