AsyncResponse
Base class containing the core async content streaming logic.
Streaming retains no payload history: library memory stays proportional to the
chunk size, and each send is awaited before the next read. All response classes
accept an integer chunk_size from 1 through 1,048,576 bytes (1 MiB), default
4096. Booleans and other values raise ValueError at construction, before
opening a stream or sending headers.
AsyncResponse uses Django's public StreamingHttpResponse interface:
is_async is true and streaming_content is an async iterator. Middleware such
as Django's GZipMiddleware can wrap that iterator; both stream(send) and
AsyncFileASGIHandler send the transformed bytes. Keep middleware wrappers
asynchronous to preserve bounded memory and backpressure. The original file or
S3 body/client context closes on completion, read/send failure or cancellation,
even when the wrapper does not forward iterator cleanup.
ASGI header names, including each separate set-cookie, are lowercase. Values
retain Django's encoding and newline validation.
For custom adapters, override _stream_content() with an async generator that
opens the resource in async with and yields chunks from _read_chunks(stream).
The existing send_stream_to_client(stream, send) helper remains available for
already-open streams; the caller owns that stream's context. Legacy adapters
that open a resource in stream(send) and call this helper also send the
middleware-wrapped iterator. Calling the helper binds the supplied stream
lazily without bypassing middleware or reopening the built-in file/S3 source.
Responses and their content iterators are single-use, as in Django.
class YourSpecialServiceResponse(AsyncResponse):
async def _stream_content(self):
async with your_service.open_stream() as stream:
async for chunk in self._read_chunks(stream):
yield chunk
chunks = []
async def send(chunk):
chunks.append(chunk)
class Stream:
def __init__(self, data):
self.index = 0
self.data = data
self.data_len = len(data)
async def read(self, chunk_size):
if self.index + chunk_size >= self.data_len:
chunk = self.data[self.index :]
else:
chunk = self.data[self.index : self.index + chunk_size]
self.index += chunk_size
return chunk
content = "1234567890"
stream = Stream(content)
response = AsyncResponse(chunk_size=3)
await response.send_stream_to_client(stream, send)
sent_body = "".join([c["body"] for c in chunks if "body" in c])
assert content == sent_body
response = AsyncResponse(chunk_size=3)
assert len(response.response_headers) > 0
AiofileFileResponse
Serve a file asynchronously using aiofiles.
from fileresponse.http import AiofileFileResponse
async def aget_file(request, num=None):
file_path = Path(__file__).parent.parent / "data" / str(num)
return AiofileFileResponse(file_path, chunk_size=4096)
import os
from pathlib import Path
path = Path("testdata.bin")
with path.open("wb") as f:
f.write(os.urandom(10000))
with path.open("rb") as f:
content = f.read()
chunk_datas = []
async def send(chunk_data):
chunk_datas.append(chunk_data)
response = AiofileFileResponse(path)
await response.stream(send)
received_content = b"".join([cd.get("body", b"") for cd in chunk_datas])
assert content == received_content
response = AiofileFileResponse("foobar")
assert "<AiofileFileResponse status_code=200>" == repr(response)
AiobotocoreFileResponse
Serve a file asynchronously using aiobotocore.
The adapter uses the public aiobotocore.session.get_session API. Constructor
overrides are resolved independently: endpoint_url, region_name,
aws_access_key_id, secret_access_key and use_ssl. Each supplied option
bypasses its Django S3 setting; omitted options fall back to the corresponding
FILERESPONSE_S3_* setting. use_ssl defaults to False. Missing required
settings raise ImproperlyConfigured at construction, before network access.
import django
try:
settings.configure()
except RuntimeError:
pass
def configure_settings():
settings.FILERESPONSE_S3_ENDPOINT_URL = "asdf"
settings.FILERESPONSE_S3_REGION = "asdf"
settings.FILERESPONSE_S3_ACCESS_KEY_ID = "asdf"
settings.FILERESPONSE_S3_SECRET_ACCESS_KEY = "asdf"
configure_settings()
response = AiobotocoreFileResponse("bucket", "key")
assert "<AiobotocoreFileResponse status_code=200>" == repr(response)
configure_settings()
expected_endpoint_url = "http://foobar:9000/"
settings.FILERESPONSE_S3_ENDPOINT_URL = "asdf"
response = AiobotocoreFileResponse("bucket", "key", endpoint_url=expected_endpoint_url)
assert expected_endpoint_url == response.client_config["endpoint_url"]