Class: Obxcura::Client
- Inherits:
-
Object
- Object
- Obxcura::Client
- Defined in:
- lib/obxcura/client.rb
Overview
The CDP transport: a single WebSocket to the browser endpoint.
Built directly on websocket-driver, because the higher-level
websocket-client-simple reads the socket one byte at a time (@socket.getc)
— a ~1MB CDP frame there takes seconds and can wedge the read loop. Here a
single reader thread pumps raw bytes through the driver (readpartial +
driver.parse), which parses frames efficiently.
This object is the driver's I/O adapter: websocket-driver calls #url for the handshake and #write to put bytes on the wire.
Everything on the wire is multiplexed over this one socket. Command replies
come back keyed by the id we assigned (globally unique across the
connection), so we resolve those regardless of session. Events instead carry
a sessionId, so we fan them out to whichever Page subscribed for that
session. Browser-level events (no sessionId) go to the :browser slot.
Constant Summary collapse
- DEFAULT_TIMEOUT =
Returns default seconds to wait for a command reply.
30- MAX_MESSAGE_SIZE =
Matches Obscura's own 64 MiB frame ceiling, so anything the browser is willing to send, the driver is willing to assemble.
64 * 1024 * 1024
- READ_CHUNK =
Bytes pulled per read syscall. This has to be large: since Obscura raised its frame ceiling to 64 MiB, multi-megabyte replies are routine, and a small value turns one reply into tens of thousands of syscalls. Measured on a 16 MiB reply: 512B → 0.188s, 64KiB → 0.105s. Past 64KiB the curve flattens, so the extra buffer buys nothing.
64 * 1024
Instance Attribute Summary collapse
-
#url ⇒ String
readonly
The WebSocket URL of the browser endpoint.
Instance Method Summary collapse
-
#close ⇒ void
Close the driver, socket and reader thread.
-
#closing? ⇒ Boolean
Whether the connection is being (or has been) torn down.
-
#command(method, params = {}, session_id: nil, timeout: @timeout) ⇒ Hash
Send a CDP command and block until its reply arrives.
-
#initialize(url, timeout: DEFAULT_TIMEOUT) ⇒ Client
constructor
Open the WebSocket to the browser and block until the handshake completes.
-
#subscribe(session_id) {|method, params| ... } ⇒ Proc
Register a handler for events on a given session.
-
#unsubscribe(session_id) ⇒ Proc?
Remove the handler previously registered for a session.
-
#write(data) ⇒ void
I/O adapter for websocket-driver: puts outgoing bytes on the wire.
Constructor Details
#initialize(url, timeout: DEFAULT_TIMEOUT) ⇒ Client
Open the WebSocket to the browser and block until the handshake completes.
51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/obxcura/client.rb', line 51 def initialize(url, timeout: DEFAULT_TIMEOUT) @url = url @timeout = timeout @command_id = 0 @mutex = Mutex.new @write_mutex = Mutex.new @pending = {} @subscribers = {} @open = Queue.new connect end |
Instance Attribute Details
#url ⇒ String (readonly)
Returns the WebSocket URL of the browser endpoint.
44 45 46 |
# File 'lib/obxcura/client.rb', line 44 def url @url end |
Instance Method Details
#close ⇒ void
This method returns an undefined value.
Close the driver, socket and reader thread. Safe to call more than once.
112 113 114 115 116 117 118 119 120 |
# File 'lib/obxcura/client.rb', line 112 def close @closing = true @driver&.close @socket&.close @subscribers.clear @reader&.kill rescue IOError, SystemCallError nil end |
#closing? ⇒ Boolean
Returns whether the connection is being (or has been) torn down.
123 124 125 |
# File 'lib/obxcura/client.rb', line 123 def closing? @closing == true end |
#command(method, params = {}, session_id: nil, timeout: @timeout) ⇒ Hash
Send a CDP command and block until its reply arrives.
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/obxcura/client.rb', line 73 def command(method, params = {}, session_id: nil, timeout: @timeout) id = next_id queue = Queue.new @mutex.synchronize { @pending[id] = queue } frame = { id: id, method: method, params: params } frame[:sessionId] = session_id if session_id @driver.text(JSON.generate(frame)) = Timeout.timeout(timeout, TimeoutError, "#{method} timed out after #{timeout}s") { queue.pop } raise ProtocolError, ["error"]["message"] if ["error"] ["result"] ensure @mutex.synchronize { @pending.delete(id) } end |
#subscribe(session_id) {|method, params| ... } ⇒ Proc
Register a handler for events on a given session.
97 98 99 |
# File 'lib/obxcura/client.rb', line 97 def subscribe(session_id, &block) @subscribers[session_id] = block end |
#unsubscribe(session_id) ⇒ Proc?
Remove the handler previously registered for a session.
105 106 107 |
# File 'lib/obxcura/client.rb', line 105 def unsubscribe(session_id) @subscribers.delete(session_id) end |
#write(data) ⇒ void
This method returns an undefined value.
I/O adapter for websocket-driver: puts outgoing bytes on the wire. Writes are serialized so command threads and the driver's own control frames (ping/pong/close) never interleave and corrupt the stream.
133 134 135 136 137 |
# File 'lib/obxcura/client.rb', line 133 def write(data) @write_mutex.synchronize { @socket.write(data) } rescue IOError, SystemCallError => e warn "[Obxcura] websocket write error: #{e}" unless closing? end |