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.
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
RpcFromHttpcarrier (JSON parsing; a body starting with[is a batch), theHttpTokenHoldertoken holder, and checks the token (ifprotected_api: true) — with the group's validator or the base one.
- determines the API group from the route and writes it to
kernel.request, priority 230 —RpcRequestHandler::handle()- batch — checks
batch.enabledand for an empty batch, selects a strategy (IBatchStrategy) and executes the items; - single request — checks
jsonrpc: "2.0", fires therpc.requestevent, then one of the branches:- request with
$rpc.callback— an immediate placeholder response andrpc.pre_response, execution is deferred; - notification — a placeholder and
rpc.pre_response, execution is deferred; - regular request —
RpcServer::handle().
- request with
- batch — checks
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_executeevent, during which, in order: argument building (1002), validation (1001), conversion (1000),#[RPC\Lock](-100), method call (-1000); - success —
rpc.pre_response, thenrpc.post_execute; error —rpc.error, thenrpc.pre_response; - storing the response in the cache (for
#[RPC\Cache]).
- looks up the method in the service map (taking the group and version into account); an unknown method is error
- Response — JSON;
204with 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. kernel.terminate— afterfastcgi_finish_request()(PHP-FPM),rpc.post_responseis fired for each successfully executed request: invalidation of#[RPC\CacheRelation]and execution of notifications / callback. ThenApiGroupContextis 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)
- Priority 240 — group detection; if
protected_doc: true— token check. - Priority 8 — language selection from
Accept-Language(RpcLocaleNegotiator). - Priority 4 — outside debug mode, a response from the cache:
304forIf-None-Matchor the stored document. ApiController::openrpc()/postman()builds the document for the version and group.kernel.response—Content-Language,Vary: Accept-Language,ETag, storing in the cache (only for200).
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):
- reading JSON (argument, stdin
-or theUFO_RPC_REQUESTvariable); - group from
--api-group, version from--ver; - the
RpcFromClicarrier, token from the--<token_name>option or theUFO_RPC_TOKENvariable; RpcRequestHandler::handle()— then as in HTTP;- JSON output to stdout;
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:
- group from
$message->apiGroup, token from$message->token, token check; - 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 processufo:rpc:process -(request via stdin, token via an environment variable, group and version as options), at mostbatch.process_countat a time. Each child process is a full console cycle with its own events. The parent process firesrpc.async_outputon the output of each; when a@FROMdependency is resolved, it starts the next items. - Inline mode (
batch.process: false) — items are executed one by one in the current process viaRpcExecutor, 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
- On receipt —
rpc.requestandrpc.pre_responsewith a placeholder; the client gets204(notification) or{"async": true, "callback": …}. - On
rpc.post_response(priority 10),AsyncServerEventListenerexecutes the method — the full cyclerpc.pre_execute→rpc.pre_response→rpc.post_execute/rpc.error. - 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.errorand is returned as an error response; rpc.post_response— onkernel.terminateof 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).