diff --git a/docker/api/client.py b/docker/api/client.py index 394ceb1f..e500c896 100644 --- a/docker/api/client.py +++ b/docker/api/client.py @@ -350,6 +350,12 @@ class APIClient( # fine because we won't be doing TLS over them pass + if six.PY3: + # Preserve the io.BufferedReader buffer as part of the socket so + # that it may be read ahead of attempting futher reads from the + # socket -- see ..utils.socket.read() implementation. + setattr(sock, '_buffer', memoryview(response.raw._fp.fp.peek())) + return sock def _stream_helper(self, response, decode=False): diff --git a/docker/utils/socket.py b/docker/utils/socket.py index c7cb584d..cf3b4703 100644 --- a/docker/utils/socket.py +++ b/docker/utils/socket.py @@ -28,6 +28,30 @@ def read(socket, n=4096): Reads at most n bytes from socket """ + # This socket may have been extracted from an HTTPResponse. That would have + # wrapped the socked with an io.BufferedReader, which may still have some + # "unread" bytes in its internal buffer. If that were the case, those would + # be preserved inside _buffer attribute of the socket (see + # ..api.client.APIClient._get_raw_response_socket() implementation). These + # must be returned firsts, before attemting any further reads from the + # socket. + if not hasattr(socket, '_buffer') or not socket._buffer: + return read_from_socket(socket, n) + + ret, socket._buffer = socket._buffer[:n], socket._buffer[n:] + + still_to_read = n - len(ret) + if still_to_read: + return ret.tobytes() + read_from_socket(socket, still_to_read) + else: + return ret.tobytes() + + +def read_from_socket(socket, n=4096): + """ + Reads at most n bytes from socket + """ + recoverable_errors = (errno.EINTR, errno.EDEADLK, errno.EWOULDBLOCK) if not isinstance(socket, NpipeSocket):