Class: ActionCable::Connection::Base

Inherits:
Object
  • Object
show all
Includes:
Authorization, Identification, InternalChannel
Defined in:
lib/action_cable/connection/base.rb

Overview

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 authentication and authorization.

Here's a basic example:

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
      logger.add_tags current_user.name
    end

    def disconnect
      # Any cleanup work needed when the cable connection is cut.
    end

    protected
      def find_verified_user
        if current_user = User.find_by_identity cookies.signed[:identity_id]
          current_user
        else
          reject_unauthorized_connection
        end
      end
  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 identification indexes as you like. Declaring an identification means that a 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 it easy to use signed cookies that were set when logging in via a web interface to authorize the websocket connection.

Finally, we add a tag to the connection-specific logger with name of the current user to easily distinguish their messages in the log.

Pretty simple, eh?

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Identification

#connection_identifier

Constructor Details

#initialize(server, env) ⇒ Base


56
57
58
59
60
61
62
63
64
65
66
# File 'lib/action_cable/connection/base.rb', line 56

def initialize(server, env)
  @server, @env = server, env

  @logger = new_tagged_logger

  @websocket      = ActionCable::Connection::WebSocket.new(env)
  @subscriptions  = ActionCable::Connection::Subscriptions.new(self)
  @message_buffer = ActionCable::Connection::MessageBuffer.new(self)

  @started_at = Time.now
end

Instance Attribute Details

#envObject (readonly)

Returns the value of attribute env


51
52
53
# File 'lib/action_cable/connection/base.rb', line 51

def env
  @env
end

#loggerObject (readonly)

Returns the value of attribute logger


54
55
56
# File 'lib/action_cable/connection/base.rb', line 54

def logger
  @logger
end

#serverObject (readonly)

Returns the value of attribute server


51
52
53
# File 'lib/action_cable/connection/base.rb', line 51

def server
  @server
end

Instance Method Details

#beatObject


117
118
119
# File 'lib/action_cable/connection/base.rb', line 117

def beat
  transmit({ identifier: '_ping', message: Time.now.to_i }.to_json)
end

#closeObject

Close the websocket connection.


101
102
103
104
# File 'lib/action_cable/connection/base.rb', line 101

def close
  logger.error "Closing connection"
  websocket.close
end

#processObject

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.


70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/action_cable/connection/base.rb', line 70

def process
  logger.info started_request_message

  if websocket.possible?
    websocket.on(:open)    { |event| send_async :on_open   }
    websocket.on(:message) { |event| on_message event.data }
    websocket.on(:close)   { |event| send_async :on_close  }

    respond_to_successful_request
  else
    respond_to_invalid_request
  end
end

#receive(data_in_json) ⇒ Object

Data received over the cable 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.


86
87
88
89
90
91
92
# File 'lib/action_cable/connection/base.rb', line 86

def receive(data_in_json)
  if websocket.alive?
    subscriptions.execute_command ActiveSupport::JSON.decode(data_in_json)
  else
    logger.error "Received data without a live websocket (#{data.inspect})"
  end
end

#send_async(method, *arguments) ⇒ Object

Invoke a method on the connection asynchronously through the pool of thread workers.


107
108
109
# File 'lib/action_cable/connection/base.rb', line 107

def send_async(method, *arguments)
  worker_pool.async.invoke(self, method, *arguments)
end

#statisticsObject

Return a basic hash of statistics for the connection keyed with `identifier`, `started_at`, and `subscriptions`. This can be returned by a health check against the connection.


113
114
115
# File 'lib/action_cable/connection/base.rb', line 113

def statistics
  { identifier: connection_identifier, started_at: @started_at, subscriptions: subscriptions.identifiers }
end

#transmit(data) ⇒ Object

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.


96
97
98
# File 'lib/action_cable/connection/base.rb', line 96

def transmit(data)
  websocket.transmit data
end