Request lifecycle

Request lifecycle

How JsonRpcBundle processes a request from the moment it is received until the response — for each entry point. Useful for writing event listeners and extensions.

HTTP POST

The bundle's routes are declared as GET (for documentation); POST requests to the same address are intercepted on kernel.request before routing and the controller.

  1. kernel.request, priority 240 — SymfonyFlowListener::initHttpRequest
    • determines the API group from the route and writes it to ApiGroupContext;
    • OPTIONS (CORS preflight) gets an empty response;
    • for POST, creates the RpcFromHttp carrier (JSON parsing; a body starting with [ is a batch), the HttpTokenHolder token holder, and checks the token (if protected_api: true) — with the group's validator or the base one.
  2. kernel.request, priority 230 — RpcRequestHandler::handle()
    • batch — checks batch.enabled and for an empty batch, selects a strategy (IBatchStrategy) and executes the items;
    • single request — checks jsonrpc: "2.0", fires the rpc.request event, then one of the branches:
      • request with $rpc.callback — an immediate placeholder response and rpc.pre_response, execution is deferred;
      • notification — a placeholder and rpc.pre_response, execution is deferred;
      • regular request — RpcServer::handle().
  3. RpcServer::handle()
    • looks up the method in the service map (taking the group and version into account); an unknown method is error -32601;
    • cache: for methods with #[RPC\Cache] (and without $rpc.ignoreCache) a ready response is returned immediately, without execution events;
    • the rpc.pre_execute event, during which, in order: argument building (1002), validation (1001), conversion (1000), #[RPC\Lock] (-100), method call (-1000);
    • success — rpc.pre_response, then rpc.post_execute; error — rpc.error, then rpc.pre_response;
    • storing the response in the cache (for #[RPC\Cache]).
  4. Response — JSON; 204 with no body if there is nothing to respond with (notification, a batch of notifications only). The Symfony router and controller do not run for POST.
  5. kernel.terminate — after fastcgi_finish_request() (PHP-FPM), rpc.post_response is fired for each successfully executed request: invalidation of #[RPC\CacheRelation] and execution of notifications / callback. Then ApiGroupContext is reset.

Errors that occur before the method is executed (invalid JSON, token, unknown method, etc.) are turned into a response by SymfonyErrorListener on kernel.exception. HTTP status: 401 — no token passed, 403 — invalid token, 500 — invalid JSON, otherwise 200.

HTTP GET (documentation)

  1. Priority 240 — group detection; if protected_doc: true — token check.
  2. Priority 8 — language selection from Accept-Language (RpcLocaleNegotiator).
  3. Priority 4 — outside debug mode, a response from the cache: 304 for If-None-Match or the stored document.
  4. ApiController::openrpc() / postman() builds the document for the version and group.
  5. kernel.response — Content-Language, Vary: Accept-Language, ETag, storing in the cache (only for 200).

No RPC events are fired on this path.

Console: ufo:rpc:process

The command has no logic of its own — the request is handled by a console.command listener (priority 999999):

  1. reading JSON (argument, stdin - or the UFO_RPC_REQUEST variable);
  2. group from --api-group, version from --ver;
  3. the RpcFromCli carrier, token from the --<token_name> option or the UFO_RPC_TOKEN variable;
  4. RpcRequestHandler::handle() — then as in HTTP;
  5. JSON output to stdout;
  6. console.terminate — rpc.post_response.

Errors are handled on console.error and go through rpc.error.

Symfony Messenger

RpcAsyncProcessor is the handler for RpcAsyncRequest messages:

  1. group from $message->apiGroup, token from $message->token, token check;
  2. direct call of RpcServer::handle().

So for requests from the queue there is no rpc.request and rpc.post_response; rpc.pre_execute, rpc.pre_response, rpc.post_execute and rpc.error are fired. Token and unknown method errors become Messenger message handling errors (retries, failure transport).

Batch

  • Process mode (batch.process: true) — each ready item is run in a child process ufo:rpc:process - (request via stdin, token via an environment variable, group and version as options), at most batch.process_count at a time. Each child process is a full console cycle with its own events. The parent process fires rpc.async_output on the output of each; when a @FROM dependency is resolved, it starts the next items.
  • Inline mode (batch.process: false) — items are executed one by one in the current process via RpcExecutor, each with its own event pool; rpc.post_response — right after the item.
  • Items whose dependency failed end with an error in the parent process.

Notifications and $rpc.callback

  1. On receipt — rpc.request and rpc.pre_response with a placeholder; the client gets 204 (notification) or {"async": true, "callback": …}.
  2. On rpc.post_response (priority 10), AsyncServerEventListener executes the method — the full cycle rpc.pre_execute → rpc.pre_response → rpc.post_execute / rpc.error.
  3. For a callback, the result is sent as a POST request to the specified URL.

With notification.process: true, a single HTTP request is handed off to a detached ufo:rpc:process @env process: the worker waits up to 3 seconds for confirmation, otherwise it executes the request itself. The number of detached processes is limited by notification.process_count.

RpcExecutor (adapters)

Ufo\JsonRpcBundle\Server\RpcExecutor::execute(IRpcRequestCarrier $carrier): RpcResponse is the entry point for extensions that receive requests not through the bundle's route (REST, MCP):

  • sets the carrier, fires rpc.request, then as in a single request;
  • any exception goes through rpc.error and is returned as an error response;
  • rpc.post_response — on kernel.terminate of the main HTTP request;
  • does not check the token — the adapter does that.

What is stored where during a request

Service What it contains
RequestCarrier The current carrier (RpcFromHttp, RpcFromCli, an adapter's carrier) and request objects
ApiGroupContext The API group of the current request
IRpcSecurity The token holder of the current request
RpcEventFactory All events fired for the request (getEvent(), getEvents())
PostResponseQueue Requests for which rpc.post_response will be fired

In long-running workers these services are reset between requests (ResetInterface).