Життєвий цикл запиту

← Життєвий цикл запиту

Ashterix

docs: rewrite JsonRpcBundle docs, add SDK docs, move them under docs/api

uk/docs/api/json-rpc-bundle/developers/lifecycle.md новий файл +95 −0

1 + # Життєвий цикл запиту
2 +
3 + Як JsonRpcBundle обробляє запит від моменту отримання до відповіді — для кожної точки входу. Знадобиться, щоб писати лістенери [подій](/docs/api/json-rpc-bundle/functionality/events) і [розширення](/docs/api/json-rpc-bundle/developers).
4 +
5 + ## HTTP POST
6 +
7 + Маршрути бандла оголошені як `GET` (для документації); POST-запити на ту саму адресу перехоплюються на `kernel.request` ще до роутингу й контролера.
8 +
9 + 1. **`kernel.request`, пріоритет 240 — `SymfonyFlowListener::initHttpRequest`**
10 + * визначає [групу API](/docs/api/json-rpc-bundle/functionality/groups) з маршруту й записує в `ApiGroupContext`;
11 + * `OPTIONS` (CORS preflight) отримує порожню відповідь;
12 + * для POST створює carrier `RpcFromHttp` (розбір JSON; тіло, що починається з `[`, — batch), токен-холдер `HttpTokenHolder` і перевіряє токен (якщо `protected_api: true`) — валідатором групи або базовим.
13 + 2. **`kernel.request`, пріоритет 230 — `RpcRequestHandler::handle()`**
14 + * **batch** — перевірка `batch.enabled` і порожнього batch, вибір стратегії (`IBatchStrategy`) і виконання елементів;
15 + * **одиночний запит** — перевірка `jsonrpc: "2.0"`, подія **`rpc.request`**, далі одна з гілок:
16 + * запит з `$rpc.callback` — миттєва відповідь-заглушка й **`rpc.pre_response`**, виконання відкладається;
17 + * notification — заглушка й **`rpc.pre_response`**, виконання відкладається;
18 + * звичайний запит — `RpcServer::handle()`.
19 + 3. **`RpcServer::handle()`**
20 + * пошук методу в карті сервісів (з урахуванням групи й версії); невідомий метод — помилка `-32601`;
21 + * **кеш**: для методів з `#[RPC\Cache]` (і без `$rpc.ignoreCache`) готова відповідь повертається одразу, без подій виконання;
22 + * подія **`rpc.pre_execute`**, на якій послідовно: формування аргументів (1002), валідація (1001), конвертація (1000), `#[RPC\Lock]` (-100), **виклик методу** (-1000);
23 + * успіх — **`rpc.pre_response`**, потім **`rpc.post_execute`**; помилка — **`rpc.error`**, потім **`rpc.pre_response`**;
24 + * збереження відповіді в кеш (для `#[RPC\Cache]`).
25 + 4. **Відповідь** — JSON; `204` без тіла, якщо відповідати нічого (notification, batch лише з notifications). Роутер і контролер Symfony для POST не запускаються.
26 + 5. **`kernel.terminate`** — після `fastcgi_finish_request()` (PHP-FPM) для кожного успішно виконаного запиту генерується **`rpc.post_response`**: інвалідація `#[RPC\CacheRelation]` і виконання notifications / callback. Потім `ApiGroupContext` скидається.
27 +
28 + Помилки, що виникли до виконання методу (недійсний JSON, токен, невідомий метод тощо), перетворює на відповідь `SymfonyErrorListener` на `kernel.exception`. HTTP-статус: 401 — токен не передано, 403 — недійсний, 500 — недійсний JSON, інакше 200.
29 +
30 + ## HTTP GET (документація)
31 +
32 + 1. Пріоритет 240 — визначення групи; якщо `protected_doc: true` — перевірка токена.
33 + 2. Пріоритет 8 — вибір мови з `Accept-Language` (`RpcLocaleNegotiator`).
34 + 3. Пріоритет 4 — поза debug-режимом відповідь з кешу: `304` для `If-None-Match` або збережений документ.
35 + 4. `ApiController::openrpc()` / `postman()` будує документ для версії та групи.
36 + 5. `kernel.response` — `Content-Language`, `Vary: Accept-Language`, `ETag`, збереження в кеш (лише для `200`).
37 +
38 + RPC-події на цьому шляху не генеруються.
39 +
40 + ## Консоль: `ufo:rpc:process`
41 +
42 + Команда не має власної логіки — запит обробляє лістенер `console.command` (пріоритет 999999):
43 +
44 + 1. читання JSON (аргумент, stdin `-` або змінна `UFO_RPC_REQUEST`);
45 + 2. група з `--api-group`, версія з `--ver`;
46 + 3. carrier `RpcFromCli`, токен з опції `--<token_name>` або змінної `UFO_RPC_TOKEN`;
47 + 4. `RpcRequestHandler::handle()` — далі як у HTTP;
48 + 5. вивід JSON у stdout;
49 + 6. `console.terminate` — `rpc.post_response`.
50 +
51 + Помилки обробляються на `console.error` і проходять через `rpc.error`.
52 +
53 + ## Symfony Messenger
54 +
55 + `RpcAsyncProcessor` — обробник повідомлень `RpcAsyncRequest`:
56 +
57 + 1. група з `$message->apiGroup`, токен з `$message->token`, перевірка токена;
58 + 2. виклик `RpcServer::handle()` напряму.
59 +
60 + Тож для запитів з черги **немає** `rpc.request` і `rpc.post_response`; `rpc.pre_execute`, `rpc.pre_response`, `rpc.post_execute` і `rpc.error` генеруються. Помилки токена й невідомого методу стають помилками обробки повідомлення Messenger (повторні спроби, failure transport).
61 +
62 + ## Batch
63 +
64 + * **Процесний режим** (`batch.process: true`) — кожен готовий елемент запускається в дочірньому процесі `ufo:rpc:process -` (запит через stdin, токен через змінну оточення, група й версія — опціями), не більше `batch.process_count` одночасно. Кожен дочірній процес — повний консольний цикл зі своїми подіями. Батьківський процес на виводі кожного генерує **`rpc.async_output`**; коли розв'язується залежність `@FROM`, запускає наступні елементи.
65 + * **Inline-режим** (`batch.process: false`) — елементи по черзі в поточному процесі через `RpcExecutor`, кожен з власним пулом подій; `rpc.post_response` — одразу після елемента.
66 + * Елементи, залежність яких не виконалась, завершуються помилкою в батьківському процесі.
67 +
68 + ## Notifications і `$rpc.callback`
69 +
70 + 1. При прийомі — `rpc.request` і `rpc.pre_response` із заглушкою; клієнт отримує `204` (notification) або `{"async": true, "callback": …}`.
71 + 2. На `rpc.post_response` (пріоритет 10) `AsyncServerEventListener` виконує метод — повний цикл `rpc.pre_execute` → `rpc.pre_response` → `rpc.post_execute` / `rpc.error`.
72 + 3. Для callback результат надсилається POST-запитом на вказаний URL.
73 +
74 + З `notification.process: true` одиночний HTTP-запит передається від'єднаному процесу `ufo:rpc:process @env`: воркер чекає підтвердження до 3 секунд, інакше виконує запит сам. Кількість від'єднаних процесів обмежує `notification.process_count`.
75 +
76 + ## `RpcExecutor` (адаптери)
77 +
78 + `Ufo\JsonRpcBundle\Server\RpcExecutor::execute(IRpcRequestCarrier $carrier): RpcResponse` — точка входу для розширень, що приймають запити не через маршрут бандла (REST, MCP):
79 +
80 + * встановлює carrier, генерує `rpc.request` і далі як в одиночному запиті;
81 + * будь-який виняток проходить через `rpc.error` і повертається як відповідь з помилкою;
82 + * `rpc.post_response` — на `kernel.terminate` основного HTTP-запиту;
83 + * **токен не перевіряє** — це робить адаптер.
84 +
85 + ## Де що зберігається під час запиту
86 +
87 + | Сервіс | Що містить |
88 + | --- | --- |
89 + | `RequestCarrier` | Поточний carrier (`RpcFromHttp`, `RpcFromCli`, carrier адаптера) і об'єкти запиту |
90 + | `ApiGroupContext` | Група API поточного запиту |
91 + | `IRpcSecurity` | Токен-холдер поточного запиту |
92 + | `RpcEventFactory` | Усі події, згенеровані для запиту (`getEvent()`, `getEvents()`) |
93 + | `PostResponseQueue` | Запити, для яких буде згенеровано `rpc.post_response` |
94 +
95 + У довгоживучих воркерах ці сервіси скидаються між запитами (`ResetInterface`).