From e155fe403d8ed86762acff88ae2848f7a0c136cb Mon Sep 17 00:00:00 2001 From: ashleysommer Date: Thu, 18 May 2017 18:04:28 +1000 Subject: [PATCH] Add file_stream response handler For streaming large static files Like `file()` but breaks the file into chunks and sends it with a `StreamingHTTPResponse` Chunk size is configurable, but defaults to 4k, this seemed to be the sweet spot in my testing. Also supports ContentRange same as `file()` does. --- sanic/response.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/sanic/response.py b/sanic/response.py index 0fc3c575..34a53387 100644 --- a/sanic/response.py +++ b/sanic/response.py @@ -310,6 +310,55 @@ async def file(location, mime_type=None, headers=None, _range=None): body_bytes=out_stream) +async def file_stream(location, chunk_size=4096, mime_type=None, headers=None, + _range=None): + """Return a streaming response object with file data. + + :param location: Location of file on system. + :param chunk_size: The size of each chunk in the stream (in bytes) + :param mime_type: Specific mime_type. + :param headers: Custom Headers. + :param _range: + """ + filename = path.split(location)[-1] + + _file = await open_async(location, mode='rb') + + async def _streaming_fn(response): + nonlocal _file, chunk_size + try: + if _range: + chunk_size = min((_range.size, chunk_size)) + await _file.seek(_range.start) + to_send = _range.size + while to_send > 0: + content = await _file.read(chunk_size) + if len(content) < 1: + break + to_send -= len(content) + response.write(content) + else: + while True: + content = await _file.read(chunk_size) + if len(content) < 1: + break + response.write(content) + except Exception as e: + print(e) + finally: + await _file.close() + return # Returning from this fn closes the stream + + mime_type = mime_type or guess_type(filename)[0] or 'text/plain' + if _range: + headers['Content-Range'] = 'bytes %s-%s/%s' % ( + _range.start, _range.end, _range.total) + return StreamingHTTPResponse(streaming_fn=_streaming_fn, + status=200, + headers=headers, + content_type=mime_type) + + def stream( streaming_fn, status=200, headers=None, content_type="text/plain; charset=utf-8"):