图片解析应用
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.

162 lines
5.3 KiB

  1. import errno
  2. import io
  3. import socket
  4. from io import SEEK_END
  5. from typing import Optional, Union
  6. from ..exceptions import ConnectionError, TimeoutError
  7. from ..utils import SSL_AVAILABLE
  8. NONBLOCKING_EXCEPTION_ERROR_NUMBERS = {BlockingIOError: errno.EWOULDBLOCK}
  9. if SSL_AVAILABLE:
  10. import ssl
  11. if hasattr(ssl, "SSLWantReadError"):
  12. NONBLOCKING_EXCEPTION_ERROR_NUMBERS[ssl.SSLWantReadError] = 2
  13. NONBLOCKING_EXCEPTION_ERROR_NUMBERS[ssl.SSLWantWriteError] = 2
  14. else:
  15. NONBLOCKING_EXCEPTION_ERROR_NUMBERS[ssl.SSLError] = 2
  16. NONBLOCKING_EXCEPTIONS = tuple(NONBLOCKING_EXCEPTION_ERROR_NUMBERS.keys())
  17. SERVER_CLOSED_CONNECTION_ERROR = "Connection closed by server."
  18. SENTINEL = object()
  19. SYM_CRLF = b"\r\n"
  20. class SocketBuffer:
  21. def __init__(
  22. self, socket: socket.socket, socket_read_size: int, socket_timeout: float
  23. ):
  24. self._sock = socket
  25. self.socket_read_size = socket_read_size
  26. self.socket_timeout = socket_timeout
  27. self._buffer = io.BytesIO()
  28. def unread_bytes(self) -> int:
  29. """
  30. Remaining unread length of buffer
  31. """
  32. pos = self._buffer.tell()
  33. end = self._buffer.seek(0, SEEK_END)
  34. self._buffer.seek(pos)
  35. return end - pos
  36. def _read_from_socket(
  37. self,
  38. length: Optional[int] = None,
  39. timeout: Union[float, object] = SENTINEL,
  40. raise_on_timeout: Optional[bool] = True,
  41. ) -> bool:
  42. sock = self._sock
  43. socket_read_size = self.socket_read_size
  44. marker = 0
  45. custom_timeout = timeout is not SENTINEL
  46. buf = self._buffer
  47. current_pos = buf.tell()
  48. buf.seek(0, SEEK_END)
  49. if custom_timeout:
  50. sock.settimeout(timeout)
  51. try:
  52. while True:
  53. data = self._sock.recv(socket_read_size)
  54. # an empty string indicates the server shutdown the socket
  55. if isinstance(data, bytes) and len(data) == 0:
  56. raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
  57. buf.write(data)
  58. data_length = len(data)
  59. marker += data_length
  60. if length is not None and length > marker:
  61. continue
  62. return True
  63. except socket.timeout:
  64. if raise_on_timeout:
  65. raise TimeoutError("Timeout reading from socket")
  66. return False
  67. except NONBLOCKING_EXCEPTIONS as ex:
  68. # if we're in nonblocking mode and the recv raises a
  69. # blocking error, simply return False indicating that
  70. # there's no data to be read. otherwise raise the
  71. # original exception.
  72. allowed = NONBLOCKING_EXCEPTION_ERROR_NUMBERS.get(ex.__class__, -1)
  73. if not raise_on_timeout and ex.errno == allowed:
  74. return False
  75. raise ConnectionError(f"Error while reading from socket: {ex.args}")
  76. finally:
  77. buf.seek(current_pos)
  78. if custom_timeout:
  79. sock.settimeout(self.socket_timeout)
  80. def can_read(self, timeout: float) -> bool:
  81. return bool(self.unread_bytes()) or self._read_from_socket(
  82. timeout=timeout, raise_on_timeout=False
  83. )
  84. def read(self, length: int) -> bytes:
  85. length = length + 2 # make sure to read the \r\n terminator
  86. # BufferIO will return less than requested if buffer is short
  87. data = self._buffer.read(length)
  88. missing = length - len(data)
  89. if missing:
  90. # fill up the buffer and read the remainder
  91. self._read_from_socket(missing)
  92. data += self._buffer.read(missing)
  93. return data[:-2]
  94. def readline(self) -> bytes:
  95. buf = self._buffer
  96. data = buf.readline()
  97. while not data.endswith(SYM_CRLF):
  98. # there's more data in the socket that we need
  99. self._read_from_socket()
  100. data += buf.readline()
  101. return data[:-2]
  102. def get_pos(self) -> int:
  103. """
  104. Get current read position
  105. """
  106. return self._buffer.tell()
  107. def rewind(self, pos: int) -> None:
  108. """
  109. Rewind the buffer to a specific position, to re-start reading
  110. """
  111. self._buffer.seek(pos)
  112. def purge(self) -> None:
  113. """
  114. After a successful read, purge the read part of buffer
  115. """
  116. unread = self.unread_bytes()
  117. # Only if we have read all of the buffer do we truncate, to
  118. # reduce the amount of memory thrashing. This heuristic
  119. # can be changed or removed later.
  120. if unread > 0:
  121. return
  122. if unread > 0:
  123. # move unread data to the front
  124. view = self._buffer.getbuffer()
  125. view[:unread] = view[-unread:]
  126. self._buffer.truncate(unread)
  127. self._buffer.seek(0)
  128. def close(self) -> None:
  129. try:
  130. self._buffer.close()
  131. except Exception:
  132. # issue #633 suggests the purge/close somehow raised a
  133. # BadFileDescriptor error. Perhaps the client ran out of
  134. # memory or something else? It's probably OK to ignore
  135. # any error being raised from purge/close since we're
  136. # removing the reference to the instance below.
  137. pass
  138. self._buffer = None
  139. self._sock = None