LoginSignup
35
37

More than 5 years have passed since last update.

ActionCableを読み解く(cable server 編)

Last updated at Posted at 2015-12-25

Introduction

Socket.ioもびっくりのスーパー抽象化がなされたActionCableについて,実装を覗いて理解を深めましょう.

なお,Rails 5-beta1を参考にしていきます.

ActionCable.server

Rails.application.routes.draw do
  mount ActionCable.server => '/cable'
end

ActionCable.serverの実装.ActionCable::Server::Baseのインスタンスを返しているだけ.

# https://github.com/rails/rails/blob/v5.0.0.beta1/actioncable/lib/action_cable.rb#L42-L44
module ActionCable
  module_function def server
    @server ||= ActionCable::Server::Base.new
  end
end

ActionCable::Server

ActionCable::Server::Base#call(env)を実装しているので,mountable engineとして動作する.

# https://github.com/rails/rails/blob/v5.0.0.beta1/actioncable/lib/action_cable/server/base.rb#L25-L28
class ActionCable::Server::Base
  def call(env)
    setup_heartbeat_timer
    config.connection_class.new(self, env).process
  end
end

setup_heartbeat_timerActionCable::Server::Connection moduleに定義されている.EventMachineで処理をスケジューリングしている.connectionsの各要素のbeatメソッドを叩いているようだ.ここは後で見ていくことにする.

# https://github.com/rails/rails/blob/v5.0.0.beta1/actioncable/lib/action_cable/server/connections.rb#L24-L30
module ActionCable::Server::Connection
  def setup_heartbeat_timer
    EM.next_tick do
      @heartbeat_timer ||= EventMachine.add_periodic_timer(BEAT_INTERVAL) do
        EM.next_tick { connections.map(&:beat) }
      end
    end
  end
end

ActionCable::Server::Base#initializeに戻る.2行目には次のようなコードがある.config.connection_classのインスタンスを作ってからprocessを呼んでいるらしい.

config.connection_class.new(self, env).process

configcattr_accessorで宣言されている.ActionCable::Server::Configurationのインスタンスらしい.

# https://github.com/rails/rails/blob/v5.0.0.beta1/actioncable/lib/action_cable/server/base.rb#L16
cattr_accessor(:config, instance_accessor: true) { ActionCable::Server::Configuration.new }

ActionCable::Server::Configurationを見てみると,connection_classのなかみはApplicationCable::Connectionであることがわかる.

# https://github.com/rails/rails/blob/v5.0.0.beta1/actioncable/lib/action_cable/server/configuration.rb#L15
class ActionCable::Server::Configuration
  attr_accessor :connection_class

  def initialize
    @connection_class  = ApplicationCable::Connection
  end
end

ActionCable::Connection

ActionCable::Connection::Base#processではWebSocketの各種イベントのコールバックを登録している.ちゃんと接続できたらMountable Engineのcallメソッドが要求する返り値を返す.

# https://github.com/rails/rails/blob/master/actioncable/lib/action_cable/connection/base.rb#L71-L83
class ActionCable::Connection::Base
  def initialize
    @websocket = ActionCable::Connection::WebSocket.new(env)
  end

  def process
    logger.info started_request_message

    if websocket.possible? && allow_request_origin?
      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
end

ActionCable::Connection::WebSocketFaye::WebSocketのラッパになっており,主要なメソッド(?)は大抵delegateされる.

# https://github.com/rails/rails/blob/v5.0.0.beta1/actioncable/lib/action_cable/connection/web_socket.rb#L7-L11
class ActionCable::Connection::WebSocket
  delegate :rack_response, :close, :on, to: :websocket

  def initialize(env)
    @websocket = Faye::WebSocket.websocket?(env) ? Faye::WebSocket.new(env) : nil
   end
end

メッセージ受信時の流れ

めんどくさくなってきたのでざっくり(そのうちちゃんと書きます:bow:

References

35
37
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
37