From 638bf73be135e915a9dedf57331a25a11d97a3c9 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Thu, 19 Mar 2026 08:04:43 +0100 Subject: [PATCH] Add missing (part 2) --- docs/ru/docs/advanced/json-base64-bytes.md | 63 ++++++++++ docs/ru/docs/advanced/stream-data.md | 117 +++++++++++++++++++ docs/ru/docs/advanced/strict-content-type.md | 88 ++++++++++++++ docs/ru/docs/tutorial/stream-json-lines.md | 111 ++++++++++++++++++ 4 files changed, 379 insertions(+) create mode 100644 docs/ru/docs/advanced/json-base64-bytes.md create mode 100644 docs/ru/docs/advanced/stream-data.md create mode 100644 docs/ru/docs/advanced/strict-content-type.md create mode 100644 docs/ru/docs/tutorial/stream-json-lines.md diff --git a/docs/ru/docs/advanced/json-base64-bytes.md b/docs/ru/docs/advanced/json-base64-bytes.md new file mode 100644 index 0000000000..390dd17fa1 --- /dev/null +++ b/docs/ru/docs/advanced/json-base64-bytes.md @@ -0,0 +1,63 @@ +# JSON с байтами в Base64 { #json-with-bytes-as-base64 } + +Если вашему приложению нужно принимать и отправлять JSON-данные, но при этом необходимо включать в них бинарные данные, вы можете кодировать их в base64. + +## Base64 и файлы { #base64-vs-files } + +Сначала рассмотрите возможность использовать [Файлы в запросе](../tutorial/request-files.md) для загрузки бинарных данных и [Пользовательский HTTP-ответ — FileResponse](./custom-response.md#fileresponse--fileresponse-) для отправки бинарных данных вместо кодирования их в JSON. + +JSON может содержать только строки в кодировке UTF-8, поэтому он не может содержать «сырые» байты. + +Base64 может кодировать бинарные данные в строки, но для этого используется больше символов, чем в исходных бинарных данных, поэтому обычно это менее эффективно, чем обычные файлы. + +Используйте base64 только если вам действительно нужно включать бинарные данные в JSON и вы не можете использовать файлы для этого. + +## Pydantic `bytes` { #pydantic-bytes } + +Вы можете объявить Pydantic-модель с полями `bytes`, а затем использовать `val_json_bytes` в конфиге модели, чтобы указать использовать base64 для валидации входящих JSON-данных; как часть этой валидации строка base64 будет декодирована в байты. + +{* ../../docs_src/json_base64_bytes/tutorial001_py310.py ln[1:9,29:35] hl[9] *} + +Если вы откроете `/docs`, вы увидите, что поле `data` ожидает байты, закодированные в base64: + +
+ +
+ +Вы можете отправить такой HTTP-запрос: + +```json +{ + "description": "Some data", + "data": "aGVsbG8=" +} +``` + +/// tip | Совет + +`aGVsbG8=` — это base64-кодирование строки `hello`. + +/// + +Затем Pydantic декодирует строку base64 и передаст вам исходные байты в поле `data` модели. + +Вы получите такой HTTP-ответ: + +```json +{ + "description": "Some data", + "content": "hello" +} +``` + +## Pydantic `bytes` для выходных данных { #pydantic-bytes-for-output-data } + +Вы также можете использовать поля `bytes` с `ser_json_bytes` в конфиге модели для выходных данных, и Pydantic будет сериализовать байты в base64 при формировании JSON-ответа. + +{* ../../docs_src/json_base64_bytes/tutorial001_py310.py ln[1:2,12:16,29,38:41] hl[16] *} + +## Pydantic `bytes` для входных и выходных данных { #pydantic-bytes-for-input-and-output-data } + +И, конечно, вы можете использовать одну и ту же модель, настроенную на использование base64, чтобы обрабатывать и входящие данные (валидация) с `val_json_bytes`, и исходящие данные (сериализация) с `ser_json_bytes` при приеме и отправке JSON-данных. + +{* ../../docs_src/json_base64_bytes/tutorial001_py310.py ln[1:2,19:26,29,44:46] hl[23:26] *} diff --git a/docs/ru/docs/advanced/stream-data.md b/docs/ru/docs/advanced/stream-data.md new file mode 100644 index 0000000000..4c373db1ad --- /dev/null +++ b/docs/ru/docs/advanced/stream-data.md @@ -0,0 +1,117 @@ +# Потоковая передача данных { #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] *} diff --git a/docs/ru/docs/advanced/strict-content-type.md b/docs/ru/docs/advanced/strict-content-type.md new file mode 100644 index 0000000000..1a0cbbc31d --- /dev/null +++ b/docs/ru/docs/advanced/strict-content-type.md @@ -0,0 +1,88 @@ +# Строгая проверка HTTP-заголовка Content-Type { #strict-content-type-checking } + +По умолчанию **FastAPI** использует строгую проверку HTTP-заголовка `Content-Type` для JSON-тел запросов. Это означает, что JSON-запросы должны включать корректный заголовок `Content-Type` (например, `application/json`), чтобы тело запроса было обработано как JSON. + +## Риск CSRF { #csrf-risk } + +Такое поведение по умолчанию обеспечивает защиту от класса атак **Cross-Site Request Forgery (CSRF)** в очень специфическом сценарии. + +Эти атаки используют то, что браузеры позволяют скриптам отправлять запросы без выполнения CORS preflight, когда они: + +- не имеют заголовка `Content-Type` (например, при использовании `fetch()` с телом `Blob`) +- и не отправляют никаких учетных данных аутентификации. + +Этот тип атак в основном актуален, когда: + +- приложение запускается локально (например, на `localhost`) или во внутренней сети +- и в приложении нет аутентификации, оно предполагает, что любому запросу из той же сети можно доверять. + +## Пример атаки { #example-attack } + +Представьте, что вы сделали способ запускать локального ИИ-агента. + +Он предоставляет API по адресу + +``` +http://localhost:8000/v1/agents/multivac +``` + +Есть также фронтенд по адресу + +``` +http://localhost:8000 +``` + +/// tip | Совет + +Обратите внимание, что у обоих один и тот же хост. + +/// + +Через фронтенд вы можете заставлять ИИ-агента выполнять действия от вашего имени. + +Так как он работает локально и не в открытом интернете, вы решаете не настраивать аутентификацию, полагаясь на доступ к локальной сети. + +Затем один из ваших пользователей может установить это и запускать локально. + +Потом он может открыть вредоносный сайт, например что-то вроде + +``` +https://evilhackers.example.com +``` + +И этот вредоносный сайт отправит запросы с помощью `fetch()` с телом `Blob` к локальному API по адресу + +``` +http://localhost:8000/v1/agents/multivac +``` + +Несмотря на то, что хост вредоносного сайта и локального приложения различается, браузер не запустит CORS preflight-запрос, потому что: + +- Приложение работает без аутентификации, ему не нужно отправлять какие-либо учетные данные. +- Браузер «считает», что он не отправляет JSON (из-за отсутствия заголовка `Content-Type`). + +В итоге вредоносный сайт может заставить локального ИИ-агента отправить злые сообщения бывшему начальнику пользователя... или что-то похуже. 😅 + +## Открытый интернет { #open-internet } + +Если ваше приложение доступно в открытом интернете, вы не будете «доверять сети» и позволять кому угодно отправлять привилегированные запросы без аутентификации. + +Злоумышленники могут просто запустить скрипт и слать запросы к вашему API, без участия браузера, так что вы, вероятно, уже защищаете любые привилегированные эндпоинты. + +В этом случае эта атака/риск на вас не распространяется. + +Этот риск и атака в основном актуальны, когда приложение работает в локальной сети и это единственная предполагаемая защита. + +## Разрешение запросов без Content-Type { #allowing-requests-without-content-type } + +Если вам нужно поддерживать клиентов, которые не отправляют заголовок `Content-Type`, вы можете отключить строгую проверку, установив `strict_content_type=False`: + +{* ../../docs_src/strict_content_type/tutorial001_py310.py hl[4] *} + +С этой настройкой запросы без заголовка `Content-Type` будут иметь тело запроса, обработанное как JSON — это такое же поведение, как в более старых версиях FastAPI. + +/// info | Информация + +Это поведение и настройка были добавлены в FastAPI 0.132.0. + +/// diff --git a/docs/ru/docs/tutorial/stream-json-lines.md b/docs/ru/docs/tutorial/stream-json-lines.md new file mode 100644 index 0000000000..d8bb9132b7 --- /dev/null +++ b/docs/ru/docs/tutorial/stream-json-lines.md @@ -0,0 +1,111 @@ +# Стриминг JSON Lines { #stream-json-lines } + +У вас может быть последовательность данных, которую вы хотите отправлять в «**потоке**». Это можно сделать с помощью **JSON Lines**. + +/// info | Информация + +Добавлено в FastAPI 0.134.0. + +/// + +## Что такое поток? { #what-is-a-stream } + +«**Стриминг**» данных означает, что ваше приложение начнет отправлять элементы данных клиенту, не дожидаясь готовности всей последовательности. + +То есть оно отправит первый элемент, клиент его получит и начнет обрабатывать, а вы в это время можете все еще генерировать следующий элемент. + +```mermaid +sequenceDiagram + participant App + participant Client + + App->>App: Produce Item 1 + App->>Client: Send Item 1 + App->>App: Produce Item 2 + Client->>Client: Process Item 1 + App->>Client: Send Item 2 + App->>App: Produce Item 3 + Client->>Client: Process Item 2 + App->>Client: Send Item 3 + Client->>Client: Process Item 3 + Note over App: Keeps producing... + Note over Client: Keeps consuming... +``` + +Это может быть даже бесконечный поток, когда вы продолжаете отправлять данные. + +## JSON Lines { #json-lines } + +В таких случаях часто отправляют «**JSON Lines**», это формат, в котором отправляется по одному JSON-объекту на строку. + +Ответ будет иметь тип содержимого `application/jsonl` (вместо `application/json`), а тело ответа будет примерно таким: + +```json +{"name": "Plumbus", "description": "A multi-purpose household device."} +{"name": "Portal Gun", "description": "A portal opening device."} +{"name": "Meeseeks Box", "description": "A box that summons a Meeseeks."} +``` + +Это очень похоже на JSON-массив (эквивалент списка Python), но вместо того чтобы быть обернутым в `[]` и иметь `,` между элементами, здесь **один JSON-объект на строку**, они разделены символом новой строки. + +/// info | Информация + +Важный момент в том, что ваше приложение сможет по очереди производить каждую строку, пока клиент потребляет предыдущие строки. + +/// + +/// note | Технические детали + +Так как каждый JSON-объект будет разделен новой строкой, в их содержимом не могут быть буквальные символы новой строки, но могут быть экранированные переводы строк (`\n`), что входит в стандарт JSON. + +Однако обычно об этом не нужно беспокоиться — всё делается автоматически, читайте дальше. 🤓 + +/// + +## Варианты использования { #use-cases } + +Вы можете использовать это для стриминга данных из сервиса **AI LLM**, из **логов** или **телеметрии**, или из других типов данных, которые можно структурировать в элементы **JSON**. + +/// tip | Совет + +Если вы хотите стримить бинарные данные, например видео или аудио, посмотрите расширенное руководство: [Потоковая передача данных](../advanced/stream-data.md). + +/// + +## Стриминг JSON Lines с FastAPI { #stream-json-lines-with-fastapi } + +Чтобы стримить JSON Lines с FastAPI, вместо использования `return` в вашей *функции-обработчике пути* используйте `yield`, чтобы по очереди выдавать каждый элемент. + +{* ../../docs_src/stream_json_lines/tutorial001_py310.py ln[1:24] hl[24] *} + +Если каждый JSON-элемент, который вы хотите отправить обратно, имеет тип `Item` (Pydantic-модель), и это асинхронная функция, вы можете объявить тип возвращаемого значения как `AsyncIterable[Item]`: + +{* ../../docs_src/stream_json_lines/tutorial001_py310.py ln[1:24] hl[9:11,22] *} + +Если вы объявите тип возвращаемого значения, FastAPI будет использовать его, чтобы **валидировать** данные, **документировать** их в OpenAPI, **фильтровать** и **сериализовать** с помощью Pydantic. + +/// tip | Совет + +Так как Pydantic будет сериализовывать это на стороне **Rust**, вы получите значительно более высокую **производительность**, чем если бы вы не указывали тип возвращаемого значения. + +/// + +### Неасинхронные функции-обработчики пути { #non-async-path-operation-functions } + +Вы также можете использовать обычные функции `def` (без `async`) и использовать `yield` таким же образом. + +FastAPI обеспечит корректное выполнение так, чтобы это не блокировало цикл событий. + +Поскольку в этом случае функция не асинхронная, подходящим типом возвращаемого значения будет `Iterable[Item]`: + +{* ../../docs_src/stream_json_lines/tutorial001_py310.py ln[27:30] hl[28] *} + +### Без возвращаемого типа { #no-return-type } + +Вы также можете опустить тип возвращаемого значения. Тогда FastAPI использует [`jsonable_encoder`](./encoder.md), чтобы преобразовать данные к виду, который можно сериализовать в JSON, и затем отправит их как JSON Lines. + +{* ../../docs_src/stream_json_lines/tutorial001_py310.py ln[33:36] hl[34] *} + +## События, отправляемые сервером (SSE) { #server-sent-events-sse } + +FastAPI также имеет полноценную поддержку Server-Sent Events (SSE), которые довольно похожи, но с парой дополнительных деталей. Вы можете узнать о них в следующей главе: [События, отправляемые сервером (SSE)](server-sent-events.md). 🤓