Skip to content

Commit

Permalink
Fallback to reading 0 bytes if readable is not implemented.
Browse files Browse the repository at this point in the history
Gunicorn will wrap the wsgi stream in its own wrapper class (see `class Body`).
This class is not compliant with the file-object api from Python (missing `readable`).

This issue never showed up before because of
https://github.com/quay/quay/blob/master/storage/swift.py#L418 always
evaluating to False in Python2, which was fixed by
#508.
  • Loading branch information
kleesc committed Aug 10, 2020
1 parent bf656b2 commit 3eea33d
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 1 deletion.
15 changes: 14 additions & 1 deletion util/registry/filelike.py
Expand Up @@ -49,7 +49,20 @@ def seek(self, index, whence=WHENCE_ABSOLUTE):
return bytes_forward

def readable(self):
return self._fileobj.readable()
# Depending on the "transfer-encoding" header, the returned stream will either be a
# Flask stream, or a gunicorn wrapped stream (see endpoints/v2/__init__.py):
# - https://github.com/pallets/werkzeug/blob/master/src/werkzeug/wsgi.py#L205
# - https://github.com/benoitc/gunicorn/blob/master/gunicorn/http/body.py#L177
#
# In the latter case, gunicorn's wrapper class does not implement the full object-file
# api expected (e.g `readable` is missing).
if hasattr(self._fileobj, "readable"):
return self._fileobj.readable()
try:
self.read(0)
return True
except ValueError:
return False


class SocketReader(BaseStreamFilelike):
Expand Down
66 changes: 66 additions & 0 deletions util/registry/test/test_filelike.py
Expand Up @@ -149,3 +149,69 @@ def test_slice_explictread():
assert stream.read(2) == b"is"
assert stream.read(5) == b" a"
assert len(b"is a") == stream.tell()


def test_non_filelike_obj_read_limit():
from gunicorn.http.body import Body

content = b"this will not really be a real fileobj"
fileobj = BytesIO(content)
body = Body(fileobj)
ls = LimitingStream(body)

assert ls.readable()
assert ls.read(-1) == content
assert len(content) == ls.tell()


def test_non_filelike_obj():
from gunicorn.http.body import Body

content = b"this will not really be a real fileobj"
fileobj = BytesIO(content)
body = Body(fileobj)
ls = LimitingStream(body)

assert ls.readable()
assert ls.read(-1) == content
assert ls.read(1) == b""
assert len(content) == ls.tell()


def test_non_filelike_obj_read():
from gunicorn.http.body import Body

content = b"this will not really be a real fileobj"
fileobj = BytesIO(content)

# Limited
body1 = Body(fileobj)
ls1 = LimitingStream(body1, 4)
assert ls1.readable()

resp1 = b""
while True:
buf = ls1.read(-1)
if not buf:
break
resp1 += buf

assert resp1 == content[:4]
assert ls1.read(1) == b""
assert 4 == ls1.tell()

# Non limited
fileobj.seek(0)
body2 = Body(fileobj)
ls2 = LimitingStream(body2)

resp2 = b""
while True:
buf = ls2.read(-1)
if not buf:
break
resp2 += buf

assert resp2 == content
assert ls2.read(1) == b""
assert len(content) == ls2.tell()

0 comments on commit 3eea33d

Please sign in to comment.