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, custom_host = nil) ⇒ WebSocketServer

Returns a new instance of WebSocketServer.



26
27
28
29
30
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 26

def initialize(port = nil, timeout = nil, custom_host = nil)
  @timeout = timeout
  @server  = start_server(port, custom_host)
  @receive_mutex = Mutex.new
end

Instance Attribute Details

#driverObject (readonly)

Returns the value of attribute driver.



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

def driver
  @driver
end

#hostObject (readonly)

Returns the value of attribute host.



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

def host
  @host
end

#portObject (readonly)

Returns the value of attribute port.



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

def port
  @port
end

#serverObject (readonly)

Returns the value of attribute server.



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

def server
  @server
end

#socketObject (readonly)

Returns the value of attribute socket.



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

def socket
  @socket
end

#timeoutObject

Returns the value of attribute timeout.



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

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.



56
57
58
59
60
61
62
63
64
65
66
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 56

def accept
  @socket   = server.accept
  @messages = {}

  @driver = ::WebSocket::Driver.server(self)
  @driver.on(:connect) { |event| @driver.start }
  @driver.on(:message) do |event|
    command_id = JSON.load(event.data)['command_id']
    @messages[command_id] = event.data
  end
end

#closeObject

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



107
108
109
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 107

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

#connected?Boolean

Returns:

  • (Boolean)


50
51
52
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 50

def connected?
  !socket.nil?
end

#receive(cmd_id, receive_timeout = nil) ⇒ Object

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



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 74

def receive(cmd_id, receive_timeout=nil)
  receive_timeout ||= timeout
  start = Time.now

  until @messages.has_key?(cmd_id)
    raise Errno::EWOULDBLOCK if (Time.now - start) >= receive_timeout
    if @receive_mutex.try_lock
      begin
        IO.select([socket], [], [], receive_timeout) or raise Errno::EWOULDBLOCK
        data = socket.recv(RECV_SIZE)
        break if data.empty?
        driver.parse(data)
      ensure
        @receive_mutex.unlock
      end
    else
      sleep(0.05)
    end
  end
  @messages.delete(cmd_id)
end

#send(cmd_id, message, accept_timeout = nil) ⇒ Object

Send a message and block until there is a response



97
98
99
100
101
102
103
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 97

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

#start_server(port, custom_host) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 32

def start_server(port, custom_host)
  time = Time.now

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

#write(data) ⇒ Object



68
69
70
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 68

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