Skip to content
This repository has been archived by the owner on Aug 2, 2023. It is now read-only.

Create command to enable pydevd to send data using http-procotol instead of by line with urllib quoting. #764

Merged
merged 2 commits into from
Aug 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 87 additions & 31 deletions ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ def unquote(s):

CMD_VERSION = 501
CMD_RETURN = 502
CMD_SET_PROTOCOL = 503
CMD_ERROR = 901

ID_TO_MEANING = {
Expand Down Expand Up @@ -257,6 +258,7 @@ def unquote(s):

'501': 'CMD_VERSION',
'502': 'CMD_RETURN',
'503': 'CMD_SET_PROTOCOL',
'901': 'CMD_ERROR',
}

Expand Down Expand Up @@ -449,16 +451,11 @@ def _on_run(self):
""" just loop and write responses """

self._stop_trace()
get_has_timeout = sys.hexversion >= 0x02030000 # 2.3 onwards have it.
try:
while True:
try:
try:
if get_has_timeout:
cmd = self.cmdQueue.get(1, 0.1)
else:
time.sleep(.01)
cmd = self.cmdQueue.get(0)
cmd = self.cmdQueue.get(1, 0.1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this okay for upstream?

Copy link
Member

@karthiknadig karthiknadig Aug 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is already on upstream https://github.com/fabioz/PyDev.Debugger/blob/master/_pydevd_bundle/pydevd_comm.py#L458

@fabioz usually makes the changes upstream then ports them over here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeap... that code was for some really old versions of Python (2.3 and before), so, now that the minimum is 2.6 it should be safe.

except _queue.Empty:
if self.killReceived:
try:
Expand All @@ -475,21 +472,8 @@ def _on_run(self):
#when liberating the thread here, we could have errors because we were shutting down
#but the thread was still not liberated
return
out = cmd.outgoing

if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1:
out_message = 'sending cmd --> '
out_message += "%20s" % ID_TO_MEANING.get(out[:3], 'UNKNOWN')
out_message += ' '
out_message += unquote(unquote(out)).replace('\n', ' ')
try:
sys.stderr.write('%s\n' % (out_message,))
except:
pass

if IS_PY3K:
out = bytearray(out, 'utf-8')
self.sock.send(out) #TODO: this does not guarantee that all message are sent (and jython does not have a send all)
cmd.send(self.sock)

if cmd.id == CMD_EXIT:
break
if time is None:
Expand All @@ -499,7 +483,7 @@ def _on_run(self):
GlobalDebuggerHolder.global_dbg.finish_debugging_session()
if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 0:
traceback.print_exc()

def empty(self):
return self.cmdQueue.empty()

Expand Down Expand Up @@ -587,19 +571,88 @@ class NetCommand:
or one to be sent by daemon.
"""
next_seq = 0 # sequence numbers

def __init__(self, id, seq, text):
""" smart handling of parameters
if sequence is 0, new sequence will be generated
if text has carriage returns they'll be replaced"""
self.id = id

# Protocol where each line is a new message (text is quoted to prevent new lines).
QUOTED_LINE_PROTOCOL = 'quoted-line'

# Uses http protocol to provide a new message.
# i.e.: Content-Length:xxx\r\n\r\npayload
HTTP_PROTOCOL = 'http'

protocol = QUOTED_LINE_PROTOCOL

_showing_debug_info = 0
_show_debug_info_lock = threading.RLock()

def __init__(self, cmd_id, seq, text):
"""
If sequence is 0, new sequence will be generated (otherwise, this was the response
to a command from the client).
"""
self.id = cmd_id
if seq == 0:
NetCommand.next_seq += 2
seq = NetCommand.next_seq
self.seq = seq
self.text = text
encoded = quote(to_string(text), '/<>_=" \t')
self.outgoing = '%s\t%s\t%s\n' % (id, seq, encoded)

if IS_PY2:
if isinstance(text, unicode):
text = text.encode('utf-8')
else:
assert isinstance(text, str)
else:
assert isinstance(text, str)

if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1:
self._show_debug_info(cmd_id, seq, text)

if self.protocol == self.HTTP_PROTOCOL:
msg = '%s\t%s\t%s\n' % (cmd_id, seq, text)
else:
encoded = quote(to_string(text), '/<>_=" \t')
msg = '%s\t%s\t%s\n' % (cmd_id, seq, encoded)


if IS_PY2:
assert isinstance(msg, str) # i.e.: bytes
as_bytes = msg
else:
if isinstance(msg, str):
msg = msg.encode('utf-8')

assert isinstance(msg, bytes)
as_bytes = msg
self._as_bytes = as_bytes

def send(self, sock):
as_bytes = self._as_bytes
if self.protocol == self.HTTP_PROTOCOL:
sock.sendall(('Content-Length: %s\r\n\r\n' % len(as_bytes)).encode('ascii'))

sock.sendall(as_bytes)

@classmethod
def _show_debug_info(cls, cmd_id, seq, text):
with cls._show_debug_info_lock:
# Only one thread each time (rlock).
if cls._showing_debug_info:
# avoid recursing in the same thread (just printing could create
# a new command when redirecting output).
return

cls._showing_debug_info += 1
try:
out_message = 'sending cmd --> '
out_message += "%20s" % ID_TO_MEANING.get(str(cmd_id), 'UNKNOWN')
out_message += ' '
out_message += text.replace('\n', ' ')
try:
sys.stderr.write('%s\n' % (out_message,))
except:
pass
finally:
cls._showing_debug_info -= 1


#=======================================================================================================================
# NetCommandFactory
Expand All @@ -617,6 +670,9 @@ def make_error_message(self, seq, text):
if DebugInfoHolder.DEBUG_TRACE_LEVEL > 2:
sys.stderr.write("Error: %s" % (text,))
return cmd

def make_protocol_set_message(self, seq):
return NetCommand(CMD_SET_PROTOCOL, seq, '')

def make_thread_created_message(self, thread):
cmdText = "<xml>" + self._thread_to_xml(thread) + "</xml>"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
CMD_RUN_CUSTOM_OPERATION, InternalRunCustomOperation, CMD_IGNORE_THROWN_EXCEPTION_AT, CMD_ENABLE_DONT_TRACE, \
CMD_SHOW_RETURN_VALUES, ID_TO_MEANING, CMD_GET_DESCRIPTION, InternalGetDescription, InternalLoadFullValue, \
CMD_LOAD_FULL_VALUE, CMD_REDIRECT_OUTPUT, CMD_GET_NEXT_STATEMENT_TARGETS, InternalGetNextStatementTargets, CMD_SET_PROJECT_ROOTS, \
CMD_GET_THREAD_STACK, CMD_THREAD_DUMP_TO_STDERR, CMD_STOP_ON_START, CMD_GET_EXCEPTION_DETAILS
CMD_GET_THREAD_STACK, CMD_THREAD_DUMP_TO_STDERR, CMD_STOP_ON_START, CMD_GET_EXCEPTION_DETAILS, NetCommand,\
CMD_SET_PROTOCOL
from _pydevd_bundle.pydevd_constants import get_thread_id, IS_PY3K, DebugInfoHolder, dict_keys, STATE_RUN, \
NEXT_VALUE_SEPARATOR, IS_WINDOWS
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
Expand Down Expand Up @@ -49,10 +50,19 @@ def process_net_command(py_db, cmd_id, seq, text):
if cmd_id == CMD_RUN:
py_db.ready_to_run = True

elif cmd_id == CMD_SET_PROTOCOL:
expected = (NetCommand.HTTP_PROTOCOL, NetCommand.QUOTED_LINE_PROTOCOL)
text = text.strip()
assert text.strip() in expected, 'Protocol (%s) should be one of: %s' % (
text, expected)

NetCommand.protocol = text
cmd = py_db.cmd_factory.make_protocol_set_message(seq)

elif cmd_id == CMD_VERSION:
# response is version number
# ide_os should be 'WINDOWS' or 'UNIX'.

# Default based on server process (although ideally the IDE should
# provide it).
if IS_WINDOWS:
Expand Down Expand Up @@ -88,7 +98,7 @@ def process_net_command(py_db, cmd_id, seq, text):

elif cmd_id == CMD_GET_THREAD_STACK:
thread_id = text

t = pydevd_find_thread_by_id(thread_id)
frame = None
if t and not getattr(t, 'pydev_do_not_trace', None):
Expand Down Expand Up @@ -441,7 +451,7 @@ def process_net_command(py_db, cmd_id, seq, text):

elif cmd_id == CMD_SET_PY_EXCEPTION:
# Command which receives set of exceptions on which user wants to break the debugger
# text is:
# text is:
#
# break_on_uncaught;
# break_on_caught;
Expand Down Expand Up @@ -557,7 +567,7 @@ def process_net_command(py_db, cmd_id, seq, text):
# notify_on_handled_exceptions can be 0, 1 or 2
# 0 means we should not stop on handled exceptions.
# 1 means we should stop on handled exceptions showing it on all frames where the exception passes.
# 2 means we should stop on handled exceptions but we should only notify about it once.
# 2 means we should stop on handled exceptions but we should only notify about it once.
#
# To ignore_libraries properly, besides setting ignore_libraries to 1, the IDE_PROJECT_ROOTS environment
# variable must be set (so, we'll ignore anything not below IDE_PROJECT_ROOTS) -- this is not ideal as
Expand Down Expand Up @@ -784,16 +794,16 @@ def process_net_command(py_db, cmd_id, seq, text):

int_cmd = InternalGetNextStatementTargets(seq, thread_id, frame_id)
py_db.post_internal_command(int_cmd, thread_id)

elif cmd_id == CMD_SET_PROJECT_ROOTS:
pydevd_utils.set_project_roots(text.split(u'\t'))

elif cmd_id == CMD_THREAD_DUMP_TO_STDERR:
pydevd_utils.dump_threads()

elif cmd_id == CMD_STOP_ON_START:
py_db.stop_on_start = text.strip() in ('True', 'true', '1')

elif cmd_id == CMD_GET_EXCEPTION_DETAILS:
thread_id = text
t = pydevd_find_thread_by_id(thread_id)
Expand All @@ -806,7 +816,7 @@ def process_net_command(py_db, cmd_id, seq, text):
finally:
frame = None
t = None

else:
#I have no idea what this is all about
cmd = py_db.cmd_factory.make_error_message(seq, "unexpected command " + str(cmd_id))
Expand Down
2 changes: 2 additions & 0 deletions ptvsd/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,8 @@ def send(self, data):
loop.call_soon_threadsafe(fut.set_result, (cmd_id, seq, args))
return result

sendall = send

def makefile(self, *args, **kwargs):
"""Return a file-like wrapper around the socket."""
return os.fdopen(self.pipe_r)
Expand Down