@ledsun blog

無味の味は佳境に入らざればすなわち知れず

DRb Websocket protocolの準備をする

RubyでWebSocketライブラリ - @ledsun blogで、RubyのWebSocketクラスができました。 これをdRubyに組み込みたいのです。 が、足りないAPIがあります。

例えば

https://github.com/ruby/drb/blob/69c2ef531f08a0874908a4306c014b325070e1fe/lib/drb/drb.rb#L987

readables, = IO.select([@socket, @shutdown_pipe_r])

です。 dRubyサーバーとして動いたときに、クライアントからの接続とCtrl + Cによる強制終了を両方待てるようにしています。 このときIO.selectの引数になります。 このためto_ioメソッドを実装しなくてはいけません。 to_ioメソッドはTCPSocketクラスが実装しています。 委譲すれば終わりです。

require 'socket'
require 'forwardable'
require 'protocol/websocket/headers'
require 'protocol/websocket/framer'
require 'protocol/websocket/text_frame'
require_relative 'upgrade_request'
require_relative 'http_response'

module WANDS
  # This is a class that represents WebSocket, which has the same interface as TCPSocket.
  #
  # The WebSocket class is responsible for reading and writing messages to the server.
  #
  # Example usage:
  #
  # web_socket = WebSocket.open('localhost', 2345)
  # web_socket.write("Hello World!")
  #
  # puts web_socket.gets
  #
  # web_socket.close
  #
  class WebSocket
    include Protocol::WebSocket::Headers
    extend Forwardable

    attr_reader :remote_address
    def_delegators :@socket, :close, :to_io

    def self.open(host, port)
      socket = TCPSocket.new('localhost', 2345)
      request = UpgradeRequest.new
      socket.write(request.to_s)
      socket.flush

      response = HTTPResponse.new
      response.parse(socket)

      request.verify response

      self.new(socket)
    end

    def initialize(socket)
      @socket = socket
      @remote_address = socket.remote_address
    end

    # @return [String]
    def gets
      framer = Protocol::WebSocket::Framer.new(@socket)
      frame = framer.read_frame
      raise 'frame is not a text' unless frame.is_a? Protocol::WebSocket::TextFrame
      frame.unpack
    end

    def write(message)
      frame = Protocol::WebSocket::TextFrame.new(true, message)
      frame.write(@socket)
    end
  end
end