Skip to content

Commit 8a3fd54

Browse files
etrclaude
andcommitted
docs(architecture): give the two diagrams their own Markdown pages
Make the docs set uniform: every topic is now a Markdown page, so the class map and request flow read like the four deep-dives instead of living inside the index. - Add class-map.md and request-flow.md — the Mermaid quick-views and supporting tables, each linking to its rich .html companion for the full colour-coded detail. - Slim README.md to a pure index (Diagrams / Deep dives), no inline diagrams. - Repoint the deep-dives' cross-links to the new .md primaries. No content lost; the two .html pages are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015tAodxYJMEY4VxCX4dk62e
1 parent fb875dc commit 8a3fd54

7 files changed

Lines changed: 235 additions & 217 deletions

File tree

docs/architecture/README.md

Lines changed: 10 additions & 212 deletions
Original file line numberDiff line numberDiff line change
@@ -1,227 +1,25 @@
1-
# libhttpserver v2.0 — architecture diagrams
1+
# libhttpserver v2.0 — architecture docs
22

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.
44

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
66

7-
| Diagram | GitHub-native | Rich page |
7+
| Page | Rich companion | What it shows |
88
|---|---|---|
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 |
1111

12-
**Four deep dives** — reference-heavy topics, as Markdown (Mermaid + tables, render inline on GitHub).
12+
### Deep dives
1313

14-
| Doc | For | Covers |
14+
| Page | For | Covers |
1515
|---|---|---|
1616
| [**threading.md**](threading.md) | contributors | DR-008 concurrency contract, full mutex inventory, lock ordering, Helgrind lane |
1717
| [**errors.md**](errors.md) | contributors | DR-009 propagation, every 4xx/5xx origin, the handler-exception path, config knobs |
1818
| [**hooks.md**](hooks.md) | API users | the 11 phases, context fields, short-circuit vs observe, `hook_handle`, recipes |
1919
| [**features.md**](features.md) | packagers / API users | `HAVE_*` detection, gated symbols, `features()`, `feature_unavailable`, build matrix |
2020

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.
22422
22523
## Keeping these current
22624

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).

docs/architecture/class-map.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Class, relationship & filesystem map
2+
3+
> How the classes fit together and where they live on disk, for libhttpserver v2.0.
4+
> Quick-view below; the full colour-coded page with every class card and file location is **[`class-map.html`](class-map.html)** (open in a browser).
5+
6+
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).
7+
8+
> **Colour language** (used by the HTML pages): composition-root = blue · state collaborator = amber · behavior service = teal · MHD C-ABI adapter = purple · domain / value type = slate.
9+
10+
## Composition backbone
11+
12+
Stereotypes 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.
13+
14+
```mermaid
15+
classDiagram
16+
direction LR
17+
class create_webserver
18+
class webserver
19+
class webserver_impl {
20+
<<adapter>>
21+
}
22+
create_webserver --> webserver : builds
23+
webserver *-- webserver_impl : owns
24+
25+
class daemon_lifecycle {
26+
<<state>>
27+
}
28+
class route_table {
29+
<<state>>
30+
}
31+
class hook_bus {
32+
<<state>>
33+
}
34+
class ip_access_control {
35+
<<state>>
36+
}
37+
class ws_registry {
38+
<<state>>
39+
}
40+
webserver_impl *-- daemon_lifecycle
41+
webserver_impl *-- route_table
42+
webserver_impl *-- hook_bus
43+
webserver_impl *-- ip_access_control
44+
webserver_impl *-- ws_registry
45+
46+
class error_pages {
47+
<<behavior>>
48+
}
49+
class hook_dispatcher {
50+
<<behavior>>
51+
}
52+
class response_materializer {
53+
<<behavior>>
54+
}
55+
class upload_pipeline {
56+
<<behavior>>
57+
}
58+
class websocket_upgrader {
59+
<<behavior>>
60+
}
61+
class request_dispatcher {
62+
<<behavior>>
63+
}
64+
class request_pipeline {
65+
<<behavior>>
66+
}
67+
webserver_impl *-- error_pages
68+
webserver_impl *-- hook_dispatcher
69+
webserver_impl *-- response_materializer
70+
webserver_impl *-- upload_pipeline
71+
webserver_impl *-- websocket_upgrader
72+
webserver_impl *-- request_dispatcher
73+
webserver_impl *-- request_pipeline
74+
75+
hook_dispatcher ..> hook_bus
76+
response_materializer ..> error_pages
77+
response_materializer ..> hook_dispatcher
78+
websocket_upgrader ..> ws_registry
79+
request_dispatcher ..> route_table
80+
request_dispatcher ..> hook_dispatcher
81+
request_dispatcher ..> error_pages
82+
request_dispatcher ..> response_materializer
83+
request_dispatcher ..> websocket_upgrader
84+
request_pipeline ..> hook_dispatcher
85+
request_pipeline ..> request_dispatcher
86+
```
87+
88+
`ws_registry` + `websocket_upgrader` are wired only on `HAVE_WEBSOCKET` builds. `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. The mutexes each state collaborator owns are catalogued in [threading.md](threading.md).
89+
90+
## Per-request / per-connection state
91+
92+
The objects threaded through the services during a request:
93+
94+
```mermaid
95+
classDiagram
96+
direction LR
97+
class connection_context
98+
class connection_state
99+
class http_request
100+
class http_response
101+
class http_resource
102+
class response_body {
103+
<<abstract>>
104+
}
105+
106+
connection_context *-- http_request : owns
107+
connection_context *-- http_response : owns optional
108+
connection_context ..> http_resource : weak_ptr
109+
http_response *-- response_body : owns 64B SBO
110+
response_body <|-- empty_response_body
111+
response_body <|-- string_response_body
112+
response_body <|-- file_response_body
113+
response_body <|-- iovec_response_body
114+
response_body <|-- pipe_response_body
115+
response_body <|-- deferred_response_body
116+
response_body <|-- digest_challenge_response_body
117+
```
118+
119+
`connection_context` is MHD's `*con_cls` (the dispatch blackboard, allocated in `uri_log`); `connection_state` is MHD's `socket_context` (the per-keep-alive-connection PMR arena). `http_response` stores one `response_body` subclass inline in a 64-byte SBO buffer.
120+
121+
## Filesystem convention
122+
123+
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). Both `webserver.cpp` and `webserver_callbacks.cpp` sit under the 600-SLOC façade/adapter carve-out of the `check-file-size` gate (others: 500). The full per-class header/cpp locations are in [`class-map.html`](class-map.html).
124+
125+
---
126+
*See also: [request-flow](request-flow.md) (how these classes collaborate per request) · [threading.md](threading.md) (the mutexes they own).*

docs/architecture/errors.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,4 @@ Notable details:
8585
- Thrown from **inside a handler** (e.g. `unauthorized(digest_challenge)` on a non-digest build, or a `websocket_session` send) → it is a normal `std::exception` on the dispatch path → generic **500** to the client, unless an `internal_error_handler` special-cases it.
8686

8787
---
88-
*See also: [request-flow](request-flow.html) (where 404/405/500 sit in the dispatch sequence) · [hooks cookbook](hooks.md) (the `handler_exception` phase).*
88+
*See also: [request-flow](request-flow.md) (where 404/405/500 sit in the dispatch sequence) · [hooks cookbook](hooks.md) (the `handler_exception` phase).*

docs/architecture/features.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,4 @@ Setters never throw — they only mutate config; validation is deferred. So `use
8080
- **CMake support is consumer-side only**: `cmakemodule/FindLibHttpServer.cmake` is a plain find-module (locates headers + library); it does no capability detection and does not build the library. There is no top-level `CMakeLists.txt`.
8181

8282
---
83-
*See also: [errors](errors.md) (`feature_unavailable`'s reach — caller vs client) · [class map](class-map.html) (the gated collaborators, marked `HAVE_WEBSOCKET`).*
83+
*See also: [errors](errors.md) (`feature_unavailable`'s reach — caller vs client) · [class map](class-map.md) (the gated collaborators, marked `HAVE_WEBSOCKET`).*

0 commit comments

Comments
 (0)