|
1 | | -# libhttpserver v2.0 — architecture diagrams |
| 1 | +# libhttpserver v2.0 — architecture docs |
2 | 2 |
|
3 | | -Architecture references for the v2.0 (`feature/v2.0`) codebase. |
| 3 | +Architecture references for the v2.0 (`feature/v2.0`) codebase. Every topic is a Markdown page (Mermaid + tables, renders inline on GitHub); the two spatial diagrams additionally have a rich, self-contained HTML companion (no external assets, light/dark aware) for the full colour-coded detail. |
4 | 4 |
|
5 | | -**Two hero diagrams** — spatial maps, best viewed as the rich self-contained HTML (no external assets, light/dark aware); Mermaid quick-views render inline below. |
| 5 | +### Diagrams |
6 | 6 |
|
7 | | -| Diagram | GitHub-native | Rich page | |
| 7 | +| Page | Rich companion | What it shows | |
8 | 8 | |---|---|---| |
9 | | -| **Class, relationship & filesystem map** | [Mermaid ↓](#1-class-relationship--filesystem-map) | [`class-map.html`](class-map.html) | |
10 | | -| **Request lifecycle & routing flow** | [Mermaid ↓](#2-request-lifecycle--routing-flow) | [`request-flow.html`](request-flow.html) | |
| 9 | +| [**class-map.md**](class-map.md) | [`class-map.html`](class-map.html) | `webserver` → `webserver_impl` composition root (5 state collaborators + 7 behavior services + MHD adapter), per-request state, `response_body` hierarchy, filesystem layout | |
| 10 | +| [**request-flow.md**](request-flow.md) | [`request-flow.html`](request-flow.html) | the MHD callback spine, body-accumulation loop, WebSocket branch, four-tier route resolution, and where the 11 hook phases fire | |
11 | 11 |
|
12 | | -**Four deep dives** — reference-heavy topics, as Markdown (Mermaid + tables, render inline on GitHub). |
| 12 | +### Deep dives |
13 | 13 |
|
14 | | -| Doc | For | Covers | |
| 14 | +| Page | For | Covers | |
15 | 15 | |---|---|---| |
16 | 16 | | [**threading.md**](threading.md) | contributors | DR-008 concurrency contract, full mutex inventory, lock ordering, Helgrind lane | |
17 | 17 | | [**errors.md**](errors.md) | contributors | DR-009 propagation, every 4xx/5xx origin, the handler-exception path, config knobs | |
18 | 18 | | [**hooks.md**](hooks.md) | API users | the 11 phases, context fields, short-circuit vs observe, `hook_handle`, recipes | |
19 | 19 | | [**features.md**](features.md) | packagers / API users | `HAVE_*` detection, gated symbols, `features()`, `feature_unavailable`, build matrix | |
20 | 20 |
|
21 | | -> **Colour language** (shared by both diagrams): composition-root = blue · state collaborator = amber · behavior service = teal · MHD C-ABI adapter = purple · domain / value type = slate. |
22 | | -
|
23 | | ---- |
24 | | - |
25 | | -## 1. Class, relationship & filesystem map |
26 | | - |
27 | | -Post-**DR-014**, `webserver` is a thin façade over `webserver_impl`, which is a **pure composition root** holding **5 state collaborators** (own their mutexes + data) and **7 behavior services** (stateless; hold `const&` into state and each other), plus a **static MHD adapter facet** (the C-ABI trampolines). Ownership is strictly linear and top-down; services form an acyclic DAG with no back-pointer (the sole exception: `daemon_lifecycle` needs `webserver_impl*` to read broad config while building the MHD option array). |
28 | | - |
29 | | -Stereotypes below encode the role: `<<state>>` = state collaborator (owns mutex + data), `<<behavior>>` = behavior service (stateless), `<<adapter>>` = MHD C-ABI facet. Solid diamond (`*--`) = owns by value; dashed arrow (`..>`) = holds `const&` reference. |
30 | | - |
31 | | -```mermaid |
32 | | -classDiagram |
33 | | - direction LR |
34 | | - class create_webserver |
35 | | - class webserver |
36 | | - class webserver_impl { |
37 | | - <<adapter>> |
38 | | - } |
39 | | - create_webserver --> webserver : builds |
40 | | - webserver *-- webserver_impl : owns |
41 | | -
|
42 | | - class daemon_lifecycle { |
43 | | - <<state>> |
44 | | - } |
45 | | - class route_table { |
46 | | - <<state>> |
47 | | - } |
48 | | - class hook_bus { |
49 | | - <<state>> |
50 | | - } |
51 | | - class ip_access_control { |
52 | | - <<state>> |
53 | | - } |
54 | | - class ws_registry { |
55 | | - <<state>> |
56 | | - } |
57 | | - webserver_impl *-- daemon_lifecycle |
58 | | - webserver_impl *-- route_table |
59 | | - webserver_impl *-- hook_bus |
60 | | - webserver_impl *-- ip_access_control |
61 | | - webserver_impl *-- ws_registry |
62 | | -
|
63 | | - class error_pages { |
64 | | - <<behavior>> |
65 | | - } |
66 | | - class hook_dispatcher { |
67 | | - <<behavior>> |
68 | | - } |
69 | | - class response_materializer { |
70 | | - <<behavior>> |
71 | | - } |
72 | | - class upload_pipeline { |
73 | | - <<behavior>> |
74 | | - } |
75 | | - class websocket_upgrader { |
76 | | - <<behavior>> |
77 | | - } |
78 | | - class request_dispatcher { |
79 | | - <<behavior>> |
80 | | - } |
81 | | - class request_pipeline { |
82 | | - <<behavior>> |
83 | | - } |
84 | | - webserver_impl *-- error_pages |
85 | | - webserver_impl *-- hook_dispatcher |
86 | | - webserver_impl *-- response_materializer |
87 | | - webserver_impl *-- upload_pipeline |
88 | | - webserver_impl *-- websocket_upgrader |
89 | | - webserver_impl *-- request_dispatcher |
90 | | - webserver_impl *-- request_pipeline |
91 | | -
|
92 | | - hook_dispatcher ..> hook_bus |
93 | | - response_materializer ..> error_pages |
94 | | - response_materializer ..> hook_dispatcher |
95 | | - websocket_upgrader ..> ws_registry |
96 | | - request_dispatcher ..> route_table |
97 | | - request_dispatcher ..> hook_dispatcher |
98 | | - request_dispatcher ..> error_pages |
99 | | - request_dispatcher ..> response_materializer |
100 | | - request_dispatcher ..> websocket_upgrader |
101 | | - request_pipeline ..> hook_dispatcher |
102 | | - request_pipeline ..> request_dispatcher |
103 | | -``` |
104 | | - |
105 | | -`daemon_lifecycle` (HAVE_WEBSOCKET builds also wire `ws_registry` + `websocket_upgrader`) — `route_table` owns `route_entry` / `segment_trie` / `route_cache`; `hook_bus` holds the 11 server-wide phase vectors; `response_materializer` turns `http_response` into an `MHD_Response`; `request_pipeline` is the re-entrant body-accumulation state machine. Full descriptions and per-class file locations: [`class-map.html`](class-map.html). |
106 | | - |
107 | | -**Per-request / per-connection state** (threaded through the services): |
108 | | - |
109 | | -```mermaid |
110 | | -classDiagram |
111 | | - direction LR |
112 | | - class connection_context |
113 | | - class connection_state |
114 | | - class http_request |
115 | | - class http_response |
116 | | - class http_resource |
117 | | - class response_body { |
118 | | - <<abstract>> |
119 | | - } |
120 | | -
|
121 | | - connection_context *-- http_request : owns |
122 | | - connection_context *-- http_response : owns optional |
123 | | - connection_context ..> http_resource : weak_ptr |
124 | | - http_response *-- response_body : owns 64B SBO |
125 | | - response_body <|-- empty_response_body |
126 | | - response_body <|-- string_response_body |
127 | | - response_body <|-- file_response_body |
128 | | - response_body <|-- iovec_response_body |
129 | | - response_body <|-- pipe_response_body |
130 | | - response_body <|-- deferred_response_body |
131 | | - response_body <|-- digest_challenge_response_body |
132 | | -``` |
133 | | - |
134 | | -**Filesystem convention.** Public surface in `src/httpserver/` (installed); internal detail headers in `src/httpserver/detail/` (never installed); implementations in `src/` and `src/detail/`. `webserver` = one façade TU (`src/webserver.cpp`); `webserver_impl` = two TUs (`src/detail/webserver_impl.cpp` composition root + `src/detail/webserver_callbacks.cpp` MHD adapter). The full per-class header/cpp locations are in [`class-map.html`](class-map.html). |
135 | | - |
136 | | ---- |
137 | | - |
138 | | -## 2. Request lifecycle & routing flow |
139 | | - |
140 | | -One HTTP request is not one function call. libmicrohttpd drives the exchange through a fixed sequence of C-ABI callbacks (static `webserver_impl` trampolines in `webserver_callbacks.cpp`), each forwarding into a behavior service. `answer_to_connection` is called **1..N times** — once for a bodyless `GET`, many times while a `POST` body streams in — but resolves to exactly one `finalize_answer`. |
141 | | - |
142 | | -```mermaid |
143 | | -sequenceDiagram |
144 | | - autonumber |
145 | | - participant MHD as libmicrohttpd |
146 | | - participant AD as webserver_impl trampolines |
147 | | - participant PL as request_pipeline |
148 | | - participant DP as request_dispatcher |
149 | | - participant RT as route_table |
150 | | - participant RM as response_materializer |
151 | | -
|
152 | | - MHD->>AD: policy_callback · IP ACL |
153 | | - Note over AD: ip_access_control.classify → accept? · ◈ accept_decision |
154 | | - MHD->>AD: connection_notify STARTED |
155 | | - Note over AD: new connection_state (arena) · ◈ connection_opened |
156 | | - MHD->>AD: uri_log |
157 | | - Note over AD: new connection_context → *con_cls |
158 | | -
|
159 | | - MHD->>AD: answer_to_connection · first · request==nullptr |
160 | | - Note over AD: canonicalize URL · resolve_method_callback |
161 | | - AD->>PL: requests_answer_first_step |
162 | | - Note over PL: build http_request · ◈ request_received short-circuit |
163 | | - alt has body · POST or PUT |
164 | | - loop until zero-size chunk |
165 | | - MHD->>AD: answer_to_connection · body chunk |
166 | | - AD->>PL: requests_answer_second_step |
167 | | - Note over PL: ◈ body_chunk · grow_content / post_iterator → upload_pipeline |
168 | | - end |
169 | | - end |
170 | | -
|
171 | | - MHD->>AD: answer_to_connection · terminal · size==0 |
172 | | - AD->>PL: requests_answer_second_step → complete_request |
173 | | - PL->>DP: finalize_answer |
174 | | - Note over DP: try_ws_upgrade → 101 branch bypasses the rest |
175 | | - DP->>RT: lookup_v2 · exact→cache→radix→regex |
176 | | - RT-->>DP: entry + captured_params · else 404 |
177 | | - Note over DP: ◈ route_resolved · ◈ before_handler auth · is_allowed → 405 |
178 | | - Note over DP: pointer-to-member → render_get · catch → ◈ handler_exception → 500 |
179 | | - Note over DP: ◈ after_handler may replace response |
180 | | - DP->>RM: materialize_and_queue_response |
181 | | - Note over RM: response_body.materialize → decorate → MHD_queue_response ◀ SEND · ◈ response_sent |
182 | | - MHD->>AD: request_completed |
183 | | - Note over AD: ◈ request_completed · delete connection_context · reset_arena |
184 | | - MHD->>AD: connection_notify CLOSED |
185 | | - Note over AD: ◈ connection_closed · delete connection_state |
186 | | -``` |
187 | | - |
188 | | -**Four-tier route resolution** (`route_table::lookup_v2`, cheapest first, first hit wins): |
189 | | - |
190 | | -```mermaid |
191 | | -flowchart LR |
192 | | - C[canonicalize path] --> T1{Tier 1<br/>exact_routes_ map} |
193 | | - T1 -- hit --> H[entry + captured_params] |
194 | | - T1 -- miss --> T2{Tier 2<br/>LRU cache} |
195 | | - T2 -- hit --> H |
196 | | - T2 -- miss --> T3{Tier 3<br/>segment_trie radix} |
197 | | - T3 -- hit --> INS[cache install] --> H |
198 | | - T3 -- miss --> T4{Tier 4<br/>regex scan} |
199 | | - T4 -- hit --> INS |
200 | | - T4 -- miss --> NF[404 not found] |
201 | | -``` |
202 | | - |
203 | | -The entry is returned **regardless of method** so the 405 path still sees it. Method → handler is chosen separately: the verb became a pointer-to-member (`render_get`/`render_post`/…) in `resolve_method_callback`, and `dispatch_resource_handler` checks `http_resource::is_allowed(method_enum)` → mismatch yields **405** + the resource's `Allow:` header. |
204 | | - |
205 | | -### The 11 hook phases (firing order) |
206 | | - |
207 | | -Server-wide hooks live on `hook_bus`; the five post-route-resolution phases are also **per-resource** (`resource_hook_table`, via `http_resource::add_hook`). Every phase is guarded by a relaxed-atomic `has_hooks_for` check — zero cost when unused. |
208 | | - |
209 | | -| # | Phase | Fires in | Scope | Kind | |
210 | | -|---|---|---|---|---| |
211 | | -| 1 | `connection_opened` | `connection_notify` STARTED | server | observe | |
212 | | -| 2 | `accept_decision` | `policy_callback` | server | observe | |
213 | | -| 3 | `request_received` | `requests_answer_first_step` | server | short-circuit | |
214 | | -| 4 | `body_chunk` | `requests_answer_second_step` (per chunk) | server | short-circuit | |
215 | | -| 5 | `route_resolved` | `finalize_answer` (after lookup) | server | observe | |
216 | | -| 6 | `before_handler` | `finalize_answer` (pre-dispatch · **auth**) | server + route | short-circuit | |
217 | | -| 7 | `handler_exception` | `dispatch_resource_handler` catch | server + route | short-circuit | |
218 | | -| 8 | `after_handler` | `finalize_answer` (post-handler) | server + route | replace resp | |
219 | | -| 9 | `response_sent` | `materialize_and_queue` (after queue) | server + route · log_access alias | observe | |
220 | | -| 10 | `request_completed` | `request_completed` callback | server + route | observe | |
221 | | -| 11 | `connection_closed` | `connection_notify` CLOSED | server | observe | |
222 | | - |
223 | | ---- |
| 21 | +> **Colour language** (shared by the two diagrams): composition-root = blue · state collaborator = amber · behavior service = teal · MHD C-ABI adapter = purple · domain / value type = slate. |
224 | 22 |
|
225 | 23 | ## Keeping these current |
226 | 24 |
|
227 | | -These diagrams describe `feature/v2.0` as of the DR-014 decomposition. When the composition root's collaborators, the callback spine, the route tiers, or the hook phases change, update **both** the Mermaid blocks above and the corresponding `.html` page (they are hand-authored, not generated). The rich pages are also published as live Claude artifacts — see the team's shared links. |
| 25 | +These describe `feature/v2.0` as of the DR-014 decomposition. They are **hand-authored, not generated** — when the composition root's collaborators, the callback spine, the route tiers, or the hook phases change, update the affected Markdown page and, for the two diagrams, its `.html` companion (and redeploy the live Claude artifact to keep the shared link in sync). |
0 commit comments