Skip to content

Commit c84d7fd

Browse files
etrclaude
andcommitted
TASK-042: Write RELEASE_NOTES.md for v2.0
Adds RELEASE_NOTES.md as a one-stop v1→v2.0 porting guide: TL;DR, "What's gone" / "What's new" / "What's renamed" (table form so v1 users can grep for any old name and find its replacement on the same line), "What changed semantically", "Threading", "Error propagation", "Build prerequisites", and "SOVERSION and packaging". Explicitly flagged as informational, not a compatibility commitment. Adds scripts/check-release-notes.sh — the gate that drove the writing. It is modelled on scripts/check-readme.sh (same structure, same fail() helper, same set -euo pipefail discipline) and enforces: A1 RELEASE_NOTES.md exists at REPO_ROOT. A2 Every required v1-era token appears at least once (32 tokens). A3 Every required v2-era token appears at least once (26 tokens, kept in lock-step with check-readme.sh A3). A4 The seven required H2 sections exist (case-insensitive). A5 Each high-value (v1 → v2) rename pair appears on the same line (25 pairs — this is the load-bearing "v1 user can grep for any v1 method name and find what replaced it" acceptance criterion). A6 The Threading and Error-propagation sections cite the architecture sources (DR-008 / §5.1 and DR-009 / §5.2 respectively). A7 An explicit "not a compatibility commitment" disclaimer. S1-S3 Markdown sanity (balanced fences, one H1, no tabs in fences). The four "Major" learnings from TASK-041's unworked review are applied: - `set -euo pipefail` from line 1 (not just `set -u`). - Wired into Makefile.am: new `check-release-notes:` target in EXTRA_DIST, in `.PHONY`, and prerequisite of `check-local` alongside `check-headers check-examples check-readme`. - Empty section-body guards before the citation greps (mirror the `[ -z "$thread_body" ]` guard). - LF-only line-ending assumption documented in the script header. `make check` is green end to end: 48 tests pass, all four check-* scripts pass, header hygiene and install layout clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c91555d commit c84d7fd

5 files changed

Lines changed: 546 additions & 8 deletions

File tree

Makefile.am

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ endif
3939
endif
4040

4141
EXTRA_DIST = libhttpserver.pc.in $(DX_CONFIG) scripts/extract-release-notes.sh scripts/validate-version.sh \
42-
scripts/check-examples.sh scripts/check-readme.sh scripts/verify-installed-examples.sh \
42+
scripts/check-examples.sh scripts/check-readme.sh scripts/check-release-notes.sh \
43+
scripts/verify-installed-examples.sh \
4344
test/headers/consumer_direct.cpp test/headers/consumer_detail.cpp test/headers/consumer_umbrella.cpp \
4445
test/headers/consumer_post_umbrella.cpp \
4546
test/headers/consumer_umbrella_no_backend.cpp
@@ -282,7 +283,7 @@ check-hygiene:
282283
# shared staged install to avoid paying two full `make install` costs on
283284
# every `make check`. Both sub-checks can still be invoked standalone (they
284285
# will do their own install when CHECK_*_SHARED is not set).
285-
check-local: check-headers check-examples check-readme
286+
check-local: check-headers check-examples check-readme check-release-notes
286287
@echo "=== Shared staged install for check-install-layout and check-hygiene ==="
287288
@rm -rf $(abs_top_builddir)/.shared-check-stage
288289
@$(MAKE) $(AM_MAKEFLAGS) install DESTDIR=$(abs_top_builddir)/.shared-check-stage >check-shared-install.log 2>&1 || { \
@@ -311,7 +312,14 @@ check-readme:
311312
@echo "=== check-readme: enforce TASK-041 invariants on README.md ==="
312313
@$(top_srcdir)/scripts/check-readme.sh
313314

314-
.PHONY: check-headers check-install-layout check-hygiene check-examples check-readme
315+
# TASK-042: enforce static invariants on RELEASE_NOTES.md
316+
# (required v1/v2 tokens, sections, same-line rename pairs, arch citations,
317+
# disclaimer). Run as part of `make check` via check-local.
318+
check-release-notes:
319+
@echo "=== check-release-notes: enforce TASK-042 invariants on RELEASE_NOTES.md ==="
320+
@$(top_srcdir)/scripts/check-release-notes.sh
321+
322+
.PHONY: check-headers check-install-layout check-hygiene check-examples check-readme check-release-notes
315323

316324
# TASK-039: top-level convenience rule that descends into test/ to
317325
# build and run the bench binaries (see test/Makefile.am and

RELEASE_NOTES.md

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# libhttpserver v2.0 — Release Notes
2+
3+
> **Status:** Informational, not a compatibility commitment.
4+
> This document summarises every public-surface change in v2.0 to help v1
5+
> consumers port. It is not part of the API contract and is not exhaustive
6+
> — when in doubt, the headers in `src/httpserver/` are authoritative.
7+
8+
## TL;DR
9+
10+
libhttpserver v2.0 is a **clean cutover** from the v1.x line: no shims, no
11+
deprecated overloads, no inline namespace. Handlers are now lambdas
12+
(`ws.on_get("/", [](auto&){ return http_response::string("hi"); })`);
13+
responses are value types built from `http_response::string` / `file` /
14+
`iovec` / `pipe` / `empty` / `deferred` / `unauthorized` factories;
15+
request getters return `const&` / `string_view` and never insert on miss;
16+
the threading and error-propagation contracts are documented (DR-008,
17+
DR-009); features disabled at build time report at runtime via
18+
`feature_unavailable` or a documented sentinel value instead of vanishing
19+
from the API. SOVERSION bumps from 1 to 2 and v2 ships as a
20+
`libhttpserver2` binary that is parallel-installable with `libhttpserver1`.
21+
v1.x is end-of-life on the day v2.0 ships.
22+
23+
## What's gone
24+
25+
- **Response subclasses.** `string_response`, `file_response`,
26+
`iovec_response`, `pipe_response`, `deferred_response`, `empty_response`,
27+
`basic_auth_fail_response`, `digest_auth_fail_response` are removed.
28+
Build responses through the `http_response::*` factory chain instead
29+
(see "What's renamed" for the 1:1 mapping).
30+
- **Raw-pointer registration.** `register_resource(string, http_resource*, bool family)`
31+
is gone. Use `register_path(unique_ptr|shared_ptr)` or
32+
`register_prefix(unique_ptr|shared_ptr)`.
33+
`register_ws_resource(string, websocket_handler*)` is gone. Use the
34+
smart-pointer overload.
35+
- **`sweet_kill`.** Removed. Use `stop_and_wait()` (or `stop()` for the
36+
signal-only form).
37+
- **IP allow/deny verbs.** `ban_ip`, `unban_ip`, `allow_ip`,
38+
`disallow_ip` are removed. Use `block_ip` / `unblock_ip`.
39+
- **Paired `no_*` boolean setters.** `no_basic_auth`, `no_digest_auth`,
40+
`no_ssl`, `no_debug`, `no_pedantic`, `no_deferred`, `no_regex_checking`,
41+
`no_ban_system`, `no_post_process`, `no_single_resource`, `no_ipv6`,
42+
`no_dual_stack` are removed. The remaining single setter takes a `bool`:
43+
e.g. `cw.ssl(false)` replaces `cw.no_ssl()`.
44+
- **`#define` constants in public headers.** `DEFAULT_WS_PORT`,
45+
`DEFAULT_WS_TIMEOUT`, and the `*_ERROR` / `*_RESPONSE` macros are gone.
46+
Their `constexpr` replacements live in the `httpserver::constants`
47+
namespace.
48+
- **`gnutls_session_t`-returning methods on `http_request`.** Removed.
49+
Use the high-level accessors: `get_client_cert_dn()`,
50+
`get_client_cert_issuer_dn()`, `get_client_cert_fingerprint()`,
51+
`get_cipher_suite()`, `get_protocol_version()`, `get_client_kx()`,
52+
`get_client_cert_type()`, `get_client_credential_type()`.
53+
- **Public virtuals on `http_response`.** `get_raw_response`,
54+
`decorate_response`, `enqueue_response` are gone. `http_response` is no
55+
longer an open base class; it is a value type with no virtual methods.
56+
- **`#ifdef HAVE_BAUTH` / `HAVE_DAUTH` / `HAVE_GNUTLS` / `HAVE_WEBSOCKET`
57+
guards in public headers.** Removed. The public API is now
58+
build-flag-invariant: every method exists on every build. Calls into a
59+
feature disabled at configure time throw `feature_unavailable` or
60+
return a documented sentinel; query the runtime configuration via
61+
`ws.features()`.
62+
- **Public transitive includes of backend C headers.** `<microhttpd.h>`,
63+
`<pthread.h>`, `<gnutls/gnutls.h>`, `<sys/socket.h>` no longer leak
64+
through `<httpserver.hpp>`. A consumer that needs MHD or GnuTLS types
65+
must include them directly.
66+
- **The implicit conversion** `webserver ws = cw;` (where `cw` is a
67+
`create_webserver`). The constructor is now `explicit`.
68+
69+
## What's new
70+
71+
- **Lambda registration.** `ws.on_get(path, handler)`, `on_post`,
72+
`on_put`, `on_delete`, `on_patch`, `on_options`, `on_head`. The handler
73+
is `http_response(const http_request&)`. No subclass required.
74+
- **Runtime-method registration.** `ws.route(http_method | method_set,
75+
path, handler)` registers a single handler for a method known only at
76+
runtime, or atomically for a bitmask of methods.
77+
- **Smart-pointer resource registration.** `register_path(path, h)` and
78+
`register_prefix(path, h)` accept `std::unique_ptr<http_resource>` or
79+
`std::shared_ptr<http_resource>`. The boolean `family` flag that used
80+
to live as the third positional argument of `register_resource` is gone
81+
— the choice is encoded in the method name.
82+
- **`http_response` factory chain.** `http_response::string(body)`,
83+
`http_response::file(path)`, `http_response::iovec(entries)`,
84+
`http_response::pipe(fd)`, `http_response::empty(status)`,
85+
`http_response::deferred(...)`,
86+
`http_response::unauthorized(scheme, realm, ...)`. Each returns a
87+
value-type `http_response`. Chain fluent mutators:
88+
`.with_status(int)`, `.with_header(name, value)`, `.with_footer(...)`,
89+
`.with_cookie(...)`.
90+
- **`feature_unavailable`.** Public `std::runtime_error` subclass thrown
91+
by API entry points whose backend feature was disabled at configure
92+
time (e.g. calling `ssl_cert(...)` on a `HAVE_GNUTLS=no` build).
93+
- **`webserver::features()`.** Returns a struct of `bool`s describing
94+
which optional features are linked in (TLS, basic auth, digest auth,
95+
websocket, …). Use this for runtime branching instead of `#ifdef HAVE_*`.
96+
- **`iovec_entry`.** Public POD struct (`{void* iov_base; size_t iov_len;}`)
97+
used by `http_response::iovec`. Replaces the opaque scatter-gather
98+
type that v1 leaked from `<sys/uio.h>`.
99+
- **`http_method` / `method_set`.** Strongly-typed enum and bitmask for
100+
HTTP methods. `route()` accepts either; `http_resource::allow_methods`
101+
uses `method_set`.
102+
- **`httpserver::constants` namespace.** `constexpr` replacements for
103+
every removed `#define`.
104+
105+
## What's renamed (v1 → v2)
106+
107+
Every entry below is a single line so a v1 user can grep for the v1 name
108+
and see the v2 replacement.
109+
110+
| v1 | v2 |
111+
|---|---|
112+
| `sweet_kill` | `stop_and_wait` |
113+
| `ban_ip` | `block_ip` |
114+
| `unban_ip` | `unblock_ip` |
115+
| `allow_ip` | `unblock_ip` (or `block_ip` to deny) |
116+
| `disallow_ip` | `block_ip` (or `unblock_ip` to allow) |
117+
| `not_found_resource` (setter) | `not_found_handler` |
118+
| `method_not_allowed_resource` (setter) | `method_not_allowed_handler` |
119+
| `internal_error_resource` (setter) | `internal_error_handler` |
120+
| `render_GET` | `render_get` |
121+
| `render_POST` | `render_post` |
122+
| `render_PUT` | `render_put` |
123+
| `render_DELETE` | `render_delete` |
124+
| `render_HEAD` | `render_head` |
125+
| `render_OPTIONS` | `render_options` |
126+
| `render_PATCH` | `render_patch` |
127+
| `render_CONNECT` | `render_connect` |
128+
| `render_TRACE` | `render_trace` |
129+
| `webserver(create_webserver const&)` — implicit | `explicit webserver(create_webserver const&)` |
130+
| `register_resource(string, http_resource*, bool family)` — raw pointer + bool flag | `register_path` / `register_prefix` taking `unique_ptr` or `shared_ptr` |
131+
| `register_ws_resource(string, websocket_handler*)` — raw pointer | `register_ws_resource(string, unique_ptr<websocket_handler>)` (and a `shared_ptr` overload) |
132+
| `string_response` | `http_response::string` |
133+
| `file_response` | `http_response::file` |
134+
| `iovec_response` | `http_response::iovec` |
135+
| `pipe_response` | `http_response::pipe` |
136+
| `empty_response` | `http_response::empty` |
137+
| `deferred_response` | `http_response::deferred` |
138+
| `basic_auth_fail_response` | `http_response::unauthorized` |
139+
| `digest_auth_fail_response` | `http_response::unauthorized` |
140+
141+
## What changed semantically
142+
143+
- **Handlers return `http_response` by value.** v1 returned
144+
`std::unique_ptr<http_response>` (and earlier `std::shared_ptr<http_response>`);
145+
v2 returns a value. The framework moves it into the dispatch path. No
146+
heap allocation is required for small responses (small-buffer optimisation
147+
inside `http_response`).
148+
- **`http_request` getters return `const&` / `string_view`.** v1's
149+
`get_header(name)` (and `get_arg`, `get_cookie`, `get_footer`) returned
150+
by value and inserted an empty entry into the request map on miss; v2's
151+
versions return a reference / `string_view` and never mutate the
152+
request. Container getters (`get_headers()`, `get_args()`, …) return
153+
`const&` to the underlying map.
154+
- **`http_response::get_header` / `get_footer` / `get_cookie` are
155+
const and do not insert on miss.** Mutation uses the
156+
`with_header` / `with_footer` / `with_cookie` chain, which returns
157+
`*this` by reference (`&` on lvalue calls, `&&` on rvalue calls) for
158+
fluent assembly.
159+
- **`webserver(create_webserver const&)` is `explicit`.** Direct-init
160+
`webserver ws{cw};` rather than copy-init `webserver ws = cw;`.
161+
162+
## Threading
163+
164+
The threading contract is now documented; in v1 it was implicit.
165+
See [README.md "Threading contract"](README.md), architecture §5.1
166+
([specs/architecture/05-cross-cutting.md](specs/architecture/05-cross-cutting.md))
167+
and DR-008 ([specs/architecture/11-decisions/DR-008.md](specs/architecture/11-decisions/DR-008.md))
168+
for the full statement. The load-bearing points for porters:
169+
170+
- Handler invocations from MHD worker threads run concurrently. Shared
171+
state owned by an `http_resource` subclass (or captured by a lambda)
172+
must be synchronised by the application.
173+
- `start()` is one-shot; **do not** call `stop()` or `stop_and_wait()`
174+
from inside a handler thread — MHD's joinable internal thread cannot
175+
join itself and the call aborts the process (DR-008).
176+
- `~webserver()` runs `stop_and_wait()` if the server is still running,
177+
so the same self-join restriction applies to destruction from a
178+
handler thread.
179+
180+
## Error propagation
181+
182+
The error-propagation contract is now documented; in v1 it was implicit.
183+
See [README.md "Error propagation"](README.md), architecture §5.2
184+
([specs/architecture/05-cross-cutting.md](specs/architecture/05-cross-cutting.md))
185+
and DR-009 ([specs/architecture/11-decisions/DR-009.md](specs/architecture/11-decisions/DR-009.md))
186+
for the full statement. The load-bearing points for porters:
187+
188+
- A handler that throws lands at the configured
189+
`internal_error_handler` (a `std::function<http_response(const http_request&, std::string_view)>`).
190+
The `string_view` carries `what()` from the caught exception. Default
191+
behaviour: log and return `500`.
192+
- Calling an API entry point whose backend feature is disabled at
193+
configure time (e.g. `ssl_cert(...)` on a `HAVE_GNUTLS=no` build)
194+
throws `feature_unavailable` — a public `std::runtime_error` subclass.
195+
Build-flag-disabled features therefore surface at **runtime** rather
196+
than disappearing from the API at compile time; query
197+
`ws.features()` to branch defensively.
198+
- The framework never swallows exceptions silently. Anything that
199+
escapes a handler is logged via the configured `log_error` callback
200+
before the `internal_error_handler` fires.
201+
202+
## Build prerequisites
203+
204+
- **C++20 floor.** libhttpserver 1.x's last release was C++17-compatible;
205+
v2.0 drops C++17. Concepts, `<bit>`, `<span>`, and `consteval` appear
206+
in public headers.
207+
- **Toolchains known good out of the box:** Debian 13 GCC 14.2,
208+
RHEL 10 GCC 14, FreeBSD 14 Clang 18+, current Apple Clang, and
209+
Homebrew GCC 15+.
210+
- **RHEL 9.** Stock GCC 11 on RHEL 9 is too old. Install Red Hat's
211+
`gcc-toolset-14` overlay and `source scl_source enable gcc-toolset-14`
212+
before configuring.
213+
214+
See [specs/architecture/08-build-and-packaging.md](specs/architecture/08-build-and-packaging.md)
215+
for the full matrix.
216+
217+
## SOVERSION and packaging
218+
219+
SOVERSION bumps from `1` to `2`. v2 ships as `libhttpserver2` (binary)
220+
with `libhttpserver2-dev` / `libhttpserver2-devel` for headers and
221+
pkg-config. v1's `libhttpserver1` and v2's `libhttpserver2` are
222+
**parallel-installable**: applications mid-port can link against both
223+
side by side, including under the same `/usr` prefix.
224+
225+
There is **no inline namespace** and **no symbol-versioning script**:
226+
v2 is a clean rename, not an ABI overlay over v1. v1.x is end-of-life
227+
on the day v2.0 ships — there is no v1 maintenance branch (PRD §1,
228+
DR-011 ([specs/architecture/11-decisions/DR-011.md](specs/architecture/11-decisions/DR-011.md))).
229+
230+
## See also
231+
232+
- [README.md](README.md) — full v2.0 introduction and worked examples.
233+
- [examples/](examples/) — every example is v2.0-idiomatic.
234+
- [specs/product_specs.md](specs/product_specs.md) §3.1–§3.7 — the
235+
authoritative requirement set behind each change.
236+
- [ChangeLog](ChangeLog) — formal per-version log.

0 commit comments

Comments
 (0)