Class: RfcWebSocket::WebSocket
- Inherits:
-
Object
- Object
- RfcWebSocket::WebSocket
- 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
- #close(code = 1000, msg = nil) ⇒ Object
- #closed? ⇒ Boolean
-
#initialize(uri, protocol = "") ⇒ WebSocket
constructor
A new instance of WebSocket.
- #receive ⇒ Object
- #send_message(message, opts = {binary: false}) ⇒ Object
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.new(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.new(e.to_s) end |
#closed? ⇒ Boolean
185 186 187 |
# File 'lib/rfc-ws-client.rb', line 185 def closed? @socket.closed? end |
#receive ⇒ Object
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.new(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 (, opts = {binary: false}) write(encode(, opts[:binary] ? OPCODE_BINARY : OPCODE_TEXT)) rescue WebSocketError raise rescue => e raise WebSocketError.new(e.to_s) end |