You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor: clarify response-body and connection-context naming
Rename internal names that read as under-specified for a reader
encountering them out of context, keeping the qualifier only where it
adds meaning (and off where it would stutter or break public API).
- detail::modded_request -> detail::connection_context, and its handle
variable mr -> conn throughout the dispatch pipeline. "modded"
(modified) said nothing; the struct is the per-connection processing
context that adapts MHD's *con_cls and accumulates state across
answer_to_connection calls.
- detail::body hierarchy -> *_response_body (response_body base;
string_/file_/iovec_/pipe_/deferred_/empty_/digest_challenge_
response_body). These flow through the pipeline as bare detail::body*,
decontextualized from any response.
- string_response_body::content_ -> data_ (get_content -> get_data):
the response payload no longer borrows the request-side "content"
vocabulary.
- digest_challenge::body field and the http_response::string /
unauthorized factory params -> response_body.
Deliberately unchanged: the public body_kind enum (always reached via
http_response::kind(), so response context is present) and
http_response's own body_/emplace_body/destroy_body members (the
enclosing type already says "response"; response_body_ would stutter).
Point-in-time records (ChangeLog, DR decision records, task specs,
unworked_review_issues) keep their original terminology.
Verified: out-of-source build green, make check 113/113, all
header/readme/release-notes gates pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tAodxYJMEY4VxCX4dk62e
| Public-header decoupling | No `<microhttpd.h>` / `<gnutls/gnutls.h>` / `<pthread.h>` / `<sys/socket.h>` / `<sys/uio.h>` in installed headers | PIMPL on `webserver` and `http_request`; forward-declared `detail::body` for `http_response`; high-level accessors (cert DN, fingerprint) replacing raw GnuTLS handles; library-defined `httpserver::iovec_entry` POD replacing `struct iovec` in the public `http_response::iovec(...)` factory |
14
+
| Public-header decoupling | No `<microhttpd.h>` / `<gnutls/gnutls.h>` / `<pthread.h>` / `<sys/socket.h>` / `<sys/uio.h>` in installed headers | PIMPL on `webserver` and `http_request`; forward-declared `detail::response_body` for `http_response`; high-level accessors (cert DN, fingerprint) replacing raw GnuTLS handles; library-defined `httpserver::iovec_entry` POD replacing `struct iovec` in the public `http_response::iovec(...)` factory |
15
15
| Build-flag stability | Public API surface invariant under `HAVE_BAUTH` / `HAVE_DAUTH` / `HAVE_GNUTLS` / `HAVE_WEBSOCKET`| Unconditional declarations; runtime sentinels or `feature_unavailable` throws when backends disabled; `webserver::features()` reports availability |
16
16
| Const correctness | Pure accessors `const`; lazy caches OK via `mutable`; daemon-driving methods exempt | Request-side caches in `mutable` storage (or unique_ptr); `is_running` / `get_fdset` / `get_timeout` documented as exempt operations |
17
17
| Hot-path performance | Per-request getters do not allocate or copy containers | Container-returning getters change to `const&` / `string_view`; per-request impl arena-allocated from a per-connection `std::pmr::monotonic_buffer_resource`; method-state held as a `uint32_t` bitmask, not a `std::map`|
|`http_response`| Response value: status, headers, footers, cookies, body | Non-PIMPL value type; polymorphic body in 64-byte SBO buffer with heap fallback |
42
42
|`http_resource`| Class-form handler (state shared across HTTP methods of one resource) | Public abstract base; allow-mask held as `method_set` (`uint32_t` bitmask) |
43
43
|`websocket_handler`| Per-endpoint WebSocket protocol handler | Public abstract base; registered via `unique_ptr` / `shared_ptr` overloads |
44
-
|`detail::body`| Polymorphic body kinds (string / file / iovec / pipe / deferred / empty) | Internal hierarchy in `src/httpserver/detail/body.hpp`|
44
+
|`detail::response_body`| Polymorphic body kinds (string / file / iovec / pipe / deferred / empty) | Internal hierarchy in `src/httpserver/detail/body.hpp`|
Copy file name to clipboardExpand all lines: specs/architecture/04-components/body-hierarchy.md
+10-10Lines changed: 10 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,4 +1,4 @@
1
-
### 4.8 `detail::body` hierarchy
1
+
### 4.8 `detail::response_body` hierarchy
2
2
3
3
**Responsibility:** Polymorphic body representation backing `http_response`'s SBO buffer. Each subclass carries the data needed for one body kind and knows how to stream itself into an MHD response.
4
4
@@ -21,24 +21,24 @@ protected:
21
21
body& operator=(body&&) = delete;
22
22
};
23
23
24
-
class string_body : public body { /* std::string content_; */ };
25
-
class file_body : public body { /* std::string path_; std::size_t size_; int fd_; bool materialized_; */ };
26
-
class iovec_body : public body { /* std::vector<iovec_entry> entries_; std::size_t total_size_; */ };
27
-
class pipe_body : public body { /* int fd_; bool materialized_; */ };
28
-
class deferred_body: public body { /* std::function<ssize_t(uint64_t, char*, std::size_t)> producer_; */ };
29
-
class empty_body : public body { /* int flags_; */ };
24
+
class string_response_body : public body { /* std::string content_; */ };
25
+
class file_response_body : public body { /* std::string path_; std::size_t size_; int fd_; bool materialized_; */ };
26
+
class iovec_response_body : public body { /* std::vector<iovec_entry> entries_; std::size_t total_size_; */ };
27
+
class pipe_response_body : public body { /* int fd_; bool materialized_; */ };
28
+
class deferred_response_body: public body { /* std::function<ssize_t(uint64_t, char*, std::size_t)> producer_; */ };
29
+
class empty_response_body : public body { /* int flags_; */ };
30
30
}
31
31
```
32
32
33
33
**SBO storage:** factories use placement-new into the response's `body_storage_` buffer when the subclass fits (always true for v2.0's set). New body kinds added in v2.x check at compile time (`static_assert`) whether they fit; if they don't, the factory falls back to `new`-allocating and storing the heap pointer.
34
34
35
35
**Materialization timing:** `materialize()` is called from `webserver`'s dispatch, not from the handler. The body holds whatever data it needs (strings, paths, callables) until that point.
36
36
37
-
**file_body open contract (implementation-close-out note):** Unlike the general guidance above, `file_body` opens the file descriptor and calls `fstat` at **construction** rather than in `materialize()`. This deliberate eager-open design achieves two goals: (1) `size()` is accurate immediately without calling `materialize()` first, and (2) `materialize()` can use `st_size` from the already-obtained `fstat` result, avoiding a TOCTOU race and an extra `lseek` syscall. The lazy-open guidance applies to the other body kinds (pipe, deferred); it does not apply to `file_body`.
37
+
**file_response_body open contract (implementation-close-out note):** Unlike the general guidance above, `file_response_body` opens the file descriptor and calls `fstat` at **construction** rather than in `materialize()`. This deliberate eager-open design achieves two goals: (1) `size()` is accurate immediately without calling `materialize()` first, and (2) `materialize()` can use `st_size` from the already-obtained `fstat` result, avoiding a TOCTOU race and an extra `lseek` syscall. The lazy-open guidance applies to the other body kinds (pipe, deferred); it does not apply to `file_response_body`.
38
38
39
-
**pipe_body ownership contract:** `pipe_body` takes ownership of a read-side file descriptor at construction. If `materialize()` succeeds, libmicrohttpd owns the fd (it is closed when `MHD_destroy_response` is called). If `materialize()` is never called, `~pipe_body()` closes the fd to prevent a leak. The `materialized_` field suppresses the destructor's close once MHD has taken ownership.
39
+
**pipe_response_body ownership contract:** `pipe_response_body` takes ownership of a read-side file descriptor at construction. If `materialize()` succeeds, libmicrohttpd owns the fd (it is closed when `MHD_destroy_response` is called). If `materialize()` is never called, `~pipe_response_body()` closes the fd to prevent a leak. The `materialized_` field suppresses the destructor's close once MHD has taken ownership.
40
40
41
-
**iovec_body allocation note:** `iovec_body` intentionally incurs one heap allocation for its `std::vector<iovec_entry>` backing store. The SBO slot holds only the vector control block (~24 bytes); the iovec entry array lives on the heap. This is the only SBO-resident body kind that performs a secondary heap allocation, and it is accepted as an inherent cost of `std::vector` (not a DR-005 violation — DR-005 governs the body-pointer allocation, not internal allocations within a fitting body).
41
+
**iovec_response_body allocation note:** `iovec_response_body` intentionally incurs one heap allocation for its `std::vector<iovec_entry>` backing store. The SBO slot holds only the vector control block (~24 bytes); the iovec entry array lives on the heap. This is the only SBO-resident body kind that performs a secondary heap allocation, and it is accepted as an inherent cost of `std::vector` (not a DR-005 violation — DR-005 governs the body-pointer allocation, not internal allocations within a fitting body).
Copy file name to clipboardExpand all lines: specs/architecture/04-components/dispatch-pipeline.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
**Responsibility:** The request-processing behavior of the server — everything between a libmicrohttpd callback firing and a response being queued — factored out of the `webserver_impl` god-object (DR-014) into seven per-server **behavior services**. Each is an internal `httpserver::detail` type gated on `HTTPSERVER_COMPILATION`; none appears on the public surface or ABI. (The connection-lifecycle callbacks — `connection_notify` / `policy_callback` / `request_completed` — were *not* extracted into an eighth service; they stay in the MHD-adapter layer, see below and DR-014.)
4
4
5
-
**What a behavior service is.** Distinct from a *state collaborator* (route_table/hook_bus/… — owns a mutex + data): a behavior service owns *logic*, not state. It is constructed once per `webserver_impl`, holds only `const&` references to its dependencies, owns no mutable state, takes no locks, and operates on the per-request `detail::modded_request` passed in by reference. Because it is stateless and lock-free it is inherently shareable across MHD worker threads; the decomposition adds no synchronization.
5
+
**What a behavior service is.** Distinct from a *state collaborator* (route_table/hook_bus/… — owns a mutex + data): a behavior service owns *logic*, not state. It is constructed once per `webserver_impl`, holds only `const&` references to its dependencies, owns no mutable state, takes no locks, and operates on the per-request `detail::connection_context` passed in by reference. Because it is stateless and lock-free it is inherently shareable across MHD worker threads; the decomposition adds no synchronization.
Acyclic, so the composition root wires it with plain member references — no mediator. Services store references at construction but never invoke a dependency during their own constructor, so binding a reference to a sibling member is well-defined irrespective of member-declaration order (`-Wreorder -Werror` guards accidental reorders).
35
35
36
-
**The MHD adapter layer.** libmicrohttpd calls in through fixed-signature C trampolines carrying a `void* cls` closure: `answer_to_connection`, `request_completed`, `connection_notify`, `policy_callback`, `post_iterator`, `uri_log`, `error_log`, `unescaper_func`, `upgrade_handler`, plus the GnuTLS `psk_cred_handler_func` / `sni_cert_callback_func`. These stay `static`/free functions — they unpack `cls` (a `webserver_impl*`, `webserver*`, `modded_request*`, or `ws_upgrade_data*`) and forward into a service. They are the C-ABI boundary, not behavior. `connection_notify`, `policy_callback`, and `request_completed` are the fullest members of this layer: they keep their per-connection arena (`connection_state` new/delete via `socket_context`), accept-policy, and request-teardown glue inline, delegating only the extracted free functions (`classify_decision`, `make_peer_address`) and the `ip_access_control` / `hook_dispatcher` collaborators — which is why they were not carved into a separate service (DR-014).
36
+
**The MHD adapter layer.** libmicrohttpd calls in through fixed-signature C trampolines carrying a `void* cls` closure: `answer_to_connection`, `request_completed`, `connection_notify`, `policy_callback`, `post_iterator`, `uri_log`, `error_log`, `unescaper_func`, `upgrade_handler`, plus the GnuTLS `psk_cred_handler_func` / `sni_cert_callback_func`. These stay `static`/free functions — they unpack `cls` (a `webserver_impl*`, `webserver*`, `connection_context*`, or `ws_upgrade_data*`) and forward into a service. They are the C-ABI boundary, not behavior. `connection_notify`, `policy_callback`, and `request_completed` are the fullest members of this layer: they keep their per-connection arena (`connection_state` new/delete via `socket_context`), accept-policy, and request-teardown glue inline, delegating only the extracted free functions (`classify_decision`, `make_peer_address`) and the `ip_access_control` / `hook_dispatcher` collaborators — which is why they were not carved into a separate service (DR-014).
37
37
38
38
**Free functions (not services).** Pure, instance-stateless helpers live as free functions in `httpserver::detail`, not one-method classes: `log_dispatch_error(const webserver_config&, std::string_view)` (called by every error path — a free function keeps it dependency-edge-free), `serialize_allow_methods` / `format_allow_header`, `resolve_method_callback`, `materialize_response`, `decorate_mhd_response`, `handle_post_form_arg`, `manage_upload_stream`.
39
39
40
-
**Per-request context.**`detail::modded_request` (per connection, arena/PMR-allocated per DR-003b) is the object the pipeline threads through the MHD callback sequence: `uri_log` allocates it; `answer_to_connection` (first invocation) stamps `start_time` / `standardized_url` / `method_enum` and builds the `http_request`; the body steps accumulate upload data; `finalize_answer` stages `response`; `request_completed` fires the terminal hook and deletes it. The services read and write its fields but do not own it.
40
+
**Per-request context.**`detail::connection_context` (per connection, arena/PMR-allocated per DR-003b) is the object the pipeline threads through the MHD callback sequence: `uri_log` allocates it; `answer_to_connection` (first invocation) stamps `start_time` / `standardized_url` / `method_enum` and builds the `http_request`; the body steps accumulate upload data; `finalize_answer` stages `response`; `request_completed` fires the terminal hook and deletes it. The services read and write its fields but do not own it.
41
41
42
42
**Key design notes:**
43
43
- Config is read-only to every service (`const webserver_config&`); no service holds the `webserver*` back-pointer. The back-pointer stays on the composition root for the members that genuinely need the owning `webserver*`.
Copy file name to clipboardExpand all lines: specs/architecture/04-components/http-resource.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
**Responsibility:** Stateful handler base for cases where state is shared across HTTP methods of one resource (counter, cache, DB handle, auth context).
4
4
5
-
**Implementation:** Public abstract base. Subclasses override one of `render_get / render_post / render_put / render_delete / render_patch / render_options / render_head` (renamed from v1's `render_GET` etc., to comply with PRD-NAM-REQ-001 snake_case). Each `render_*` override returns `http_response`**by value** (DR-004 / PRD-RSP-REQ-007); the webserver moves the returned value into the per-connection `modded_request::response` anchor. The default `render(...)` falls back when the method-specific override is not provided; it returns a default-constructed `http_response` whose `status_code_ == -1` sentinel routes through `internal_error_page`.
5
+
**Implementation:** Public abstract base. Subclasses override one of `render_get / render_post / render_put / render_delete / render_patch / render_options / render_head` (renamed from v1's `render_GET` etc., to comply with PRD-NAM-REQ-001 snake_case). Each `render_*` override returns `http_response`**by value** (DR-004 / PRD-RSP-REQ-007); the webserver moves the returned value into the per-connection `connection_context::response` anchor. The default `render(...)` falls back when the method-specific override is not provided; it returns a default-constructed `http_response` whose `status_code_ == -1` sentinel routes through `internal_error_page`.
6
6
7
7
The allow-mask (formerly `std::map<std::string, bool> method_state`) becomes `method_set methods_allowed_;` — a `uint32_t` bitmask wrapper (DR-6). `is_allowed(http_method)` and `get_allowed_methods()` are `const` and return without allocation.
0 commit comments