Skip to content

Commit 0d0ea3d

Browse files
etrclaude
andcommitted
TASK-011: http_response const-correct accessors
Replace the v1 insert-on-miss accessors on http_response with const, string_view-returning lookups that do NOT mutate the underlying maps. Fixes PRD-RSP-REQ-002 (callable on const&) and PRD-RSP-REQ-003 (no insert on miss). Changes: - http_utils.hpp: add `is_transparent` to header_comparator so header_map::find(string_view) is a heterogeneous lookup. - http_response.hpp: * get_header / get_footer / get_cookie now take string_view, are const, and return std::string_view (empty on miss). * get_headers / get_footers / get_cookies marked noexcept and return const http::header_map&. * Add get_status() (noexcept) as the v2 spelling of the status code accessor; get_response_code() retained as a compatibility alias while v1 subclasses still inherit (TASK-013 removes both together with webserver.cpp:1336's dispatch path). * Lifetime contract documented above the single-key accessors. - http_response.cpp: out-of-line definitions for the three single-key accessors via a shared header_map_find_view helper. - test/unit/http_response_test.cpp: add 11 tests covering const callability, no-insert-on-miss for headers/footers/cookies, empty view on miss, read-back after with_header from const&, get_status / kind / get_headers noexcept, single-key accessors take string_view, case-insensitive lookup, view reflects replacement. Also tighten existing `auto headers = resp.get_headers()` lines to `const auto&` (avoids needless map copies now that the accessor returns by reference). - test/integ/basic.cpp: silence -Werror,-Wunused-variable on the smoke `auto checksum = response->get_footer(...)` line; the variable was harmless under the v1 const-string& return but a string_view dtor is trivial so the warning now fires. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 21f1ab8 commit 0d0ea3d

5 files changed

Lines changed: 257 additions & 37 deletions

File tree

src/http_response.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,39 @@ void http_response::shoutCAST() {
189189
status_code_ |= http::http_utils::shoutcast_response;
190190
}
191191

192+
// -----------------------------------------------------------------------
193+
// Const single-key accessors (TASK-011).
194+
//
195+
// All three share the same shape: heterogeneous lookup into the
196+
// corresponding header_map (transparent header_comparator), returning an
197+
// empty std::string_view on miss. NEVER inserts (PRD-RSP-REQ-003); the
198+
// previous v1 accessors used `headers_[key]`, which silently inserted
199+
// an empty entry on miss and consequently could not be const.
200+
//
201+
// View lifetime is documented in the class-level contract block in
202+
// http_response.hpp.
203+
// -----------------------------------------------------------------------
204+
namespace {
205+
inline std::string_view header_map_find_view(const http::header_map& m,
206+
std::string_view key) {
207+
auto it = m.find(key);
208+
if (it == m.end()) return {};
209+
return std::string_view(it->second);
210+
}
211+
} // namespace
212+
213+
std::string_view http_response::get_header(std::string_view key) const {
214+
return header_map_find_view(headers_, key);
215+
}
216+
217+
std::string_view http_response::get_footer(std::string_view key) const {
218+
return header_map_find_view(footers_, key);
219+
}
220+
221+
std::string_view http_response::get_cookie(std::string_view key) const {
222+
return header_map_find_view(cookies_, key);
223+
}
224+
192225
namespace {
193226
static inline http::header_view_map to_view_map(const http::header_map& hdr_map) {
194227
http::header_view_map view_map;

src/httpserver/http_response.hpp

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -183,53 +183,87 @@ class http_response {
183183
std::string_view realm,
184184
std::string body = {});
185185

186-
/**
187-
* Method used to get a specified header defined for the response
188-
* @param key The header identification
189-
* @return a string representing the value assumed by the header
190-
**/
191-
const std::string& get_header(const std::string& key) {
192-
return headers_[key];
193-
}
186+
// -----------------------------------------------------------------
187+
// Read accessors (TASK-011, PRD-RSP-REQ-002 / PRD-RSP-REQ-003).
188+
//
189+
// Lifetime contract for the string_view-returning accessors:
190+
//
191+
// The returned view points into storage owned by *this. The view is
192+
// valid until ANY of the following happen:
193+
// 1. *this is destroyed.
194+
// 2. *this is moved-from (move ctor / move-assign target).
195+
// 3. The corresponding map is mutated for the SAME key
196+
// (with_header(key, ...) replacing an existing value
197+
// invalidates a view obtained from a prior get_header(key)).
198+
//
199+
// std::map's node-stability guarantee means that adding or removing
200+
// OTHER keys does NOT invalidate views of unrelated keys; only
201+
// same-key re-assignment, erase, or whole-response destruction
202+
// does. Multi-value headers are not modelled in v2.0 — header_map
203+
// is single-valued per key.
204+
//
205+
// Callers MUST NOT keep the view past the next non-const operation
206+
// on the response, and MUST NOT keep it past the response's
207+
// destruction. If a longer lifetime is required, copy into a
208+
// std::string.
209+
//
210+
// No noexcept on the single-key accessors: std::map::find can in
211+
// principle propagate a comparator exception. The map-returning
212+
// accessors and the trivial scalar accessors (get_status, kind) are
213+
// noexcept (they only return a reference / scalar member).
214+
// -----------------------------------------------------------------
194215

195-
/**
196-
* Method used to get a specified footer defined for the response
197-
* @param key The footer identification
198-
* @return a string representing the value assumed by the footer
199-
**/
200-
const std::string& get_footer(const std::string& key) {
201-
return footers_[key];
202-
}
216+
/// Returns the value of header `key`, or an empty view if absent.
217+
/// Does NOT insert on miss (PRD-RSP-REQ-003).
218+
/// View lifetime: see lifetime contract above.
219+
[[nodiscard]] std::string_view get_header(std::string_view key) const;
203220

204-
const std::string& get_cookie(const std::string& key) {
205-
return cookies_[key];
206-
}
221+
/// Returns the value of footer `key`, or an empty view if absent.
222+
/// Does NOT insert on miss. View lifetime: see lifetime contract.
223+
[[nodiscard]] std::string_view get_footer(std::string_view key) const;
224+
225+
/// Returns the value of cookie `key`, or an empty view if absent.
226+
/// Does NOT insert on miss. View lifetime: see lifetime contract.
227+
[[nodiscard]] std::string_view get_cookie(std::string_view key) const;
207228

208229
/**
209230
* Method used to get all headers passed with the request.
210231
* @return a map<string,string> containing all headers.
211232
**/
212-
const std::map<std::string, std::string, http::header_comparator>& get_headers() const {
233+
[[nodiscard]] const http::header_map& get_headers() const noexcept {
213234
return headers_;
214235
}
215236

216237
/**
217238
* Method used to get all footers passed with the request.
218239
* @return a map<string,string> containing all footers.
219240
**/
220-
const std::map<std::string, std::string, http::header_comparator>& get_footers() const {
241+
[[nodiscard]] const http::header_map& get_footers() const noexcept {
221242
return footers_;
222243
}
223244

224-
const std::map<std::string, std::string, http::header_comparator>& get_cookies() const {
245+
[[nodiscard]] const http::header_map& get_cookies() const noexcept {
225246
return cookies_;
226247
}
227248

228249
/**
229-
* Method used to get the response code from the response
250+
* Method used to get the response status code.
251+
* Spelled `get_status` to match the v2 vocabulary (TASK-011);
252+
* `get_response_code` survives as a compatibility alias while the
253+
* v1 subclass hierarchy still inherits from http_response
254+
* (TASK-013 removes both the subclasses and the alias together
255+
* with the dispatch path in webserver.cpp:1336).
230256
* @return The response code
231257
**/
232-
int get_response_code() const {
258+
[[nodiscard]] int get_status() const noexcept {
259+
return status_code_;
260+
}
261+
262+
// Compatibility shim retained while v1 subclasses still inherit
263+
// (TASK-013 removes them). Internal dispatch (webserver.cpp:1336)
264+
// reaches through a base pointer; that call site flips to
265+
// get_status() when TASK-013 lands.
266+
[[nodiscard]] int get_response_code() const noexcept {
233267
return status_code_;
234268
}
235269

src/httpserver/http_utils.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,12 @@ class http_utils {
312312

313313
class header_comparator {
314314
public:
315+
// is_transparent enables heterogeneous lookup against header_map
316+
// (std::map<std::string, std::string, header_comparator>): callers
317+
// can pass std::string_view directly to find()/count() without
318+
// constructing a std::string. Required by TASK-011's
319+
// string_view-returning const accessors on http_response.
320+
using is_transparent = std::true_type;
315321
/**
316322
* Operator used to compare strings.
317323
* @param first string

test/integ/basic.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2472,9 +2472,14 @@ class response_footer_resource : public http_resource {
24722472
response->with_footer("X-Checksum", "abc123");
24732473
response->with_footer("X-Processing-Time", "42ms");
24742474

2475-
// Test get_footer and get_footers on response
2475+
// Test get_footer and get_footers on response. The returned
2476+
// string_view points into the response's storage; we only
2477+
// read it before returning so the response (and thus the
2478+
// backing string) outlives any read.
24762479
auto checksum = response->get_footer("X-Checksum");
24772480
auto all_footers = response->get_footers();
2481+
(void)checksum;
2482+
(void)all_footers;
24782483

24792484
return response;
24802485
}

0 commit comments

Comments
 (0)