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

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

Ashterix

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

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

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

Як JsonRpcBundle обробляє запит від моменту отримання до відповіді — для кожної точки входу. Знадобиться, щоб писати лістенери подій і розширення.

HTTP POST

Маршрути бандла оголошені як GET (для документації); POST-запити на ту саму адресу перехоплюються на kernel.request ще до роутингу й контролера.

  1. kernel.request, пріоритет 240 — SymfonyFlowListener::initHttpRequest
    • визначає групу API з маршруту й записує в ApiGroupContext;
    • OPTIONS (CORS preflight) отримує порожню відповідь;
    • для POST створює carrier RpcFromHttp (розбір JSON; тіло, що починається з [, — batch), токен-холдер HttpTokenHolder і перевіряє токен (якщо protected_api: true) — валідатором групи або базовим.
  2. kernel.request, пріоритет 230 — RpcRequestHandler::handle()
    • batch — перевірка batch.enabled і порожнього batch, вибір стратегії (IBatchStrategy) і виконання елементів;
    • одиночний запит — перевірка jsonrpc: "2.0", подія rpc.request, далі одна з гілок:
      • запит з $rpc.callback — миттєва відповідь-заглушка й rpc.pre_response, виконання відкладається;
      • notification — заглушка й rpc.pre_response, виконання відкладається;
      • звичайний запит — RpcServer::handle().
  3. RpcServer::handle()
    • пошук методу в карті сервісів (з урахуванням групи й версії); невідомий метод — помилка -32601;
    • кеш: для методів з #[RPC\Cache] (і без $rpc.ignoreCache) готова відповідь повертається одразу, без подій виконання;
    • подія rpc.pre_execute, на якій послідовно: формування аргументів (1002), валідація (1001), конвертація (1000), #[RPC\Lock] (-100), виклик методу (-1000);
    • успіх — rpc.pre_response, потім rpc.post_execute; помилка — rpc.error, потім rpc.pre_response;
    • збереження відповіді в кеш (для #[RPC\Cache]).
  4. Відповідь — JSON; 204 без тіла, якщо відповідати нічого (notification, batch лише з notifications). Роутер і контролер Symfony для POST не запускаються.
  5. kernel.terminate — після fastcgi_finish_request() (PHP-FPM) для кожного успішно виконаного запиту генерується rpc.post_response: інвалідація #[RPC\CacheRelation] і виконання notifications / callback. Потім ApiGroupContext скидається.

Помилки, що виникли до виконання методу (недійсний JSON, токен, невідомий метод тощо), перетворює на відповідь SymfonyErrorListener на kernel.exception. HTTP-статус: 401 — токен не передано, 403 — недійсний, 500 — недійсний JSON, інакше 200.

HTTP GET (документація)

  1. Пріоритет 240 — визначення групи; якщо protected_doc: true — перевірка токена.
  2. Пріоритет 8 — вибір мови з Accept-Language (RpcLocaleNegotiator).
  3. Пріоритет 4 — поза debug-режимом відповідь з кешу: 304 для If-None-Match або збережений документ.
  4. ApiController::openrpc() / postman() будує документ для версії та групи.
  5. kernel.response — Content-Language, Vary: Accept-Language, ETag, збереження в кеш (лише для 200).

RPC-події на цьому шляху не генеруються.

Консоль: ufo:rpc:process

Команда не має власної логіки — запит обробляє лістенер console.command (пріоритет 999999):

  1. читання JSON (аргумент, stdin - або змінна UFO_RPC_REQUEST);
  2. група з --api-group, версія з --ver;
  3. carrier RpcFromCli, токен з опції --<token_name> або змінної UFO_RPC_TOKEN;
  4. RpcRequestHandler::handle() — далі як у HTTP;
  5. вивід JSON у stdout;
  6. console.terminate — rpc.post_response.

Помилки обробляються на console.error і проходять через rpc.error.

Symfony Messenger

RpcAsyncProcessor — обробник повідомлень RpcAsyncRequest:

  1. група з $message->apiGroup, токен з $message->token, перевірка токена;
  2. виклик RpcServer::handle() напряму.

Тож для запитів з черги немає rpc.request і rpc.post_response; rpc.pre_execute, rpc.pre_response, rpc.post_execute і rpc.error генеруються. Помилки токена й невідомого методу стають помилками обробки повідомлення Messenger (повторні спроби, failure transport).

Batch

  • Процесний режим (batch.process: true) — кожен готовий елемент запускається в дочірньому процесі ufo:rpc:process - (запит через stdin, токен через змінну оточення, група й версія — опціями), не більше batch.process_count одночасно. Кожен дочірній процес — повний консольний цикл зі своїми подіями. Батьківський процес на виводі кожного генерує rpc.async_output; коли розв'язується залежність @FROM, запускає наступні елементи.
  • Inline-режим (batch.process: false) — елементи по черзі в поточному процесі через RpcExecutor, кожен з власним пулом подій; rpc.post_response — одразу після елемента.
  • Елементи, залежність яких не виконалась, завершуються помилкою в батьківському процесі.

Notifications і $rpc.callback

  1. При прийомі — rpc.request і rpc.pre_response із заглушкою; клієнт отримує 204 (notification) або {"async": true, "callback": …}.
  2. На rpc.post_response (пріоритет 10) AsyncServerEventListener виконує метод — повний цикл rpc.pre_execute → rpc.pre_response → rpc.post_execute / rpc.error.
  3. Для callback результат надсилається POST-запитом на вказаний URL.

З notification.process: true одиночний HTTP-запит передається від'єднаному процесу ufo:rpc:process @env: воркер чекає підтвердження до 3 секунд, інакше виконує запит сам. Кількість від'єднаних процесів обмежує notification.process_count.

RpcExecutor (адаптери)

Ufo\JsonRpcBundle\Server\RpcExecutor::execute(IRpcRequestCarrier $carrier): RpcResponse — точка входу для розширень, що приймають запити не через маршрут бандла (REST, MCP):

  • встановлює carrier, генерує rpc.request і далі як в одиночному запиті;
  • будь-який виняток проходить через rpc.error і повертається як відповідь з помилкою;
  • rpc.post_response — на kernel.terminate основного HTTP-запиту;
  • токен не перевіряє — це робить адаптер.

Де що зберігається під час запиту

Сервіс Що містить
RequestCarrier Поточний carrier (RpcFromHttp, RpcFromCli, carrier адаптера) і об'єкти запиту
ApiGroupContext Група API поточного запиту
IRpcSecurity Токен-холдер поточного запиту
RpcEventFactory Усі події, згенеровані для запиту (getEvent(), getEvents())
PostResponseQueue Запити, для яких буде згенеровано rpc.post_response

У довгоживучих воркерах ці сервіси скидаються між запитами (ResetInterface).