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).

Defined Under Namespace

Classes: FayeHandler

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.



65
66
67
68
69
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 65

def initialize(port = nil, timeout = nil)
  @timeout = timeout
  @parser  = Http::Parser.new
  @server  = start_server(port)
end

Instance Attribute Details

#handlerObject (readonly)

Returns the value of attribute handler.



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

def handler
  @handler
end

#parserObject (readonly)

Returns the value of attribute parser.



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

def parser
  @parser
end

#portObject (readonly)

Returns the value of attribute port.



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

def port
  @port
end

#serverObject (readonly)

Returns the value of attribute server.



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

def server
  @server
end

#socketObject (readonly)

Returns the value of attribute socket.



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

def socket
  @socket
end

#timeoutObject

Returns the value of attribute timeout.



63
64
65
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 63

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.



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

def accept
  @socket = server.accept

  while msg = socket.gets
    parser << msg
    break if msg == "\r\n"
  end

  @handler = FayeHandler.new(self, env)
  socket.write handler.handshake_response
end

#closeObject



155
156
157
158
159
160
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 155

def close
  [server, socket].compact.each do |s|
    s.close_read
    s.close_write
  end
end

#connected?Boolean

Returns:

  • (Boolean)


90
91
92
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 90

def connected?
  !socket.nil?
end

#envObject

Note that the socket.read(8) assumes we’re using the hixie-76 parser. This is fine for now as it corresponds to the version of Web Sockets that the version of WebKit in PhantomJS uses, but it might need to change in the future.



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 111

def env
  @env ||= begin
    env = {
      'REQUEST_METHOD' => parser.http_method,
      'SCRIPT_NAME'    => '',
      'PATH_INFO'      => '',
      'QUERY_STRING'   => '',
      'SERVER_NAME'    => '127.0.0.1',
      'SERVER_PORT'    => port.to_s,
      'HTTP_ORIGIN'    => 'http://127.0.0.1:2000/',
      'rack.input'     => StringIO.new(socket.read(8))
    }
    parser.headers.each do |header, value|
      env['HTTP_' + header.upcase.gsub('-', '_')] = value
    end
    env
  end
end

#receiveObject

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



132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 132

def receive
  start = Time.now

  until handler.message?
    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?
    handler.parse(data)
  end

  handler.next_message
end

#send(message) ⇒ Object

Send a message and block until there is a response



147
148
149
150
151
152
153
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 147

def send(message)
  accept unless connected?
  socket.write handler.encode(message)
  receive
rescue Errno::EWOULDBLOCK
  raise TimeoutError.new(message)
end

#start_server(port) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/capybara/poltergeist/web_socket_server.rb', line 75

def start_server(port)
  time = Time.now

  begin
    TCPServer.open(HOST, port || 0)
  rescue Errno::EADDRINUSE
    if (Time.now - time) < BIND_TIMEOUT
      sleep(0.01)
      retry
    else
      raise
    end
  end
end