174 lines
4.3 KiB
Ruby
174 lines
4.3 KiB
Ruby
|
# encoding: BINARY
|
||
|
|
||
|
require 'rubygems'
|
||
|
require 'rspec'
|
||
|
require 'em-spec/rspec'
|
||
|
require 'em-http'
|
||
|
|
||
|
require 'em-websocket'
|
||
|
require 'em-websocket-client'
|
||
|
|
||
|
require 'integration/shared_examples'
|
||
|
require 'integration/gte_03_examples'
|
||
|
|
||
|
RSpec.configure do |c|
|
||
|
c.mock_with :rspec
|
||
|
end
|
||
|
|
||
|
class FakeWebSocketClient < EM::Connection
|
||
|
attr_reader :handshake_response, :packets
|
||
|
|
||
|
def onopen(&blk); @onopen = blk; end
|
||
|
def onclose(&blk); @onclose = blk; end
|
||
|
def onerror(&blk); @onerror = blk; end
|
||
|
def onmessage(&blk); @onmessage = blk; end
|
||
|
|
||
|
def initialize
|
||
|
@state = :new
|
||
|
@packets = []
|
||
|
end
|
||
|
|
||
|
def receive_data(data)
|
||
|
# puts "RECEIVE DATA #{data}"
|
||
|
if @state == :new
|
||
|
@handshake_response = data
|
||
|
@onopen.call if defined? @onopen
|
||
|
@state = :open
|
||
|
else
|
||
|
@onmessage.call(data) if defined? @onmessage
|
||
|
@packets << data
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def send(application_data)
|
||
|
send_frame(:text, application_data)
|
||
|
end
|
||
|
|
||
|
def send_frame(type, application_data)
|
||
|
send_data construct_frame(type, application_data)
|
||
|
end
|
||
|
|
||
|
def unbind
|
||
|
@onclose.call if defined? @onclose
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def construct_frame(type, data)
|
||
|
"\x00#{data}\xff"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class Draft03FakeWebSocketClient < FakeWebSocketClient
|
||
|
private
|
||
|
|
||
|
def construct_frame(type, data)
|
||
|
frame = ""
|
||
|
frame << EM::WebSocket::Framing03::FRAME_TYPES[type]
|
||
|
frame << encoded_length(data.size)
|
||
|
frame << data
|
||
|
end
|
||
|
|
||
|
def encoded_length(length)
|
||
|
if length <= 125
|
||
|
[length].pack('C') # since rsv4 is 0
|
||
|
elsif length < 65536 # write 2 byte length
|
||
|
"\126#{[length].pack('n')}"
|
||
|
else # write 8 byte length
|
||
|
"\127#{[length >> 32, length & 0xFFFFFFFF].pack("NN")}"
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class Draft05FakeWebSocketClient < Draft03FakeWebSocketClient
|
||
|
private
|
||
|
|
||
|
def construct_frame(type, data)
|
||
|
frame = ""
|
||
|
frame << "\x00\x00\x00\x00" # Mask with nothing for simplicity
|
||
|
frame << (EM::WebSocket::Framing05::FRAME_TYPES[type] | 0b10000000)
|
||
|
frame << encoded_length(data.size)
|
||
|
frame << data
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class Draft07FakeWebSocketClient < Draft05FakeWebSocketClient
|
||
|
private
|
||
|
|
||
|
def construct_frame(type, data)
|
||
|
frame = ""
|
||
|
frame << (EM::WebSocket::Framing07::FRAME_TYPES[type] | 0b10000000)
|
||
|
# Should probably mask the data, but I get away without bothering since
|
||
|
# the server doesn't enforce that incoming frames are masked
|
||
|
frame << encoded_length(data.size)
|
||
|
frame << data
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Wrapper around em-websocket-client
|
||
|
class Draft75WebSocketClient
|
||
|
def onopen(&blk); @onopen = blk; end
|
||
|
def onclose(&blk); @onclose = blk; end
|
||
|
def onerror(&blk); @onerror = blk; end
|
||
|
def onmessage(&blk); @onmessage = blk; end
|
||
|
|
||
|
def initialize
|
||
|
@ws = EventMachine::WebSocketClient.connect('ws://127.0.0.1:12345/',
|
||
|
:version => 75,
|
||
|
:origin => 'http://example.com')
|
||
|
@ws.errback { |err| @onerror.call if defined? @onerror }
|
||
|
@ws.callback { @onopen.call if defined? @onopen }
|
||
|
@ws.stream { |msg| @onmessage.call(msg) if defined? @onmessage }
|
||
|
@ws.disconnect { @onclose.call if defined? @onclose }
|
||
|
end
|
||
|
|
||
|
def send(message)
|
||
|
@ws.send_msg(message)
|
||
|
end
|
||
|
|
||
|
def close_connection
|
||
|
@ws.close_connection
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def start_server(opts = {})
|
||
|
EM::WebSocket.run({:host => "0.0.0.0", :port => 12345}.merge(opts)) { |ws|
|
||
|
yield ws if block_given?
|
||
|
}
|
||
|
end
|
||
|
|
||
|
def format_request(r)
|
||
|
data = "#{r[:method]} #{r[:path]} HTTP/1.1\r\n"
|
||
|
header_lines = r[:headers].map { |k,v| "#{k}: #{v}" }
|
||
|
data << [header_lines, '', r[:body]].join("\r\n")
|
||
|
data
|
||
|
end
|
||
|
|
||
|
def format_response(r)
|
||
|
data = r[:protocol] || "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
|
||
|
header_lines = r[:headers].map { |k,v| "#{k}: #{v}" }
|
||
|
data << [header_lines, '', r[:body]].join("\r\n")
|
||
|
data
|
||
|
end
|
||
|
|
||
|
RSpec::Matchers.define :succeed_with_upgrade do |response|
|
||
|
match do |actual|
|
||
|
success = nil
|
||
|
actual.callback { |upgrade_response, handler_klass|
|
||
|
success = (upgrade_response.lines.sort == format_response(response).lines.sort)
|
||
|
}
|
||
|
success
|
||
|
end
|
||
|
end
|
||
|
|
||
|
RSpec::Matchers.define :fail_with_error do |error_klass, error_message|
|
||
|
match do |actual|
|
||
|
success = nil
|
||
|
actual.errback { |e|
|
||
|
success = (e.class == error_klass)
|
||
|
success &= (e.message == error_message) if error_message
|
||
|
}
|
||
|
success
|
||
|
end
|
||
|
end
|