Class: RfcWebSocket::WebSocket

Inherits:
Object
  • Object
show all
Defined in:
lib/rfc-ws-client.rb

Constant Summary collapse

WEB_SOCKET_GUID =
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
OPCODE_CONTINUATION =
0x00
OPCODE_TEXT =
0x01
OPCODE_BINARY =
0x02
OPCODE_CLOSE =
0x08
OPCODE_PING =
0x09
OPCODE_PONG =
0x0a

Instance Method Summary collapse

Constructor Details

#initialize(uri, protocol = "") ⇒ WebSocket

Returns a new instance of WebSocket.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/rfc-ws-client.rb', line 29

def initialize(uri, protocol = "")
  uri = URI.parse(uri) unless uri.is_a?(URI)
  @protocol = protocol

  if uri.scheme == "ws"
    default_port = 80
  elsif uri.scheme = "wss"
    default_port = 443
  else
    raise WebSocketError.new("unsupported scheme: #{uri.scheme}")
  end
  host = uri.host + ((!uri.port || uri.port == default_port) ? "" : ":#{uri.port}")
  path = (uri.path.empty? ? "/" : uri.path) + (uri.query ? "?" + uri.query : "")

  @socket = TCPSocket.new(uri.host, uri.port || default_port)
  if uri.scheme == "wss"
    @socket = OpenSSL::SSL::SSLSocket.new(@socket)
    @socket.sync_close = true
    @socket.connect
  end

  request_key = SecureRandom::base64(16)
  write(handshake(host, path, request_key))
  flush()

  status_line = @socket.gets.chomp
  raise WebSocketError.new("bad response: #{status_line}") unless status_line.start_with?("HTTP/1.1 101")

  header = {}
  while line = @socket.gets
    line.chomp!
    break if line.empty?
    if !(line =~ /\A(\S+): (.*)\z/n)
      raise WebSocketError.new("invalid response: #{line}")
    end
    header[$1.downcase] = $2
  end
  raise WebSocketError.new("upgrade missing") unless header["upgrade"]
  raise WebSocketError.new("connection missing") unless header["connection"]
  accept = header["sec-websocket-accept"]
  raise WebSocketError.new("sec-websocket-accept missing") unless accept
  expected_accept = Digest::SHA1.base64digest(request_key + WEB_SOCKET_GUID)
  raise WebSocketError.new("sec-websocket-accept is invalid, actual: #{accept}, expected: #{expected_accept}") unless accept == expected_accept
rescue WebSocketError
  raise
rescue => e
  raise WebSocketError(e.to_s)
end

Instance Method Details

#close(code = 1000, msg = nil) ⇒ Object



176
177
178
179
180
181
182
183
# File 'lib/rfc-ws-client.rb', line 176

def close(code = 1000, msg = nil)
  write(encode [code ? code : 1000, msg].pack("nA*"), OPCODE_CLOSE)
  @socket.close
rescue WebSocketError
  raise
rescue => e
  raise WebSocketError(e.to_s)
end

#receiveObject



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/rfc-ws-client.rb', line 86

def receive
  buffer = ""
  fragmented = nil
  # Loop until something returns
  while true
    b1, b2 = read(2).unpack("CC")
    # first byte
    fin = (b1 & 0x80) != 0
    raise WebSocketError.new("reserved bits must be 0") if (b1 & 0b01110000) != 0
    opcode = b1 & 0x0f
    # second byte
    mask = (b2 & 0x80) != 0
    # we're a client
    raise WebSocketError.new("server->client must not be masked!") if mask
    length = b2 & 0x7f
    if opcode > 7
      raise WebSocketError.new("control frame cannot be fragmented") unless fin
      raise WebSocketError.new("control frame is too large: #{length}") if length > 125
      raise WebSocketError.new("unexpected reserved opcode: #{opcode}") if opcode > 0xA
      raise WebSocketError.new("close frame with payload length 1") if length == 1 and opcode == OPCODE_CLOSE
    elsif opcode != OPCODE_CONTINUATION && opcode != OPCODE_TEXT && opcode != OPCODE_BINARY
      raise WebSocketError.new("unexpected reserved opcode: #{opcode}")
    end
    # extended payload length
    if length == 126
      length = read(2).unpack("n")[0]
    elsif length == 127
      high, low = *read(8).unpack("NN")
      length = high * (2 ** 32) + low
    end
    # payload
    payload = read(length)
    case opcode
    when OPCODE_CONTINUATION
      raise WebSocketError.new("no frame to continue") unless fragmented
      if fragmented == :binary
        buffer << payload
      else
        buffer << payload.force_encoding("UTF-8")
      end
      if fin
        raise WebSocketError.new("invalid utf8", 1007) if fragmented == :text and !valid_utf8?(buffer)
        return buffer, fragmented == :binary
      else
        next
      end
    when OPCODE_TEXT
      raise WebSocketError.new("unexpected opcode in continuation mode") if fragmented
      if !fin
        fragmented = :text
        buffer << payload.force_encoding("UTF-8")
        next
      else
        raise WebSocketError.new("invalid utf8", 1007) unless valid_utf8?(payload)
        return payload, false
      end
    when OPCODE_BINARY
      raise WebSocketError.new("unexpected opcode in continuation mode") if fragmented
      if !fin
        fragmented = :binary
        buffer << payload
      else
        return payload, true
      end
    when OPCODE_CLOSE
      code, explain = payload.unpack("nA*")
      if explain && !valid_utf8?(explain)
        close(1007)
      else
        close(response_close_code(code))
      end
      return nil, nil
    when OPCODE_PING
      write(encode(payload, OPCODE_PONG))
      next
    when OPCODE_PONG
      next
    else
      raise WebSocketError.new("received unknown opcode: #{opcode}")
    end
  end
rescue EOFError
  return nil, nil
rescue WebSocketError => e
  close(e.code)
  raise e
rescue => e
  raise WebSocketError(e.to_s)
end

#send_message(message, opts = {binary: false}) ⇒ Object



78
79
80
81
82
83
84
# File 'lib/rfc-ws-client.rb', line 78

def send_message(message, opts = {binary: false})
  write(encode(message, opts[:binary] ? OPCODE_BINARY : OPCODE_TEXT))
rescue WebSocketError
  raise
rescue => e
  raise WebSocketError(e.to_s)
end