Skip to content

Commit de34ceb

Browse files
committed
Allow debugger to be attached from Inspector page in Chrome
1 parent a402e73 commit de34ceb

File tree

5 files changed

+92
-17
lines changed

5 files changed

+92
-17
lines changed

lib/debug/server.rb

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def initialize
2121

2222
class Terminate < StandardError; end
2323
class GreetingError < StandardError; end
24+
class RetryConnection < StandardError; end
2425

2526
def deactivate
2627
@reader_thread.raise Terminate
@@ -77,6 +78,8 @@ def activate session, on_fork: false
7778
next
7879
rescue Terminate
7980
raise # should catch at outer scope
81+
rescue RetryConnection
82+
next
8083
rescue => e
8184
DEBUGGER__.warn "ReaderThreadError: #{e}"
8285
pp e.backtrace
@@ -158,16 +161,13 @@ def greeting
158161
@need_pause_at_first = false
159162
dap_setup @sock.read($1.to_i)
160163

161-
when /^GET \/.* HTTP\/1.1/
164+
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/
165+
# The reason for not using @uuid here is @uuid is nil if users run debugger without `--open=chrome`.
166+
162167
require_relative 'server_cdp'
163168

164169
self.extend(UI_CDP)
165-
@repl = false
166-
@need_pause_at_first = false
167-
CONFIG.set_config no_color: true
168-
169-
@ws_server = UI_CDP::WebSocketServer.new(@sock)
170-
@ws_server.handshake
170+
send_chrome_response g
171171
else
172172
raise GreetingError, "Unknown greeting message: #{g}"
173173
end
@@ -396,18 +396,20 @@ def initialize host: nil, port: nil
396396
raise "Specify digits for port number"
397397
end
398398
end
399+
@uuid = nil # for CDP
399400

400401
super()
401402
end
402403

403404
def chrome_setup
404405
require_relative 'server_cdp'
405406

406-
unless @chrome_pid = UI_CDP.setup_chrome(@local_addr.inspect_sockaddr)
407+
@uuid = SecureRandom.uuid
408+
unless @chrome_pid = UI_CDP.setup_chrome(@local_addr.inspect_sockaddr, @uuid)
407409
DEBUGGER__.warn <<~EOS
408410
With Chrome browser, type the following URL in the address-bar:
409411
410-
devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{@local_addr.inspect_sockaddr}/#{SecureRandom.uuid}
412+
devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{@local_addr.inspect_sockaddr}/#{@uuid}
411413
412414
EOS
413415
end

lib/debug/server_cdp.rb

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ module UI_CDP
1313
SHOW_PROTOCOL = ENV['RUBY_DEBUG_CDP_SHOW_PROTOCOL'] == '1'
1414

1515
class << self
16-
def setup_chrome addr
16+
def setup_chrome addr, uuid
1717
return if CONFIG[:chrome_path] == ''
1818

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

105+
def send_chrome_response req
106+
@repl = false
107+
case req
108+
when /^GET\s\/json\/version\sHTTP\/1.1/
109+
body = {
110+
Browser: "ruby/v#{RUBY_VERSION}",
111+
'Protocol-Version': "1.1"
112+
}
113+
send_http_res body
114+
raise UI_ServerBase::RetryConnection
115+
116+
when /^GET\s\/json\sHTTP\/1.1/
117+
@uuid = @uuid || SecureRandom.uuid
118+
addr = @local_addr.inspect_sockaddr
119+
body = [{
120+
description: "ruby instance",
121+
devtoolsFrontendUrl: "devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=#{addr}/#{@uuid}",
122+
id: @uuid,
123+
title: $0,
124+
type: "node",
125+
url: "file://#{File.absolute_path($0)}",
126+
webSocketDebuggerUrl: "ws://#{addr}/#{@uuid}"
127+
}]
128+
send_http_res body
129+
raise UI_ServerBase::RetryConnection
130+
131+
when /^GET\s\/(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})\sHTTP\/1.1/
132+
raise 'Incorrect uuid' unless $1 == @uuid
133+
134+
@need_pause_at_first = false
135+
CONFIG.set_config no_color: true
136+
137+
@ws_server = WebSocketServer.new(@sock)
138+
@ws_server.handshake
139+
end
140+
end
141+
142+
def send_http_res body
143+
json = JSON.generate body
144+
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"
145+
@sock.puts "#{header}#{json}"
146+
end
147+
105148
module WebSocketUtils
106149
class Frame
107150
attr_reader :b

test/support/cdp_utils.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@ def setup_chrome_debuggee
3434
def connect_to_cdp_server
3535
ENV['RUBY_DEBUG_TEST_MODE'] = 'true'
3636

37+
body = get_request HOST, @remote_info.port, '/json'
3738
sock = Socket.tcp HOST, @remote_info.port
39+
uuid = body[0][:id]
40+
3841
Timeout.timeout(TIMEOUT_SEC) do
39-
sleep 0.001 until @remote_info.debuggee_backlog.join.include? 'Connected'
42+
sleep 0.001 until @remote_info.debuggee_backlog.join.match?(/Disconnected\.\R.*Connected/)
4043
end
4144
@web_sock = WebSocketClient.new sock
42-
@web_sock.handshake @remote_info.port, '/'
45+
@web_sock.handshake @remote_info.port, uuid
4346
@reader_thread = Thread.new do
4447
Thread.current.abort_on_exception = true
4548
while res = @web_sock.extract_data

test/support/protocol_test_case.rb

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -394,14 +394,20 @@ def attach_to_dap_server
394394
HOST = '127.0.0.1'
395395

396396
def attach_to_cdp_server
397+
body = get_request HOST, @remote_info.port, '/json'
398+
Timeout.timeout(TIMEOUT_SEC) do
399+
sleep 0.001 until @remote_info.debuggee_backlog.join.include? 'Disconnected.'
400+
end
401+
397402
sock = Socket.tcp HOST, @remote_info.port
403+
uuid = body[0][:id]
398404

399405
Timeout.timeout(TIMEOUT_SEC) do
400-
sleep 0.001 until @remote_info.debuggee_backlog.join.include? 'Connected'
406+
sleep 0.001 until @remote_info.debuggee_backlog.join.match?(/Disconnected\.\R.*Connected/)
401407
end
402408

403409
@web_sock = WebSocketClient.new sock
404-
@web_sock.handshake @remote_info.port, '/'
410+
@web_sock.handshake @remote_info.port, uuid
405411
@id = 1
406412
@reader_thread = Thread.new do
407413
while res = @web_sock.extract_data
@@ -797,9 +803,9 @@ def initialize s
797803
@sock = s
798804
end
799805

800-
def handshake port, path
806+
def handshake port, uuid
801807
key = SecureRandom.hex(11)
802-
@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"
808+
@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"
803809
server_key = get_server_key
804810

805811
correct_key = Base64.strict_encode64 Digest::SHA1.digest "#{key}==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

test/support/test_case.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,5 +188,26 @@ def setup_tcpip_remote_debuggee
188188
remote_info.port = TCPIP_PORT
189189
remote_info
190190
end
191+
192+
# Debuggee sometimes sends msgs such as "out [1, 5] in ...".
193+
# This http request method is for ignoring them.
194+
def get_request host, port, path
195+
Timeout.timeout(TIMEOUT_SEC) do
196+
Socket.tcp(host, port){|sock|
197+
sock.print "GET #{path} HTTP/1.1\r\n"
198+
sock.close_write
199+
loop do
200+
case header = sock.gets
201+
when /Content-Length: (\d+)/
202+
b = sock.read(2)
203+
raise b.inspect unless b == "\r\n"
204+
205+
l = sock.read $1.to_i
206+
return JSON.parse l, symbolize_names: true
207+
end
208+
end
209+
}
210+
end
211+
end
191212
end
192213
end

0 commit comments

Comments
 (0)