Request lifecycle

← Request lifecycle

Ashterix

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

en/docs/api/rest-bundle/developers/lifecycle.md new file +51 āˆ’0

1 + # Request lifecycle
2 +
3 + How REST Bundle processes a request: from the HTTP request to the RPC call and back. The description matches version 1.3; how the core processes the same call is described on the [JsonRpcBundle lifecycle](/docs/api/json-rpc-bundle/developers/lifecycle) page.
4 +
5 + ## REST call
6 +
7 + 1. **`kernel.request`, priority 32: `RouterListener`** finds the generated REST route. Its defaults contain `_rest_route` (the REST route name without the version), `_ver` for versions other than `v1`, `api_group` and `_rest_name_prefix`.
8 + 2. **`kernel.request`, priority 30: `RestControllerListener`** writes the group to `ApiGroupContext` and, for a REST call, points `_controller` to `RestEntryPointController::rest` with the `rpcRoute` and `ver` attributes.
9 + 3. **`RestRequestParser::parse()`**
10 + * finds the REST route and the RPC `Service` for the group and version (`404` if missing);
11 + * checks the HTTP method: `HEAD` counts as `GET`, a method that is not allowed gives `405`;
12 + * resets the RPC event pool (`RpcEventFactory::reset()`), sets `HttpTokenHolder` and the `RpcFromRest` carrier, checks the token (`protected_api`).
13 + 4. **`RpcFromRest`** builds an `RpcRequest`: a generated `id`, the RPC method name, the merged [parameters](/docs/api/rest-bundle/requests) and `apiVersion`.
14 + 5. **`RpcExecutor::execute()`** executes the request the same way as JSON-RPC: `rpc.request` → `rpc.pre_execute` (building arguments, validation, conversion, `#[RPC\Lock]`, method call) → `rpc.pre_response` → `rpc.post_execute`. Any error goes through `rpc.error` and becomes an error response.
15 + 6. **The controller** returns `200` with the method result or converts the error into an [RFC 9457 problem](/docs/api/rest-bundle/error_handling) via `RestErrorNormalizer`.
16 + 7. **`kernel.terminate`**: `rpc.post_response` fires for a successful call (`#[RPC\CacheRelation]` invalidation, post-response listeners). Not for an error.
17 +
18 + A request without a matching REST route lands in the catch-all entry point `ufo_rest_api_entrypoint` (priority `-100`), and the parser returns `404`. A request with a wrong HTTP method lands in the `<name>_method_not_allowed` route (priority `-10`), and the parser returns `405`.
19 +
20 + ## Documentation
21 +
22 + 1. Priority 32: routing to a route with the `_rest_doc` default.
23 + 2. Priority 30: `RestControllerListener` writes the group to `ApiGroupContext`.
24 + 3. Priority 8: `RestDocLocaleListener` picks the language from `Accept-Language` (`RpcLocaleNegotiator::applyLocale()`).
25 + 4. `RestEntryPointController::openapiAction()` / `postmanAction()` checks the token (`protected_doc`) and builds the document for the version and group.
26 + 5. `kernel.response`: `Content-Language` and `Vary: Accept-Language`.
27 +
28 + No RPC events are fired on this path; the document is built on every request.
29 +
30 + ## Events
31 +
32 + A REST call fires the same events as JSON-RPC, so the application's listeners work for both:
33 +
34 + | Event | Constant | When |
35 + | --- | --- | --- |
36 + | `rpc.request` | `RpcEvent::REQUEST` | the request is accepted |
37 + | `rpc.pre_execute` | `RpcEvent::PRE_EXECUTE` | before the method |
38 + | `rpc.pre_response` | `RpcEvent::PRE_RESPONSE` | the response is formed |
39 + | `rpc.post_execute` | `RpcEvent::POST_EXECUTE` | after the method |
40 + | `rpc.error` | `RpcEvent::ERROR` | any error |
41 + | `rpc.post_response` | `RpcEvent::POST_RESPONSE` | on `kernel.terminate`, after a successful call |
42 +
43 + Each event is described in detail in [Lifecycle events](/docs/api/json-rpc-bundle/functionality/events).
44 +
45 + :::info
46 + Before 1.3 the REST controller executed the request through `RpcRequestHandler` without `RpcExecutor`, and `rpc.post_response` was not fired for REST.
47 + :::
48 +
49 + ## State between requests
50 +
51 + The API group is kept in `ApiGroupContext`, the carrier in `RequestCarrier`, the token holder in `IRpcSecurity`; these services belong to JsonRpcBundle and are reset between requests in long-running workers. `RestServiceMap` builds the REST routes for a group once and switches between already built maps when the group in `ApiGroupContext` changes.