Faye::ClientがWebSocket経由でpublishするまで


Faye&colon;&colon;Client は gem `faye` が提供するクラス。

->publish

```ruby:Faye&colon;&colon;Client#publish
    def publish(channel, data)
      publication = Publication.new
      connect {
        info('Client ? queueing published message to ?: ?', @client_id, channel, data)

        send({
          'channel'   => channel,
          'data'      => data,
          'clientId'  => @client_id
        }) do |response|
```

->send

```ruby:Faye&colon;&colon;Transport#send
    def send(message, timeout)
      debug('Client ? sending message to ?: ?', @client.client_id, @endpoint, message)

      return request([message], timeout) unless batching?

```

->request

```ruby:Faye&colon;&colon;Transport&colon;&colon;WebSocket#request
    def request(messages, timeout = nil)
      return if messages.empty?
      @messages ||= {}
      messages.each { |message| @messages[message['id']] = message }
      callback { |socket| socket.send(Faye.to_json(messages)) }
      connect
    end

```

上記 callback は、Faye::Transport::WebSocket#connect でsocket接続が確立したときに呼び出される

```ruby:
      @socket = Faye::WebSocket::Client.new(@endpoint.gsub(/^http(s?):/, 'ws\1:'))

      @socket.onopen = lambda do |*args|
        @state = CONNECTED
        @ever_connected = true
        set_deferred_status(:succeeded, @socket)
        trigger(:up)
      end
```

Faye::WebSocket::Client は`faye-websocket`が提供するクラス。

ここから、faye-websocket

->send

```ruby:Faye&colon;&colon;WebSocket&colon;&colon;API#send&nbsp;(Faye&colon;&colon;WebSocket&colon;&colon;Client)
      def send(data, type = nil, error_type = nil)
        if @ready_state == CONNECTING
          if @send_buffer
            @send_buffer << [data, type, error_type]
            return true
          else
            raise IllegalStateError, 'Cannot call send(), socket is not open yet'
          end
        end

        return false if @ready_state == CLOSED

        data = data.to_s unless Array === data

        data = WebSocket.encode(data) if String === data
        frame = @parser.frame(data, type, error_type)
        @stream.write(frame) if frame
      end

```

@stream は Faye::WebSocket::Client#initialize で EventMachine.connect に渡すブロック内で受け取っている。

```ruby:Faye&colon;&colon;WebSocket&colon;&colon;Client#initialize
        EventMachine.connect(@uri.host, port, Connection) do |conn|
          @stream = conn
          conn.parent = self
        end
 ```

->write

```ruby:Faye&colon;&colon;WebSocket&colon;&colon;Connection#write
        def write(data)
          send_data(data) rescue nil
        end
```

`send_data`は EventMachine::Connection のメソッド
[Method: EventMachine::Connection#send_data — Documentation for eventmachine/eventmachine (master)](http://rubydoc.info/github/eventmachine/eventmachine/EventMachine/Connection:send_data)

## 補足:frame の中身を調べる

Faye::WebSocket を使ってバイナリデータが送受信できるか調べる。

@parser は Faye::WebSocket::HibiParser のインスタンス

```ruby:Faye&colon;&colon;WebSocket&colon;&colon;HibiParser
      def frame(data, type = nil, code = nil)
        return nil if @closed

        is_text = (String === data)
        opcode  = OPCODES[type || (is_text ? :text : :binary)]
        buffer  = data.respond_to?(:bytes) ? data.bytes.to_a : data
 
```

WebSocket の仕様「[RFC 6455 - The WebSocket Protocol](http://tools.ietf.org/html/rfc6455#section-5.1)」によればフレームの構造は以下のとおり。

```

      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-------+-+-------------+-------------------------------+
     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
     | |1|2|3|       |K|             |                               |
     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     |     Extended payload length continued, if payload len == 127  |
     + - - - - - - - - - - - - - - - +-------------------------------+
     |                               |Masking-key, if MASK set to 1  |
     +-------------------------------+-------------------------------+
     | Masking-key (continued)       |          Payload Data         |
     +-------------------------------- - - - - - - - - - - - - - - - +
     :                     Payload Data continued ...                :
     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
     |                     Payload Data continued ...                |
     +---------------------------------------------------------------+
```
引用元：[RFC 6455 - The WebSocket Protocol](http://tools.ietf.org/html/rfc6455)

opcode でデータの種類（テキスト・バイナリ）を判別するらしい。
よって、data の内容が String クラス以外であれば、バイナリとして送信されるっぽい。

クライアントからサーバに送信する Payload のデータは、Mask-key を使ってマスクすることになっている。

具体的には、以下のコード。

```c:ext/faye_websocket_mask/faye_websocket_mask.c
VALUE method_faye_websocket_mask(VALUE self, VALUE payload, VALUE mask) {
  int n = RARRAY_LEN(payload), i, p, m;
  VALUE unmasked = rb_ary_new2(n);

  int mask_array[] = {
    NUM2INT(rb_ary_entry(mask, 0)),
    NUM2INT(rb_ary_entry(mask, 1)),
    NUM2INT(rb_ary_entry(mask, 2)),
    NUM2INT(rb_ary_entry(mask, 3))
  };

  for (i = 0; i < n; i++) {
    p = NUM2INT(rb_ary_entry(payload, i));
    m = mask_array[i % 4];
    rb_ary_store(unmasked, i, INT2NUM(p ^ m));
  }
  return unmasked;
}
```

32bitで構成されるマスクキーを1バイトづつ4つに分割し、その値を Payload のバイト列に対して順繰りに xor を掛ける。
マスクをはずには、マスク済みのデータに対して同じことを行うことで元のバイト列が復元される。という仕組み。