@ledsun blog

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

RubyでWebSocketクライアントを書く その3

RubyでWebSocketクライアントを書く その2 - @ledsun blog でWebSocketコネクションが確立できました。 今回はWebSocketを使ってサーバーとメッセージを送受信します。

#!/usr/bin/env ruby
# frozen_string_literal: true

require "erb"
require "socket"
require_relative "protocol_websocket_server/web_socket"

URL = "ws://localhost:2345/"

module WANDS
  class HTTPResponse
    attr_reader :header

    def parse(stream)
      @response_string = read(stream)
      parse_response_string(@response_string)
    end

    def to_s
      @response_string
    end

    private

    def read(stream)
      response_string = ""
      while (line = stream.gets) != "\r\n"
        response_string += line
      end

      response_string
    end

    def parse_response_string(response_string)
      headers, _body = response_string.split("\r\n\r\n", 2)
      headers_lines = headers.split("\r\n")
      _status_line = headers_lines.shift
      @header = headers_lines.map do |line|
        key, value = line.split(': ', 2)
        [key.downcase, [value]]
      end.to_h
    end
  end

  class UpgradeWebSocketRequest
    include ::Protocol::WebSocket::Headers

    TEMPLATE = <<~REQUEST
      GET / HTTP/1.1
      Host: <%= @host %>:<%= @port %>
      Connection: Upgrade
      Upgrade: websocket
      Sec-WebSocket-Version: 13
      Sec-WebSocket-Key: <%= @key %>

    REQUEST

    def initialize(host, port)
      @erb = ERB.new(TEMPLATE)
      @host = host
      @port = port
      @key = Nounce.generate_key
    end

    def to_s
      @erb.result(binding).gsub(/\r?\n/, "\r\n")
    end

    def verify(response)
      accept_digest = response.header[SEC_WEBSOCKET_ACCEPT].first
      accept_digest == Nounce.accept_digest(@key) || raise("Invalid accept digest")
    end
  end
end

request = WANDS::UpgradeWebSocketRequest.new('localhost', 2345)
puts request.to_s

socket = TCPSocket.new('localhost', 2345)
socket.write(request.to_s)
socket.flush

puts 'Request sent'

response = WANDS::HTTPResponse.new
response.parse(socket)

puts "Response: #{response.to_s}"

request.verify response

web_socket = WebSocket.new(socket)
web_socket.write("Hello World!")

puts web_socket.gets

socket.close

RubyでつくるWebSocketサーバーにprotocol-websocket gemを取り入れる その6 - @ledsun blog で作成したWebSocketクラスがそのままつかえました。 これでPure RubyなTCPSocketライクなインターフェースを持つWebSocket用のクラスが作成できそうです。