Skip to content

Commit 7e3c77f

Browse files
etrclaude
andcommitted
Merge TASK-054: auth_handler_ptr → optional<http_response>
Migrates auth_handler_ptr from shared_ptr<http_response> to optional<http_response>, removing the last shared_ptr<http_response> on the public API (PRD-RSP-REQ-007, DR-009). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 parents 00d57e2 + 2118212 commit 7e3c77f

15 files changed

Lines changed: 741 additions & 63 deletions

README.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -618,9 +618,10 @@ build, `webserver(create_webserver{}.use_ssl(true))` throws
618618
digest auth.
619619
* **`.auth_handler(auth_handler_ptr cb)`** — install a centralized
620620
authentication handler that runs before every resource's `render_*`
621-
call. The callback returns `nullptr` to allow the request to proceed,
622-
or an `http_response` (typically `http_response::unauthorized(realm)`)
623-
to reject it. Single source of truth for auth — see [Centralized
621+
call. The callback returns `std::nullopt` to allow the request to
622+
proceed, or an `http_response` (typically
623+
`http_response::unauthorized(realm)`) to reject it. Single source of
624+
truth for auth — see [Centralized
624625
authentication](#using-centralized-authentication).
625626
* **`.auth_skip_paths(const std::vector<std::string>& paths)`** — paths
626627
to bypass `auth_handler`. Exact match (`"/health"`) and wildcard
@@ -1226,14 +1227,13 @@ resource. Centralized authentication lets you define the policy once and
12261227
have it applied automatically to every request:
12271228

12281229
```cpp
1229-
// auth runs once per request before any render_*; return nullptr to allow
1230+
// auth runs once per request before any render_*; return std::nullopt to allow
12301231
auto auth = [](const httpserver::http_request& req)
1231-
-> std::shared_ptr<httpserver::http_response> {
1232+
-> std::optional<httpserver::http_response> {
12321233
if (req.get_user() != "admin" || req.get_pass() != "secret") {
1233-
return std::make_shared<httpserver::http_response>(
1234-
httpserver::http_response::unauthorized("MyRealm"));
1234+
return httpserver::http_response::unauthorized("MyRealm");
12351235
}
1236-
return nullptr;
1236+
return std::nullopt;
12371237
};
12381238

12391239
httpserver::webserver ws{httpserver::create_webserver(8080)
@@ -1248,9 +1248,10 @@ ws.start(true);
12481248
The `auth_handler` callback runs for every request before the resource's
12491249
render method. It receives the `http_request` and can:
12501250
1251-
* Return `nullptr` to allow the request to proceed normally.
1251+
* Return `std::nullopt` to allow the request to proceed normally.
12521252
* Return an `http_response` (typically `http_response::unauthorized`) to
1253-
reject the request.
1253+
reject the request. The response is moved onto the wire by the
1254+
dispatcher; no heap allocation is required.
12541255
12551256
`auth_skip_paths` accepts a vector of paths that should bypass the
12561257
handler:

RELEASE_NOTES.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ and see the v2 replacement.
159159
| `deferred_response` | `http_response::deferred` |
160160
| `basic_auth_fail_response` | `http_response::unauthorized` |
161161
| `digest_auth_fail_response` | `http_response::unauthorized` |
162+
| `auth_handler_ptr` returning `std::shared_ptr<http_response>` | `auth_handler_ptr` returning `std::optional<http_response>` |
162163

163164
> **Note:** `shoutCAST()` is the sole camelCase survivor in the v2 public API.
164165
> The name maps to the SHOUTcast streaming protocol and is intentionally
@@ -171,6 +172,16 @@ and see the v2 replacement.
171172
v2 returns a value. The framework moves it into the dispatch path. No
172173
heap allocation is required for small responses (small-buffer optimisation
173174
inside `http_response`).
175+
- **Centralized auth handler returns `std::optional<http_response>`.**
176+
The `auth_handler` callback signature is now
177+
`std::function<std::optional<http_response>(const http_request&)>`
178+
(earlier v2 work-in-progress shipped
179+
`std::function<std::shared_ptr<http_response>(const http_request&)>`).
180+
Return `std::nullopt` to allow the request; return an `http_response`
181+
to reject. The v1 `shared_ptr` shape still compiles for one
182+
transitional build via `httpserver::compat::auth_handler_v1_ptr` and a
183+
`[[deprecated]]` setter overload; both emit a deprecation warning.
184+
Removes one heap allocation per authenticated request.
174185
- **`http_request` getters return `const&` / `string_view`.** v1's
175186
`get_header(name)` (and `get_arg`, `get_cookie`, `get_footer`) returned
176187
by value and inserted an empty entry into the request map on miss; v2's

examples/centralized_authentication.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*/
2020

2121
// centralized_authentication.cpp - install a server-wide auth_handler
22-
// that runs before every request. The handler returns nullptr to
22+
// that runs before every request. The handler returns std::nullopt to
2323
// accept, or an http_response to reject. auth_skip_paths lists routes
2424
// that bypass auth entirely.
2525
//
@@ -42,29 +42,30 @@
4242

4343
#include <cstdlib>
4444
#include <iostream>
45-
#include <memory>
45+
#include <optional>
4646
#include <string>
4747

4848
#include <httpserver.hpp>
4949

50-
// Returns nullptr to allow the request, or an http_response to reject it.
51-
std::shared_ptr<httpserver::http_response> auth_handler(
50+
// Returns std::nullopt to allow the request, or an engaged optional
51+
// carrying an http_response to reject it (TASK-054).
52+
std::optional<httpserver::http_response> auth_handler(
5253
const httpserver::http_request& req) {
5354
const char* expected_user = std::getenv("AUTH_USER");
5455
const char* expected_pass = std::getenv("AUTH_PASS");
5556
if (!expected_user || !expected_pass) {
5657
std::cerr << "centralized_authentication: AUTH_USER and AUTH_PASS "
5758
"environment variables must be set.\n";
58-
return std::make_shared<httpserver::http_response>(
59+
return std::optional<httpserver::http_response>(
5960
httpserver::http_response::string("Server configuration error")
6061
.with_status(500));
6162
}
6263
if (req.get_user() != expected_user || req.get_pass() != expected_pass) {
63-
return std::make_shared<httpserver::http_response>(
64+
return std::optional<httpserver::http_response>(
6465
httpserver::http_response::unauthorized(
6566
"Basic", "MyRealm", "Unauthorized"));
6667
}
67-
return nullptr;
68+
return std::nullopt;
6869
}
6970

7071
int main() {

specs/architecture/04-components/create-webserver.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@
66

77
The builder remains non-PIMPL (it's a pure value carrier; PIMPL would buy nothing).
88

9-
**Related requirements:** PRD-CFG-REQ-001..004.
9+
**`auth_handler_ptr` shape (TASK-054).** The centralised authentication
10+
callback is `std::function<std::optional<http_response>(const http_request&)>`.
11+
Returning `std::nullopt` allows the request through; returning an
12+
engaged optional short-circuits the before_handler chain with that
13+
response (the dispatcher moves it into `mr->response`). The earlier v2
14+
work-in-progress shape returned `std::shared_ptr<http_response>`; that
15+
shape remains available for one transitional build via the
16+
`httpserver::compat::auth_handler_v1_ptr` typedef alias and a
17+
`[[deprecated]]` setter overload (`auth_handler(compat::auth_handler_v1_ptr)`),
18+
both of which wrap the legacy callable via `compat::adapt_legacy_auth`
19+
into the canonical `auth_handler_ptr` shape and emit a deprecation
20+
diagnostic at the call site. The compat alias and overload will be
21+
removed after the next release. Rationale: completes the
22+
PRD-RSP-REQ-007 value-typed response cutover (DR-009) onto the auth
23+
path, removing the per-authenticated-request control-block allocation.
24+
25+
**Related requirements:** PRD-CFG-REQ-001..004, PRD-RSP-REQ-007.
1026

1127
---

specs/tasks/v2-deferred-backlog-plan.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,22 +107,22 @@ TODO comment marking this migration already lives at
107107
`src/httpserver/create_webserver.hpp:92-99`.
108108

109109
**Action Items:**
110-
- [ ] Define `auth_handler_ptr` as
110+
- [x] Define `auth_handler_ptr` as
111111
`std::function<std::optional<http_response>(const http_request&)>` in
112112
`create_webserver.hpp`. Keep the old typedef alias for one transitional
113113
build with a `[[deprecated]]` attribute pointing at the new shape.
114-
- [ ] Update the auth short-circuit path in `webserver_dispatch.cpp` to
114+
- [x] Update the auth short-circuit path in `webserver_dispatch.cpp` to
115115
consume `optional<http_response>` instead of dereferencing a
116116
`shared_ptr`.
117-
- [ ] Update the documented v1 alias surface (`auth_handler` setter) to
117+
- [x] Update the documented v1 alias surface (`auth_handler` setter) to
118118
internally adapt v1 callers that still produce a `shared_ptr` (free
119119
function `compat::adapt_legacy_auth(...)`), with a `[[deprecated]]`
120120
warning.
121-
- [ ] Update `examples/centralized_authentication.cpp` to return
121+
- [x] Update `examples/centralized_authentication.cpp` to return
122122
`std::optional<http_response>` directly (drop `std::make_shared`).
123-
- [ ] Update Doxygen, README "Centralized authentication" section, and
123+
- [x] Update Doxygen, README "Centralized authentication" section, and
124124
RELEASE_NOTES.md "Migration notes" with the new signature.
125-
- [ ] Remove the TODO comment at `create_webserver.hpp:92-99`.
125+
- [x] Remove the TODO comment at `create_webserver.hpp:92-99`.
126126

127127
**Dependencies:**
128128
- Blocked by: TASK-046 (by-value response cutover, Done)
@@ -141,6 +141,8 @@ TODO comment marking this migration already lives at
141141
**Related Findings:** task-036 #38, task-030 #18
142142
**Related Decisions:** DR-009, PRD-RSP-REQ-007
143143

144+
**Status:** Done
145+
144146
---
145147

146148
## TASK-055 — DR-009 revision: default error body must not surface `e.what()`

0 commit comments

Comments
 (0)