2
require 'websocket/driver'
4
module Capybara::Poltergeist
5
# This is a 'custom' Web Socket server that is designed to be synchronous. What
6
# this means is that it sends a message, and then waits for a response. It does
7
# not expect to receive a message at any other time than right after it has sent
8
# a message. So it is basically operating a request/response cycle (which is not
9
# how Web Sockets are usually used, but it's what we want here, as we want to
10
# send a message to PhantomJS and then wait for it to respond).
12
# How much to try to read from the socket at once (it's kinda arbitrary because we
13
# just keep reading until we've received a full frame)
16
# How many seconds to try to bind to the port for before failing
21
attr_reader :port, :driver, :socket, :server
22
attr_accessor :timeout
24
def initialize(port = nil, timeout = nil)
26
@server = start_server(port)
27
@receive_mutex = Mutex.new
30
def start_server(port)
34
TCPServer.open(HOST, port || 0).tap do |server|
35
@port = server.addr[1]
37
rescue Errno::EADDRINUSE
38
if (Time.now - time) < BIND_TIMEOUT
51
# Accept a client on the TCP server socket, then receive its initial HTTP request
52
# and use that to initialize a Web Socket.
54
@socket = server.accept
57
@driver = ::WebSocket::Driver.server(self)
58
@driver.on(:connect) { |event| @driver.start }
59
@driver.on(:message) do |event|
60
command_id = JSON.load(event.data)['command_id']
61
@messages[command_id] = event.data
69
# Block until the next message is available from the Web Socket.
70
# Raises Errno::EWOULDBLOCK if timeout is reached.
71
def receive(cmd_id, receive_timeout=nil)
72
receive_timeout ||= timeout
75
until @messages.has_key?(cmd_id)
76
raise Errno::EWOULDBLOCK if (Time.now - start) >= receive_timeout
77
if @receive_mutex.try_lock
79
IO.select([socket], [], [], receive_timeout) or raise Errno::EWOULDBLOCK
80
data = socket.recv(RECV_SIZE)
90
@messages.delete(cmd_id)
93
# Send a message and block until there is a response
94
def send(cmd_id, message, accept_timeout=nil)
95
accept unless connected?
97
receive(cmd_id, accept_timeout)
98
rescue Errno::EWOULDBLOCK
99
raise TimeoutError.new(message)
102
# Closing sockets separately as `close_read`, `close_write`
103
# causes IO mistakes on JRuby, using just `close` fixes that.
105
[server, socket].compact.each(&:close)