|
| 1 | +# libhttpserver v2.0 — architecture diagrams |
| 2 | + |
| 3 | +Two visual references for the v2.0 (`feature/v2.0`) codebase. Each exists in two forms: |
| 4 | + |
| 5 | +| Diagram | GitHub-native (below) | Rich standalone page | |
| 6 | +|---|---|---| |
| 7 | +| **Class, relationship & filesystem map** | [Mermaid ↓](#1-class-relationship--filesystem-map) | [`class-map.html`](class-map.html) | |
| 8 | +| **Request lifecycle & routing flow** | [Mermaid ↓](#2-request-lifecycle--routing-flow) | [`request-flow.html`](request-flow.html) | |
| 9 | + |
| 10 | +The `.html` files are self-contained (no external assets, light/dark aware) — open them directly in a browser for the full, colour-coded detail: every class card, filesystem location, ownership edge, all 28 flow steps, the four-tier route cascade, and the hook rail. The Mermaid versions below render inline on GitHub for a quick orientation. |
| 11 | + |
| 12 | +> **Colour language** (shared by both diagrams): composition-root = blue · state collaborator = amber · behavior service = teal · MHD C-ABI adapter = purple · domain / value type = slate. |
| 13 | +
|
| 14 | +--- |
| 15 | + |
| 16 | +## 1. Class, relationship & filesystem map |
| 17 | + |
| 18 | +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). |
| 19 | + |
| 20 | +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. |
| 21 | + |
| 22 | +```mermaid |
| 23 | +classDiagram |
| 24 | + direction LR |
| 25 | + class create_webserver |
| 26 | + class webserver |
| 27 | + class webserver_impl { |
| 28 | + <<adapter>> |
| 29 | + } |
| 30 | + create_webserver --> webserver : builds |
| 31 | + webserver *-- webserver_impl : owns |
| 32 | +
|
| 33 | + class daemon_lifecycle { |
| 34 | + <<state>> |
| 35 | + } |
| 36 | + class route_table { |
| 37 | + <<state>> |
| 38 | + } |
| 39 | + class hook_bus { |
| 40 | + <<state>> |
| 41 | + } |
| 42 | + class ip_access_control { |
| 43 | + <<state>> |
| 44 | + } |
| 45 | + class ws_registry { |
| 46 | + <<state>> |
| 47 | + } |
| 48 | + webserver_impl *-- daemon_lifecycle |
| 49 | + webserver_impl *-- route_table |
| 50 | + webserver_impl *-- hook_bus |
| 51 | + webserver_impl *-- ip_access_control |
| 52 | + webserver_impl *-- ws_registry |
| 53 | +
|
| 54 | + class error_pages { |
| 55 | + <<behavior>> |
| 56 | + } |
| 57 | + class hook_dispatcher { |
| 58 | + <<behavior>> |
| 59 | + } |
| 60 | + class response_materializer { |
| 61 | + <<behavior>> |
| 62 | + } |
| 63 | + class upload_pipeline { |
| 64 | + <<behavior>> |
| 65 | + } |
| 66 | + class websocket_upgrader { |
| 67 | + <<behavior>> |
| 68 | + } |
| 69 | + class request_dispatcher { |
| 70 | + <<behavior>> |
| 71 | + } |
| 72 | + class request_pipeline { |
| 73 | + <<behavior>> |
| 74 | + } |
| 75 | + webserver_impl *-- error_pages |
| 76 | + webserver_impl *-- hook_dispatcher |
| 77 | + webserver_impl *-- response_materializer |
| 78 | + webserver_impl *-- upload_pipeline |
| 79 | + webserver_impl *-- websocket_upgrader |
| 80 | + webserver_impl *-- request_dispatcher |
| 81 | + webserver_impl *-- request_pipeline |
| 82 | +
|
| 83 | + hook_dispatcher ..> hook_bus |
| 84 | + response_materializer ..> error_pages |
| 85 | + response_materializer ..> hook_dispatcher |
| 86 | + websocket_upgrader ..> ws_registry |
| 87 | + request_dispatcher ..> route_table |
| 88 | + request_dispatcher ..> hook_dispatcher |
| 89 | + request_dispatcher ..> error_pages |
| 90 | + request_dispatcher ..> response_materializer |
| 91 | + request_dispatcher ..> websocket_upgrader |
| 92 | + request_pipeline ..> hook_dispatcher |
| 93 | + request_pipeline ..> request_dispatcher |
| 94 | +``` |
| 95 | + |
| 96 | +`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). |
| 97 | + |
| 98 | +**Per-request / per-connection state** (threaded through the services): |
| 99 | + |
| 100 | +```mermaid |
| 101 | +classDiagram |
| 102 | + direction LR |
| 103 | + class connection_context |
| 104 | + class connection_state |
| 105 | + class http_request |
| 106 | + class http_response |
| 107 | + class http_resource |
| 108 | + class response_body { |
| 109 | + <<abstract>> |
| 110 | + } |
| 111 | +
|
| 112 | + connection_context *-- http_request : owns |
| 113 | + connection_context *-- http_response : owns optional |
| 114 | + connection_context ..> http_resource : weak_ptr |
| 115 | + http_response *-- response_body : owns 64B SBO |
| 116 | + response_body <|-- empty_response_body |
| 117 | + response_body <|-- string_response_body |
| 118 | + response_body <|-- file_response_body |
| 119 | + response_body <|-- iovec_response_body |
| 120 | + response_body <|-- pipe_response_body |
| 121 | + response_body <|-- deferred_response_body |
| 122 | + response_body <|-- digest_challenge_response_body |
| 123 | +``` |
| 124 | + |
| 125 | +**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). |
| 126 | + |
| 127 | +--- |
| 128 | + |
| 129 | +## 2. Request lifecycle & routing flow |
| 130 | + |
| 131 | +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`. |
| 132 | + |
| 133 | +```mermaid |
| 134 | +sequenceDiagram |
| 135 | + autonumber |
| 136 | + participant MHD as libmicrohttpd |
| 137 | + participant AD as webserver_impl trampolines |
| 138 | + participant PL as request_pipeline |
| 139 | + participant DP as request_dispatcher |
| 140 | + participant RT as route_table |
| 141 | + participant RM as response_materializer |
| 142 | +
|
| 143 | + MHD->>AD: policy_callback · IP ACL |
| 144 | + Note over AD: ip_access_control.classify → accept? · ◈ accept_decision |
| 145 | + MHD->>AD: connection_notify STARTED |
| 146 | + Note over AD: new connection_state (arena) · ◈ connection_opened |
| 147 | + MHD->>AD: uri_log |
| 148 | + Note over AD: new connection_context → *con_cls |
| 149 | +
|
| 150 | + MHD->>AD: answer_to_connection · first · request==nullptr |
| 151 | + Note over AD: canonicalize URL · resolve_method_callback |
| 152 | + AD->>PL: requests_answer_first_step |
| 153 | + Note over PL: build http_request · ◈ request_received short-circuit |
| 154 | + alt has body · POST or PUT |
| 155 | + loop until zero-size chunk |
| 156 | + MHD->>AD: answer_to_connection · body chunk |
| 157 | + AD->>PL: requests_answer_second_step |
| 158 | + Note over PL: ◈ body_chunk · grow_content / post_iterator → upload_pipeline |
| 159 | + end |
| 160 | + end |
| 161 | +
|
| 162 | + MHD->>AD: answer_to_connection · terminal · size==0 |
| 163 | + AD->>PL: requests_answer_second_step → complete_request |
| 164 | + PL->>DP: finalize_answer |
| 165 | + Note over DP: try_ws_upgrade → 101 branch bypasses the rest |
| 166 | + DP->>RT: lookup_v2 · exact→cache→radix→regex |
| 167 | + RT-->>DP: entry + captured_params · else 404 |
| 168 | + Note over DP: ◈ route_resolved · ◈ before_handler auth · is_allowed → 405 |
| 169 | + Note over DP: pointer-to-member → render_get · catch → ◈ handler_exception → 500 |
| 170 | + Note over DP: ◈ after_handler may replace response |
| 171 | + DP->>RM: materialize_and_queue_response |
| 172 | + Note over RM: response_body.materialize → decorate → MHD_queue_response ◀ SEND · ◈ response_sent |
| 173 | + MHD->>AD: request_completed |
| 174 | + Note over AD: ◈ request_completed · delete connection_context · reset_arena |
| 175 | + MHD->>AD: connection_notify CLOSED |
| 176 | + Note over AD: ◈ connection_closed · delete connection_state |
| 177 | +``` |
| 178 | + |
| 179 | +**Four-tier route resolution** (`route_table::lookup_v2`, cheapest first, first hit wins): |
| 180 | + |
| 181 | +```mermaid |
| 182 | +flowchart LR |
| 183 | + C[canonicalize path] --> T1{Tier 1<br/>exact_routes_ map} |
| 184 | + T1 -- hit --> H[entry + captured_params] |
| 185 | + T1 -- miss --> T2{Tier 2<br/>LRU cache} |
| 186 | + T2 -- hit --> H |
| 187 | + T2 -- miss --> T3{Tier 3<br/>segment_trie radix} |
| 188 | + T3 -- hit --> INS[cache install] --> H |
| 189 | + T3 -- miss --> T4{Tier 4<br/>regex scan} |
| 190 | + T4 -- hit --> INS |
| 191 | + T4 -- miss --> NF[404 not found] |
| 192 | +``` |
| 193 | + |
| 194 | +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. |
| 195 | + |
| 196 | +### The 11 hook phases (firing order) |
| 197 | + |
| 198 | +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. |
| 199 | + |
| 200 | +| # | Phase | Fires in | Scope | Kind | |
| 201 | +|---|---|---|---|---| |
| 202 | +| 1 | `connection_opened` | `connection_notify` STARTED | server | observe | |
| 203 | +| 2 | `accept_decision` | `policy_callback` | server | observe | |
| 204 | +| 3 | `request_received` | `requests_answer_first_step` | server | short-circuit | |
| 205 | +| 4 | `body_chunk` | `requests_answer_second_step` (per chunk) | server | short-circuit | |
| 206 | +| 5 | `route_resolved` | `finalize_answer` (after lookup) | server | observe | |
| 207 | +| 6 | `before_handler` | `finalize_answer` (pre-dispatch · **auth**) | server + route | short-circuit | |
| 208 | +| 7 | `handler_exception` | `dispatch_resource_handler` catch | server + route | short-circuit | |
| 209 | +| 8 | `after_handler` | `finalize_answer` (post-handler) | server + route | replace resp | |
| 210 | +| 9 | `response_sent` | `materialize_and_queue` (after queue) | server + route · log_access alias | observe | |
| 211 | +| 10 | `request_completed` | `request_completed` callback | server + route | observe | |
| 212 | +| 11 | `connection_closed` | `connection_notify` CLOSED | server | observe | |
| 213 | + |
| 214 | +--- |
| 215 | + |
| 216 | +## Keeping these current |
| 217 | + |
| 218 | +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. |
0 commit comments