Class: Capybara::Poltergeist::WebSocketServer

Inherits:
Object
  • Object
show all
Defined in:
lib/capybara/poltergeist/web_socket_server.rb

Overview

This is a 'custom' Web Socket server that is designed to be synchronous. What this means is that it sends a message, and then waits for a response. It does not expect to receive a message at any other time than right after it has sent a message. So it is basically operating a request/response cycle (which is not how Web Sockets are usually used, but it's what we want here, as we want to send a message to PhantomJS and then wait for it to respond).

Constant Summary collapse

RECV_SIZE =

How much to try to read from the socket at once (it's kinda arbitrary because we just keep reading until we've received a full frame)

1024
BIND_TIMEOUT =

How many seconds to try to bind to the port for before failing

5
HOST =
'127.0.0.1'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(port = nil, timeout = nil) ⇒ WebSocketServer

Returns a new instance of WebSocketServer.


24
25
26
27
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 24

def initialize(port = nil, timeout = nil)
  @timeout = timeout
  @server  = start_server(port)
end

Instance Attribute Details

#driverObject (readonly)

Returns the value of attribute driver


21
22
23
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 21

def driver
  @driver
end

#portObject (readonly)

Returns the value of attribute port


21
22
23
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 21

def port
  @port
end

#serverObject (readonly)

Returns the value of attribute server


21
22
23
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 21

def server
  @server
end

#socketObject (readonly)

Returns the value of attribute socket


21
22
23
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 21

def socket
  @socket
end

#timeoutObject

Returns the value of attribute timeout


22
23
24
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 22

def timeout
  @timeout
end

Instance Method Details

#acceptObject

Accept a client on the TCP server socket, then receive its initial HTTP request and use that to initialize a Web Socket.


52
53
54
55
56
57
58
59
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 52

def accept
  @socket   = server.accept
  @messages = []

  @driver = ::WebSocket::Driver.server(self)
  @driver.on(:connect) { |event| @driver.start }
  @driver.on(:message) { |event| @messages << event.data }
end

#closeObject

Closing sockets separately as `close_read`, `close_write` causes IO mistakes on JRuby, using just `close` fixes that.


92
93
94
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 92

def close
  [server, socket].compact.each(&:close)
end

#connected?Boolean

Returns:

  • (Boolean)

46
47
48
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 46

def connected?
  !socket.nil?
end

#receiveObject

Block until the next message is available from the Web Socket. Raises Errno::EWOULDBLOCK if timeout is reached.


67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 67

def receive
  start = Time.now

  until @messages.any?
    raise Errno::EWOULDBLOCK if (Time.now - start) >= timeout
    IO.select([socket], [], [], timeout) or raise Errno::EWOULDBLOCK
    data = socket.recv(RECV_SIZE)
    break if data.empty?
    driver.parse(data)
  end

  @messages.shift
end

#send(message) ⇒ Object

Send a message and block until there is a response


82
83
84
85
86
87
88
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 82

def send(message)
  accept unless connected?
  driver.text(message)
  receive
rescue Errno::EWOULDBLOCK
  raise TimeoutError.new(message)
end

#start_server(port) ⇒ Object


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 29

def start_server(port)
  time = Time.now

  begin
    TCPServer.open(HOST, port || 0).tap do |server|
      @port = server.addr[1]
    end
  rescue Errno::EADDRINUSE
    if (Time.now - time) < BIND_TIMEOUT
      sleep(0.01)
      retry
    else
      raise
    end
  end
end

#write(data) ⇒ Object


61
62
63
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 61

def write(data)
  @socket.write(data)
end