require 'rubygems' require 'rspec' require 'em-spec/rspec' require 'pp' require 'em-http' require 'em-websocket' 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 @onopen @state = :open else @onmessage.call(data) if @onmessage @packets << data end end def send(data) send_data("\x00#{data}\xff") end def unbind @onclose.call if @onclose end end class Draft03FakeWebSocketClient < FakeWebSocketClient def send(application_data) frame = '' opcode = 4 # fake only supports text frames byte1 = opcode # since more, rsv1-3 are 0 frame << byte1 length = application_data.size if length <= 125 byte2 = length # since rsv4 is 0 frame << byte2 elsif length < 65536 # write 2 byte length frame << 126 frame << [length].pack('n') else # write 8 byte length frame << 127 frame << [length >> 32, length & 0xFFFFFFFF].pack("NN") end frame << application_data send_data(frame) end end class Draft07FakeWebSocketClient < FakeWebSocketClient def send(application_data) frame = '' opcode = 1 # fake only supports text frames byte1 = opcode | 0b10000000 # since more, rsv1-3 are 0 frame << byte1 mask = 0b10000000 length = application_data.size if length <= 125 byte2 = length # since rsv4 is 0 frame << (mask | byte2) elsif length < 65536 # write 2 byte length frame << (mask | 126) frame << [length].pack('n') else # write 8 byte length frame << (mask | 127) frame << [length >> 32, length & 0xFFFFFFFF].pack("NN") end frame << EventMachine::WebSocket::MaskedString.create_masked_string(application_data) send_data(frame) end end # Wrap EM:HttpRequest in a websocket like interface so that it can be used in the specs with the same interface as FakeWebSocketClient 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::HttpRequest.new('ws://127.0.0.1:12345/').get(:timeout => 0) @ws.errback { @onerror.call if @onerror } @ws.callback { @onopen.call if @onopen } @ws.stream { |msg| @onmessage.call(msg) if @onmessage } end def send(message) @ws.send(message) end def close_connection @ws.close_connection end 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 def handler(request, secure = false) connection = Object.new EM::WebSocket::HandlerFactory.build(connection, format_request(request), secure) end RSpec::Matchers.define :send_handshake do |response| match do |actual| actual.handshake.lines.sort == format_response(response).lines.sort end end