流处理#

请求流处理#

Sanic 允许客户端发送的数据字节到达时开始处理数据。

在端点上启用时,你可以使用 await request.stream.read() 来流式处理请求体。

from sanic.views import stream, HTTPMethodView

class SimpleView(HTTPMethodView):
    @stream
    async def post(self, request):
        result = ""
        while True:
            body = await request.stream.read()
            if body is None:
                break
            result += body.decode("utf-8")
        return text(result)

当请求体完成时,该方法将返回 None

它还可以通过装饰器中的关键字参数启用:

@app.post("/stream", stream=True)
async def handler(request):
    ...
    body = await request.stream.read()
    ...

或者使用 add_route()

bp.add_route(
    bp_handler,
    "/bp_stream",
    methods=["POST"],
    stream=True,
)

响应流处理#

Sanic 允许你将内容流式传输到客户端。

@app.route("/")
async def test(request):
    response = await request.respond(content_type="text/csv")
    await response.send("foo,")
    await response.send("bar")

    # Optionally, you can explicitly end the stream by calling:
    await response.eof()

这在你想要将源自外部服务(如数据库)的内容流式传输到客户端的情况下非常有用。例如,你可以使用 asyncpg 提供的异步游标将数据库记录流式传输到客户端。

@app.route("/")
async def index(request):
    response = await request.respond()
    conn = await asyncpg.connect(database='test')
    async with conn.transaction():
        async for record in conn.cursor('SELECT generate_series(0, 10)'):
            await response.send(record[0])

你可以通过调用 await response.eof() 来明确结束流。这是一个方便的方法,用来替换 await response.send("", True)。在你的处理程序确定没有其他内容要发送回客户端后,应该调用一次。虽然在 Sanic 服务器中使用它是可选的,但如果你在 ASGI 模式下运行 Sanic,那么你必须明确终止流。

文件流#

Sanic 提供了 sanic.response.file_stream 函数,当你想发送大文件时非常有用。它返回 StreamingHTTPResponse 对象,并默认使用分块传输编码;因此,Sanic 不会在响应中添加 Content-Length HTTP 头。

典型的用例可能是流式传输视频文件。

@app.route("/mp4")
async def handler_file_stream(request):
    return await response.file_stream(
        "/path/to/sample.mp4",
        chunk_size=1024,
        mime_type="application/metalink4+xml",
        headers={
            "Content-Disposition": 'Attachment; filename="nicer_name.meta4"',
            "Content-Type": "application/metalink4+xml",
        },
    )

如果你想使用 Content-Length 头,你可以通过添加 Content-Length 头来禁用分块传输编码并手动添加。

from aiofiles import os as async_os
from sanic.response import file_stream

@app.route("/")
async def index(request):
    file_path = "/srv/www/whatever.png"

    file_stat = await async_os.stat(file_path)
    headers = {"Content-Length": str(file_stat.st_size)}

    return await file_stream(
        file_path,
        headers=headers,
    )