mirror of https://github.com/tiangolo/fastapi.git
118 lines
8.9 KiB
Markdown
118 lines
8.9 KiB
Markdown
# Потоковая передача данных { #stream-data }
|
||
|
||
Если вам нужно передавать потоковые данные, которые можно представить как JSON, воспользуйтесь [стримингом JSON Lines](../tutorial/stream-json-lines.md).
|
||
|
||
Но если вы хотите передавать в потоке чистые бинарные данные или строки, ниже показано, как это сделать.
|
||
|
||
/// info | Информация
|
||
|
||
Добавлено в FastAPI 0.134.0.
|
||
|
||
///
|
||
|
||
## Варианты использования { #use-cases }
|
||
|
||
Это можно использовать, если вы хотите стримить чистые строки, например, напрямую из вывода сервиса **AI LLM**.
|
||
|
||
Также вы можете стримить **большие бинарные файлы**, передавая каждый чанк данных по мере чтения, без необходимости загружать всё в память сразу.
|
||
|
||
Аналогично можно стримить **видео** или **аудио** — данные могут даже генерироваться по мере обработки и отправки.
|
||
|
||
## «`StreamingResponse` с `yield`» { #a-streamingresponse-with-yield }
|
||
|
||
Если вы укажете `response_class=StreamingResponse` в своей *функции-обработчике пути*, вы можете использовать `yield`, чтобы по очереди отправлять каждый чанк данных.
|
||
|
||
{* ../../docs_src/stream_data/tutorial001_py310.py ln[1:23] hl[20,23] *}
|
||
|
||
FastAPI будет передавать каждый чанк данных в `StreamingResponse` как есть, не пытаясь конвертировать его в JSON или что-то подобное.
|
||
|
||
### Не-async *функции-обработчики пути* { #non-async-path-operation-functions }
|
||
|
||
Можно использовать и обычные функции `def` (без `async`) и точно так же применять `yield`.
|
||
|
||
{* ../../docs_src/stream_data/tutorial001_py310.py ln[26:29] hl[27] *}
|
||
|
||
### Без аннотации { #no-annotation }
|
||
|
||
Для потоковой передачи бинарных данных вам не нужно указывать аннотацию типа возвращаемого значения.
|
||
|
||
Поскольку FastAPI не будет пытаться конвертировать данные в JSON с помощью Pydantic или сериализовать их каким-либо образом, в данном случае аннотация типа нужна только для вашего редактора кода и инструментов, FastAPI её использовать не будет.
|
||
|
||
{* ../../docs_src/stream_data/tutorial001_py310.py ln[32:35] hl[33] *}
|
||
|
||
Это также означает, что с `StreamingResponse` у вас есть и свобода, и ответственность — производить и кодировать байты данных ровно в том виде, в котором они должны быть отправлены, независимо от аннотаций типов. 🤓
|
||
|
||
### Потоковая передача байтов { #stream-bytes }
|
||
|
||
Один из основных сценариев — стримить `bytes` вместо строк, и, конечно, это можно сделать.
|
||
|
||
{* ../../docs_src/stream_data/tutorial001_py310.py ln[44:47] hl[47] *}
|
||
|
||
## Пользовательский `PNGStreamingResponse` { #a-custom-pngstreamingresponse }
|
||
|
||
В примерах выше байты данных передавались потоком, но в ответе не было HTTP-заголовка `Content-Type`, поэтому клиент не знал, какой тип данных он получает.
|
||
|
||
Вы можете создать пользовательский подкласс `StreamingResponse`, который устанавливает HTTP-заголовок `Content-Type` в тип данных, которые вы стримите.
|
||
|
||
Например, вы можете создать `PNGStreamingResponse`, который устанавливает HTTP-заголовок `Content-Type` в `image/png` с помощью атрибута `media_type`:
|
||
|
||
{* ../../docs_src/stream_data/tutorial002_py310.py ln[6,19:20] hl[20] *}
|
||
|
||
Затем вы можете использовать этот новый класс в `response_class=PNGStreamingResponse` в своей *функции-обработчике пути*:
|
||
|
||
{* ../../docs_src/stream_data/tutorial002_py310.py ln[23:27] hl[23] *}
|
||
|
||
### Симулировать файл { #simulate-a-file }
|
||
|
||
В этом примере мы симулируем файл с помощью `io.BytesIO` — это «файлоподобный» объект, который существует только в памяти, но позволяет использовать тот же интерфейс.
|
||
|
||
Например, мы можем итерироваться по нему, чтобы потреблять его содержимое, как и по обычному файлу.
|
||
|
||
{* ../../docs_src/stream_data/tutorial002_py310.py ln[1:27] hl[3,12:13,25] *}
|
||
|
||
/// note | Технические детали
|
||
|
||
Две другие переменные, `image_base64` и `binary_image`, — это изображение, закодированное в Base64, а затем преобразованное в байты, после чего переданное в `io.BytesIO`.
|
||
|
||
Только для того, чтобы всё помещалось в одном файле для этого примера, и вы могли скопировать код и запустить его как есть. 🥚
|
||
|
||
///
|
||
|
||
Используя блок `with`, мы гарантируем, что объект, ведущий себя как файл, будет закрыт после завершения работы функции‑генератора (функции с `yield`). То есть после того, как она закончит отправку ответа.
|
||
|
||
В этом конкретном примере это не столь важно, потому что это «фейковый» файл в памяти (`io.BytesIO`), но для реального файла важно удостовериться, что файл закрыт после завершения работы с ним.
|
||
|
||
### Файлы и async { #files-and-async }
|
||
|
||
В большинстве случаев «файлоподобные» объекты по умолчанию не совместимы с async и await.
|
||
|
||
Например, у них нет `await file.read()` или `async for chunk in file`.
|
||
|
||
И во многих случаях чтение таких объектов будет блокирующей операцией (которая может заблокировать цикл событий), потому что данные читаются с диска или из сети.
|
||
|
||
/// info | Информация
|
||
|
||
Приведённый выше пример — исключение, потому что объект `io.BytesIO` уже находится в памяти, поэтому чтение ничего не блокирует.
|
||
|
||
Но во многих случаях чтение файла или «файлоподобного» объекта будет блокировать.
|
||
|
||
///
|
||
|
||
Чтобы не блокировать цикл событий, вы можете просто объявить *функцию-обработчик пути* обычной `def` вместо `async def`. Тогда FastAPI выполнит её в воркере из пула потоков (threadpool), чтобы не блокировать основной цикл.
|
||
|
||
{* ../../docs_src/stream_data/tutorial002_py310.py ln[30:34] hl[31] *}
|
||
|
||
/// tip | Совет
|
||
|
||
Если вам нужно вызвать блокирующий код изнутри async-функции, или async-функцию изнутри блокирующей функции, вы можете использовать [Asyncer](https://asyncer.tiangolo.com), родственную библиотеку к FastAPI.
|
||
|
||
///
|
||
|
||
### `yield from` { #yield-from }
|
||
|
||
Когда вы итерируетесь по чему‑то, например, по «файлоподобному» объекту, и делаете `yield` для каждого элемента, вы можете также использовать `yield from`, чтобы отдавать каждый элемент напрямую и не писать цикл `for`.
|
||
|
||
Это не специфично для FastAPI, это просто Python, но полезный приём. 😎
|
||
|
||
{* ../../docs_src/stream_data/tutorial002_py310.py ln[37:40] hl[40] *}
|