ActionCableを読み解く（cable server 編）

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

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

## ActionCable.server

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

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

```ruby
# 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として動作する．

```ruby
# 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_timer`は`ActionCable::Server::Connection` moduleに定義されている．[EventMachine][eventmachine]で処理をスケジューリングしている．`connections`の各要素の`beat`メソッドを叩いているようだ．ここは後で見ていくことにする．

```ruby
# 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`を呼んでいるらしい．

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

`config`は`cattr_accessor`で宣言されている．`ActionCable::Server::Configuration`のインスタンスらしい．

```ruby
# 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`であることがわかる．

```ruby
# 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`メソッドが要求する返り値を返す．

```ruby
# 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::WebSocket`は`Faye::WebSocket`のラッパになっており，主要なメソッド（？）は大抵delegateされる．

```ruby
# 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:）

* [`ActionCable::Connection::Base#on_message`](https://github.com/rails/rails/blob/v5.0.0.beta1/actioncable/lib/action_cable/connection/base.rb#L158-L160)
  - とりあえずバッファに突っ込む
  - [`ActionCable::Connection::MessageBuffer#append`](https://github.com/rails/rails/blob/v5.0.0.beta1/actioncable/lib/action_cable/connection/message_buffer.rb#L11-L21)
  - [`ActionCable::Connection::MessageBuffer#receive`](https://github.com/rails/rails/blob/v5.0.0.beta1/actioncable/lib/action_cable/connection/message_buffer.rb#L41-L43)
  - バッファからConnectionに投げ返す
* [`ActionCable::Connection::Base#receive`](https://github.com/rails/rails/blob/v5.0.0.beta1/actioncable/lib/action_cable/connection/base.rb#L87-L93)
* [`ActionCable::Connection::Subscriptions#execute_command`](https://github.com/rails/rails/blob/v5.0.0.beta1/actioncable/lib/action_cable/connection/subscriptions.rb#L13-L23)
  - `data['command'] == 'subscribe'`
      - このタイミングで`ActionCable::Channels::Base`のインスタンスを作っておく
      - [`ActionCable::Connection::Subscriptions#add`](https://github.com/rails/rails/blob/v5.0.0.beta1/actioncable/lib/action_cable/connection/subscriptions.rb#L25-L36)
  - `data['command'] == 'message'`
      - [`ActionCable::Connection::Subscriptions#perform_action`](https://github.com/rails/rails/blob/v5.0.0.beta1/actioncable/lib/action_cable/connection/subscriptions.rb#L48-L50)
      - [`ActionCable::Channel::Base#perform_action`](https://github.com/rails/rails/blob/v5.0.0.beta1/actioncable/lib/action_cable/channel/base.rb#L150-L158)
      - `ActionCable::Channel::Base`サブクラスのメソッドを起動する

## References
* [Rails 5 + ActionCableで作る！シンプルなチャットアプリ（DHH氏のデモ動画より） - Qiita][jnchito-ac]

* [eventmachine/eventmachine][eventmachine]
* [faye/faye-websocket-ruby][faye]

[jnchito-ac]: http://qiita.com/jnchito/items/aec75fab42804287d71b
[eventmachine]: https://github.com/eventmachine/eventmachine
[faye]: https://github.com/faye/faye-websocket-ruby