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