Skip to content

Commit

Permalink
Allow debugger to be attached from Inspector page in Chrome
Browse files Browse the repository at this point in the history
  • Loading branch information
ono-max committed Nov 13, 2022
1 parent a402e73 commit de34ceb
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 17 deletions.
20 changes: 11 additions & 9 deletions lib/debug/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def initialize

class Terminate < StandardError; end
class GreetingError < StandardError; end
class RetryConnection < StandardError; end

def deactivate
@reader_thread.raise Terminate
Expand Down Expand Up @@ -77,6 +78,8 @@ def activate session, on_fork: false
next
rescue Terminate
raise # should catch at outer scope
rescue RetryConnection
next
rescue => e
DEBUGGER__.warn "ReaderThreadError: #{e}"
pp e.backtrace
Expand Down Expand Up @@ -158,16 +161,13 @@ def greeting
@need_pause_at_first = false
dap_setup @sock.read($1.to_i)

when /^GET \/.* HTTP\/1.1/
when /^GET\s\/json\sHTTP\/1.1/, /^GET\s\/json\/version\sHTTP\/1.1/, /^GET\s\/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}\sHTTP\/1.1/
# The reason for not using @uuid here is @uuid is nil if users run debugger without `--open=chrome`.

require_relative 'server_cdp'

self.extend(UI_CDP)
@repl = false
@need_pause_at_first = false
CONFIG.set_config no_color: true

@ws_server = UI_CDP::WebSocketServer.new(@sock)
@ws_server.handshake
send_chrome_response g
else
raise GreetingError, "Unknown greeting message: #{g}"
end
Expand Down Expand Up @@ -396,18 +396,20 @@ def initialize host: nil, port: nil
raise "Specify digits for port number"
end
end
@uuid = nil # for CDP

super()
end

def chrome_setup
require_relative 'server_cdp'

unless @chrome_pid = UI_CDP.setup_chrome(@local_addr.inspect_sockaddr)
@uuid = SecureRandom.uuid
unless @chrome_pid = UI_CDP.setup_chrome(@local_addr.inspect_sockaddr, @uuid)
DEBUGGER__.warn <<~EOS
With Chrome browser, type the following URL in the address-bar:
devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{@local_addr.inspect_sockaddr}/#{SecureRandom.uuid}
devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{@local_addr.inspect_sockaddr}/#{@uuid}
EOS
end
Expand Down
47 changes: 45 additions & 2 deletions lib/debug/server_cdp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module UI_CDP
SHOW_PROTOCOL = ENV['RUBY_DEBUG_CDP_SHOW_PROTOCOL'] == '1'

class << self
def setup_chrome addr
def setup_chrome addr, uuid
return if CONFIG[:chrome_path] == ''

port, path, pid = run_new_chrome
Expand Down Expand Up @@ -51,7 +51,7 @@ def setup_chrome addr
ws_client.send sessionId: s_id, id: 5,
method: 'Page.navigate',
params: {
url: "devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{addr}/#{SecureRandom.uuid}",
url: "devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{addr}/#{uuid}",
frameId: f_id
}
when res['method'] == 'Page.loadEventFired'
Expand Down Expand Up @@ -102,6 +102,49 @@ def run_new_chrome
end
end

def send_chrome_response req
@repl = false
case req
when /^GET\s\/json\/version\sHTTP\/1.1/
body = {
Browser: "ruby/v#{RUBY_VERSION}",
'Protocol-Version': "1.1"
}
send_http_res body
raise UI_ServerBase::RetryConnection

when /^GET\s\/json\sHTTP\/1.1/
@uuid = @uuid || SecureRandom.uuid
addr = @local_addr.inspect_sockaddr
body = [{
description: "ruby instance",
devtoolsFrontendUrl: "devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=#{addr}/#{@uuid}",
id: @uuid,
title: $0,
type: "node",
url: "file://#{File.absolute_path($0)}",
webSocketDebuggerUrl: "ws://#{addr}/#{@uuid}"
}]
send_http_res body
raise UI_ServerBase::RetryConnection

when /^GET\s\/(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})\sHTTP\/1.1/
raise 'Incorrect uuid' unless $1 == @uuid

@need_pause_at_first = false
CONFIG.set_config no_color: true

@ws_server = WebSocketServer.new(@sock)
@ws_server.handshake
end
end

def send_http_res body
json = JSON.generate body
header = "HTTP/1.0 200 OK\r\nContent-Type: application/json; charset=UTF-8\r\nCache-Control: no-cache\r\nContent-Length: #{json.bytesize}\r\n\r\n"
@sock.puts "#{header}#{json}"
end

module WebSocketUtils
class Frame
attr_reader :b
Expand Down
7 changes: 5 additions & 2 deletions test/support/cdp_utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@ def setup_chrome_debuggee
def connect_to_cdp_server
ENV['RUBY_DEBUG_TEST_MODE'] = 'true'

body = get_request HOST, @remote_info.port, '/json'
sock = Socket.tcp HOST, @remote_info.port
uuid = body[0][:id]

Timeout.timeout(TIMEOUT_SEC) do
sleep 0.001 until @remote_info.debuggee_backlog.join.include? 'Connected'
sleep 0.001 until @remote_info.debuggee_backlog.join.match?(/Disconnected\.\R.*Connected/)
end
@web_sock = WebSocketClient.new sock
@web_sock.handshake @remote_info.port, '/'
@web_sock.handshake @remote_info.port, uuid
@reader_thread = Thread.new do
Thread.current.abort_on_exception = true
while res = @web_sock.extract_data
Expand Down
14 changes: 10 additions & 4 deletions test/support/protocol_test_case.rb
Original file line number Diff line number Diff line change
Expand Up @@ -394,14 +394,20 @@ def attach_to_dap_server
HOST = '127.0.0.1'

def attach_to_cdp_server
body = get_request HOST, @remote_info.port, '/json'
Timeout.timeout(TIMEOUT_SEC) do
sleep 0.001 until @remote_info.debuggee_backlog.join.include? 'Disconnected.'
end

sock = Socket.tcp HOST, @remote_info.port
uuid = body[0][:id]

Timeout.timeout(TIMEOUT_SEC) do
sleep 0.001 until @remote_info.debuggee_backlog.join.include? 'Connected'
sleep 0.001 until @remote_info.debuggee_backlog.join.match?(/Disconnected\.\R.*Connected/)
end

@web_sock = WebSocketClient.new sock
@web_sock.handshake @remote_info.port, '/'
@web_sock.handshake @remote_info.port, uuid
@id = 1
@reader_thread = Thread.new do
while res = @web_sock.extract_data
Expand Down Expand Up @@ -797,9 +803,9 @@ def initialize s
@sock = s
end

def handshake port, path
def handshake port, uuid
key = SecureRandom.hex(11)
@sock.print "GET #{path} HTTP/1.1\r\nHost: 127.0.0.1:#{port}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: #{key}==\r\n\r\n"
@sock.print "GET /#{uuid} HTTP/1.1\r\nHost: 127.0.0.1:#{port}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: #{key}==\r\n\r\n"
server_key = get_server_key

correct_key = Base64.strict_encode64 Digest::SHA1.digest "#{key}==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
Expand Down
21 changes: 21 additions & 0 deletions test/support/test_case.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,5 +188,26 @@ def setup_tcpip_remote_debuggee
remote_info.port = TCPIP_PORT
remote_info
end

# Debuggee sometimes sends msgs such as "out [1, 5] in ...".
# This http request method is for ignoring them.
def get_request host, port, path
Timeout.timeout(TIMEOUT_SEC) do
Socket.tcp(host, port){|sock|
sock.print "GET #{path} HTTP/1.1\r\n"
sock.close_write
loop do
case header = sock.gets
when /Content-Length: (\d+)/
b = sock.read(2)
raise b.inspect unless b == "\r\n"

l = sock.read $1.to_i
return JSON.parse l, symbolize_names: true
end
end
}
end
end
end
end

0 comments on commit de34ceb

Please sign in to comment.