Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

socket.py 5.0KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import errno
  2. import os
  3. import select
  4. import socket as pysocket
  5. import struct
  6. try:
  7. from ..transport import NpipeSocket
  8. except ImportError:
  9. NpipeSocket = type(None)
  10. STDOUT = 1
  11. STDERR = 2
  12. class SocketError(Exception):
  13. pass
  14. # NpipeSockets have their own error types
  15. # pywintypes.error: (109, 'ReadFile', 'The pipe has been ended.')
  16. NPIPE_ENDED = 109
  17. def read(socket, n=4096):
  18. """
  19. Reads at most n bytes from socket
  20. """
  21. recoverable_errors = (errno.EINTR, errno.EDEADLK, errno.EWOULDBLOCK)
  22. if not isinstance(socket, NpipeSocket):
  23. if not hasattr(select, "poll"):
  24. # Limited to 1024
  25. select.select([socket], [], [])
  26. else:
  27. poll = select.poll()
  28. poll.register(socket, select.POLLIN | select.POLLPRI)
  29. poll.poll()
  30. try:
  31. if hasattr(socket, 'recv'):
  32. return socket.recv(n)
  33. if isinstance(socket, getattr(pysocket, 'SocketIO')):
  34. return socket.read(n)
  35. return os.read(socket.fileno(), n)
  36. except OSError as e:
  37. if e.errno not in recoverable_errors:
  38. raise
  39. except Exception as e:
  40. is_pipe_ended = (isinstance(socket, NpipeSocket) and
  41. len(e.args) > 0 and
  42. e.args[0] == NPIPE_ENDED)
  43. if is_pipe_ended:
  44. # npipes don't support duplex sockets, so we interpret
  45. # a PIPE_ENDED error as a close operation (0-length read).
  46. return ''
  47. raise
  48. def read_exactly(socket, n):
  49. """
  50. Reads exactly n bytes from socket
  51. Raises SocketError if there isn't enough data
  52. """
  53. data = bytes()
  54. while len(data) < n:
  55. next_data = read(socket, n - len(data))
  56. if not next_data:
  57. raise SocketError("Unexpected EOF")
  58. data += next_data
  59. return data
  60. def next_frame_header(socket):
  61. """
  62. Returns the stream and size of the next frame of data waiting to be read
  63. from socket, according to the protocol defined here:
  64. https://docs.docker.com/engine/api/v1.24/#attach-to-a-container
  65. """
  66. try:
  67. data = read_exactly(socket, 8)
  68. except SocketError:
  69. return (-1, -1)
  70. stream, actual = struct.unpack('>BxxxL', data)
  71. return (stream, actual)
  72. def frames_iter(socket, tty):
  73. """
  74. Return a generator of frames read from socket. A frame is a tuple where
  75. the first item is the stream number and the second item is a chunk of data.
  76. If the tty setting is enabled, the streams are multiplexed into the stdout
  77. stream.
  78. """
  79. if tty:
  80. return ((STDOUT, frame) for frame in frames_iter_tty(socket))
  81. else:
  82. return frames_iter_no_tty(socket)
  83. def frames_iter_no_tty(socket):
  84. """
  85. Returns a generator of data read from the socket when the tty setting is
  86. not enabled.
  87. """
  88. while True:
  89. (stream, n) = next_frame_header(socket)
  90. if n < 0:
  91. break
  92. while n > 0:
  93. result = read(socket, n)
  94. if result is None:
  95. continue
  96. data_length = len(result)
  97. if data_length == 0:
  98. # We have reached EOF
  99. return
  100. n -= data_length
  101. yield (stream, result)
  102. def frames_iter_tty(socket):
  103. """
  104. Return a generator of data read from the socket when the tty setting is
  105. enabled.
  106. """
  107. while True:
  108. result = read(socket)
  109. if len(result) == 0:
  110. # We have reached EOF
  111. return
  112. yield result
  113. def consume_socket_output(frames, demux=False):
  114. """
  115. Iterate through frames read from the socket and return the result.
  116. Args:
  117. demux (bool):
  118. If False, stdout and stderr are multiplexed, and the result is the
  119. concatenation of all the frames. If True, the streams are
  120. demultiplexed, and the result is a 2-tuple where each item is the
  121. concatenation of frames belonging to the same stream.
  122. """
  123. if demux is False:
  124. # If the streams are multiplexed, the generator returns strings, that
  125. # we just need to concatenate.
  126. return bytes().join(frames)
  127. # If the streams are demultiplexed, the generator yields tuples
  128. # (stdout, stderr)
  129. out = [None, None]
  130. for frame in frames:
  131. # It is guaranteed that for each frame, one and only one stream
  132. # is not None.
  133. assert frame != (None, None)
  134. if frame[0] is not None:
  135. if out[0] is None:
  136. out[0] = frame[0]
  137. else:
  138. out[0] += frame[0]
  139. else:
  140. if out[1] is None:
  141. out[1] = frame[1]
  142. else:
  143. out[1] += frame[1]
  144. return tuple(out)
  145. def demux_adaptor(stream_id, data):
  146. """
  147. Utility to demultiplex stdout and stderr when reading frames from the
  148. socket.
  149. """
  150. if stream_id == STDOUT:
  151. return (data, None)
  152. elif stream_id == STDERR:
  153. return (None, data)
  154. else:
  155. raise ValueError(f'{stream_id} is not a valid stream')