108 lines
2.9 KiB
Ruby
108 lines
2.9 KiB
Ruby
|
require 'socket' # Provides TCPServer and TCPSocket classes
|
||
|
require 'digest/sha1'
|
||
|
|
||
|
class WebSocketServer
|
||
|
def initialize(host="localhost",port=2345)
|
||
|
@server=TCPServer.new(host,port)
|
||
|
end
|
||
|
|
||
|
def accept()
|
||
|
|
||
|
# Wait for a connection
|
||
|
@socket = @server.accept
|
||
|
|
||
|
# Read the HTTP request. We know it's finished when we see a line with nothing but \r\n
|
||
|
http_request = ""
|
||
|
while (line = @socket.gets) && (line != "\r\n")
|
||
|
http_request += line
|
||
|
end
|
||
|
|
||
|
# Grab the security key from the headers. If one isn't present, close the connection.
|
||
|
if matches = http_request.match(/^Sec-WebSocket-Key: (\S+)/)
|
||
|
websocket_key = matches[1]
|
||
|
puts "Websocket handshake detected with key: #{websocket_key}"
|
||
|
else
|
||
|
puts "Aborting non-websocket connection"
|
||
|
socket.close
|
||
|
return
|
||
|
end
|
||
|
|
||
|
|
||
|
response_key = Digest::SHA1.base64digest([websocket_key, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"].join)
|
||
|
puts "Responding to handshake with key: #{response_key}"
|
||
|
|
||
|
@socket.write <<-eos
|
||
|
HTTP/1.1 101 Switching Protocols
|
||
|
Upgrade: websocket
|
||
|
Connection: Upgrade
|
||
|
Sec-WebSocket-Accept: #{response_key}
|
||
|
|
||
|
eos
|
||
|
|
||
|
puts "Handshake completed."
|
||
|
end
|
||
|
|
||
|
def recv()
|
||
|
first_byte = @socket.getbyte
|
||
|
fin = first_byte & 0b10000000
|
||
|
opcode = first_byte & 0b00001111
|
||
|
fin = fin >> 7
|
||
|
fin = (fin ? true : false)
|
||
|
puts "Got frame. FIN:#{fin}. Opcode:#{opcode}"
|
||
|
raise "We don't support continuations" unless fin
|
||
|
unless opcode == 1 or opcode == 8
|
||
|
puts "We only support text data and close frame"
|
||
|
send("",3)
|
||
|
end
|
||
|
if opcode == 1
|
||
|
second_byte = @socket.getbyte
|
||
|
is_masked = second_byte & 0b10000000
|
||
|
payload_size = second_byte & 0b01111111
|
||
|
|
||
|
raise "All incoming frames should be masked according to the websocket spec" unless is_masked
|
||
|
raise "We only support payloads < 126 bytes in length" unless payload_size < 126
|
||
|
|
||
|
puts "Payload size: #{payload_size} bytes"
|
||
|
|
||
|
mask = 4.times.map {@socket.getbyte}
|
||
|
puts "Got mask: #{mask.inspect}"
|
||
|
|
||
|
data = payload_size.times.map {@socket.getbyte}
|
||
|
puts "Got masked data: #{data.inspect}"
|
||
|
|
||
|
unmasked_data = data.each_with_index.map { |byte, i| byte ^ mask[i % 4] }
|
||
|
puts "Unmasked the data: #{unmasked_data.inspect}"
|
||
|
|
||
|
string=unmasked_data.pack('C*').force_encoding('utf-8')
|
||
|
puts "Converted to a string: #{string}"
|
||
|
|
||
|
return string
|
||
|
elsif opcode == 8
|
||
|
send("",0)
|
||
|
return false
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def send(message,closetype=false)
|
||
|
if closetype
|
||
|
puts "Sending close frame"
|
||
|
output=[0x88,2,3,0xe8+closetype]
|
||
|
@socket.write output.pack("C*")
|
||
|
@socket.close
|
||
|
else
|
||
|
puts "Sending #{message}"
|
||
|
output = [0x81, message.size, message]
|
||
|
@socket.write output.pack("CCA#{message.size}")
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
serv=WebSocketServer.new
|
||
|
serv.accept
|
||
|
while true
|
||
|
message=serv.recv
|
||
|
break if message==false
|
||
|
puts message
|
||
|
serv.send("Got #{message}")
|
||
|
end
|