diff --git a/CHANGES.rst b/CHANGES.rst index 5b5f54f741f..afd6e9d3e6a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -22,6 +22,8 @@ CHANGES - Do not use readline when reading the content of a part in the multipart reader #1535 +- 416 Range Not Satisfiable if requested range end > file size #1588 + - Having a `:` or `@` in a route does not work #1552 - Added `receive_timeout` timeout for websocket to receive complete message. #1325 diff --git a/aiohttp/file_sender.py b/aiohttp/file_sender.py index b2005a668a2..4255881c259 100644 --- a/aiohttp/file_sender.py +++ b/aiohttp/file_sender.py @@ -184,7 +184,14 @@ def send(self, request, filepath): count = (end or file_size) - start if start + count > file_size: - raise HTTPRequestRangeNotSatisfiable + # rfc7233:If the last-byte-pos value is + # absent, or if the value is greater than or equal to + # the current length of the representation data, + # the byte range is interpreted as the remainder + # of the representation (i.e., the server replaces the + # value of last-byte-pos with a value that is one less than + # the current length of the selected representation). + count = file_size - start resp = self._response_factory(status=status) resp.content_type = ct diff --git a/tests/test_web_sendfile_functional.py b/tests/test_web_sendfile_functional.py index eaf1fa0328f..bc6009a111e 100644 --- a/tests/test_web_sendfile_functional.py +++ b/tests/test_web_sendfile_functional.py @@ -371,6 +371,36 @@ def handler(request): assert content == b"".join(body) +@asyncio.coroutine +def test_static_file_range_end_bigger_than_size(loop, test_client, sender): + filepath = (pathlib.Path(__file__).parent / 'aiohttp.png') + + @asyncio.coroutine + def handler(request): + resp = yield from sender(chunk_size=16).send(request, filepath) + return resp + + app = web.Application(loop=loop) + app.router.add_get('/', handler) + client = yield from test_client(lambda loop: app) + + with filepath.open('rb') as f: + content = f.read() + + # Ensure the whole file requested in parts is correct + response = yield from client.get( + '/', headers={'Range': 'bytes=61000-62000'}) + + assert response.status == 206, \ + "failed 'bytes=61000-62000': %s" % response.reason + + body = yield from response.read() + assert len(body) == 108, \ + "failed 'bytes=0-999', received %d bytes" % len(body[0]) + + assert content[61000:] == body + + @asyncio.coroutine def test_static_file_range_tail(loop, test_client, sender): filepath = (pathlib.Path(__file__).parent / 'aiohttp.png') @@ -408,18 +438,11 @@ def handler(request): app.router.add_get('/', handler) client = yield from test_client(lambda loop: app) - flen = filepath.stat().st_size - # range must be in bytes resp = yield from client.get('/', headers={'Range': 'blocks=0-10'}) assert resp.status == 416, 'Range must be in bytes' resp.close() - # Range end is inclusive - resp = yield from client.get('/', headers={'Range': 'bytes=0-%d' % flen}) - assert resp.status == 416, 'Range end must be inclusive' - resp.close() - # start > end resp = yield from client.get('/', headers={'Range': 'bytes=100-0'}) assert resp.status == 416, "Range start can't be greater than end"