Browse Source

Add missing (part 2)

pull/15152/head
Yurii Motov 4 months ago
parent
commit
638bf73be1
  1. 63
      docs/ru/docs/advanced/json-base64-bytes.md
  2. 117
      docs/ru/docs/advanced/stream-data.md
  3. 88
      docs/ru/docs/advanced/strict-content-type.md
  4. 111
      docs/ru/docs/tutorial/stream-json-lines.md

63
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:
<div class="screenshot">
<img src="/img/tutorial/json-base64-bytes/image01.png">
</div>
Вы можете отправить такой 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] *}

117
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] *}

88
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.
///

111
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). 🤓
Loading…
Cancel
Save