Skip to content

Commit

Permalink
Merge 6c34f7f into 8df8e76
Browse files Browse the repository at this point in the history
  • Loading branch information
hozn committed Dec 20, 2016
2 parents 8df8e76 + 6c34f7f commit 52b643e
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 4 deletions.
5 changes: 1 addition & 4 deletions falcon/api.py
Expand Up @@ -668,10 +668,7 @@ def _get_body(self, resp, wsgi_file_wrapper=None):
iterable = wsgi_file_wrapper(stream,
self._STREAM_BLOCK_SIZE)
else:
iterable = iter(
lambda: stream.read(self._STREAM_BLOCK_SIZE),
b''
)
iterable = helpers.CloseableStreamIterator(stream, self._STREAM_BLOCK_SIZE)
else:
iterable = stream

Expand Down
31 changes: 31 additions & 0 deletions falcon/api_helpers.py
Expand Up @@ -16,6 +16,8 @@

from functools import wraps

import six

from falcon import util


Expand Down Expand Up @@ -150,3 +152,32 @@ def new_fn(req, resp, exception):
resp.content_type = media_type

return new_fn


class CloseableStreamIterator(six.Iterator):
"""
An iterator that returns next block-size number of bytes until response from stream is empty string (bytes).
"""

def __init__(self, stream, block_size):
"""
Args:
stream: Stream file-like object.
block_size: Number of bytes to read per iteration.
"""
self.stream = stream
self.block_size = block_size

def __iter__(self):
return self

def __next__(self):
data = self.stream.read(self.block_size)
if data == b'':
raise StopIteration
else:
return data

def close(self):
if hasattr(self.stream, 'close') and callable(self.stream.close):
self.stream.close()
50 changes: 50 additions & 0 deletions tests/test_hello.py
Expand Up @@ -69,6 +69,34 @@ def on_head(self, req, resp):
self.on_get(req, resp)


class ClosingBytesIO(io.BytesIO):

close_called = False

def close(self):
super().close()
self.close_called = True


class ClosingFilelikeHelloResource(object):
sample_status = '200 OK'
sample_unicode = (u'Hello World! \x80' +
six.text_type(testing.rand_string(0, 0)))

sample_utf8 = sample_unicode.encode('utf-8')

def __init__(self):
self.called = False
self.stream = ClosingBytesIO(self.sample_utf8)
self.stream_len = len(self.sample_utf8)

def on_get(self, req, resp):
self.called = True
self.req, self.resp = req, resp
resp.status = falcon.HTTP_200
resp.set_stream(self.stream, self.stream_len)


class NoStatusResource(object):
def on_get(self, req, resp):
pass
Expand Down Expand Up @@ -158,6 +186,28 @@ def test_filelike(self):
self.assertEqual(actual_len, expected_len)
self.assertEqual(len(result.content), expected_len)

for file_wrapper in (None, FileWrapper):
result = self.simulate_get('/filelike', file_wrapper=file_wrapper)
self.assertTrue(resource.called)

expected_len = resource.resp.stream_len
actual_len = int(result.headers['content-length'])
self.assertEqual(actual_len, expected_len)
self.assertEqual(len(result.content), expected_len)

def test_filelike_closing(self):
resource = ClosingFilelikeHelloResource()
self.api.add_route('/filelike-closing', resource)

result = self.simulate_get('/filelike-closing', file_wrapper=None)
self.assertTrue(resource.called)

expected_len = resource.resp.stream_len
actual_len = int(result.headers['content-length'])
self.assertEqual(actual_len, expected_len)
self.assertEqual(len(result.content), expected_len)
self.assertTrue(resource.stream.close_called)

def test_filelike_using_helper(self):
resource = HelloResource('stream, stream_len, filelike, use_helper')
self.api.add_route('/filelike-helper', resource)
Expand Down

0 comments on commit 52b643e

Please sign in to comment.