Skip to content

Commit 32dfdf1

Browse files
etrclaude
andcommitted
webserver: decompose finalize_answer under the CCN 10 bar (46 -> 6)
finalize_answer was a 255-line monolith doing WebSocket upgrade detection, v1 resource lookup with regex+LRU cache, centralized auth, handler dispatch (with try/catch + method-not-allowed), and response materialisation/queue all inline. CCN 46. Decomposed into per-responsibility helpers on detail::webserver_impl (which already has the parent back-pointer and includes the MHD headers behind the PIMPL barrier): try_handle_websocket_upgrade probe + delegate. optional<MHD_Result>. validate_websocket_handshake RFC 6455 header validation. complete_websocket_upgrade handler lookup, accept header, queue. resolve_resource_for_request single-resource / exact / regex+cache. lookup_route_cache LRU front-promote + match copy. scan_regex_routes longest-match-wins linear scan. store_route_cache LRU front-insert + eviction. apply_extracted_params url_pars/chunks -> request args. apply_auth_short_circuit centralized auth handler short-circuit. dispatch_resource_handler try/catch around handler invocation serialize_allow_methods method_set -> "GET, HEAD, ..." (TASK-021). materialize_and_queue_response final stage with fallback path. finalize_answer is now a 6-call orchestrator at CCN 6. Every new helper sits at CCN <= 8. Locking discipline preserved: * resolve_resource_for_request takes the shared registered_resources lock for the entire lookup phase and releases at scope exit, exactly as the original {…} block did. * lookup_route_cache / store_route_cache each take route_cache_mutex internally (nested under registered_resources, matching the original lock-order documented at the head of route_cache_v2). * TASK-035: complete_websocket_upgrade takes the shared_ptr copy under the shared lock before unlocking, preserving the handler-alive-across-upgrade invariant. Subtle reformulation in scan_regex_routes: the original guarded the inner match() with `!found || pieces_len > len || (pieces_len == len && tot_len > tot_len)`. I inverted it to a `continue` for clarity (skip when found AND not strictly longer in either dimension). The boolean is identical. Verified locally: full `make check` (all 48 unit tests + invariants). scripts/check-complexity.sh CCN_MAX ratcheted 47 -> 35 (the new worst offender is ip_representation::ip_representation at CCN 34). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 60b4c64 commit 32dfdf1

3 files changed

Lines changed: 377 additions & 228 deletions

File tree

scripts/check-complexity.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
set -euo pipefail
2828

2929
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
30-
CCN_MAX="${CCN_MAX:-47}"
30+
CCN_MAX="${CCN_MAX:-35}"
3131

3232
# Prefer the standalone `lizard` entrypoint if it's on PATH; fall back to
3333
# `python3 -m lizard` which is what `pip install --user lizard` produces

src/httpserver/detail/webserver_impl.hpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
#include <memory>
5151
#include <memory_resource>
5252
#include <mutex>
53+
#include <optional>
5354
#include <regex>
5455
#include <set>
5556
#include <shared_mutex>
@@ -383,6 +384,100 @@ class webserver_impl {
383384
// finalize_answer now consults mr->method_enum (set once by
384385
// answer_to_connection) for the is_allowed check.
385386
MHD_Result finalize_answer(MHD_Connection* connection, modded_request* mr);
387+
388+
// Sub-helpers carved out of finalize_answer to keep each function
389+
// under the cyclomatic-complexity bar. The orchestrator delegates,
390+
// in order, to the websocket-upgrade probe, the resource lookup,
391+
// the auth short-circuit, the dispatch path, and the response
392+
// materialiser.
393+
std::optional<MHD_Result> try_handle_websocket_upgrade(MHD_Connection* connection,
394+
modded_request* mr);
395+
#ifdef HAVE_WEBSOCKET
396+
// Returns the validated Sec-WebSocket-Key on a well-formed RFC 6455
397+
// handshake; nullopt if any required header is missing or malformed.
398+
std::optional<const char*> validate_websocket_handshake(MHD_Connection* connection);
399+
400+
// Finish the upgrade once the handshake is validated. Returns the
401+
// MHD_queue_response result if the upgrade was queued; nullopt if
402+
// no handler is registered at this URL or the upgrade response
403+
// could not be created (caller falls through to normal dispatch).
404+
std::optional<MHD_Result> complete_websocket_upgrade(MHD_Connection* connection,
405+
modded_request* mr,
406+
const char* ws_key);
407+
#endif // HAVE_WEBSOCKET
408+
409+
// Carry the data the regex+cache lookup path produces back to the
410+
// caller. matched_endpoint is needed both to extract URL parameters
411+
// and to store the entry in the LRU cache.
412+
struct regex_route_lookup {
413+
std::shared_ptr<::httpserver::http_resource> hrm;
414+
std::vector<std::string> url_pars;
415+
std::vector<int> chunks;
416+
};
417+
418+
// Locate the resource serving @p mr. Returns true and sets @p hrm
419+
// on hit (also populates URL parameters on @p mr for regex-route
420+
// hits); false otherwise. Takes a shared lock on
421+
// registered_resources_mutex internally.
422+
bool resolve_resource_for_request(modded_request* mr,
423+
std::shared_ptr<::httpserver::http_resource>& hrm);
424+
425+
// LRU cache hit path: returns the cached match (hrm + url_pars +
426+
// chunks) and promotes the entry to the front of the list. Caller
427+
// must hold registered_resources_mutex (shared).
428+
std::optional<regex_route_lookup>
429+
lookup_route_cache(const std::string& key);
430+
431+
// Linear regex scan with longest-match-wins tie-breaking. Returns
432+
// the matched endpoint + resource on hit. Caller must hold
433+
// registered_resources_mutex (shared).
434+
struct regex_route_scan_hit {
435+
detail::http_endpoint endpoint;
436+
std::shared_ptr<::httpserver::http_resource> hrm;
437+
};
438+
std::optional<regex_route_scan_hit>
439+
scan_regex_routes(const detail::http_endpoint& target);
440+
441+
// Insert a (key -> matched_endpoint, hrm) entry at the front of
442+
// the LRU and evict the oldest entry if the cache is full. Caller
443+
// must hold registered_resources_mutex (shared).
444+
void store_route_cache(const std::string& key,
445+
const detail::http_endpoint& matched,
446+
std::shared_ptr<::httpserver::http_resource> hrm);
447+
448+
// Walk url_pars/chunks parallel arrays and set each named parameter
449+
// on the request, guarding against an out-of-range chunk index.
450+
void apply_extracted_params(modded_request* mr,
451+
const detail::http_endpoint& target,
452+
const std::vector<std::string>& url_pars,
453+
const std::vector<int>& chunks);
454+
455+
// Run the centralized auth handler if one is configured. Returns
456+
// true if the handler produced a response (dispatch should
457+
// short-circuit and the caller must skip resource rendering);
458+
// false to continue with normal dispatch.
459+
bool apply_auth_short_circuit(modded_request* mr);
460+
461+
// Invoke the resource handler bound to @p mr, populating
462+
// mr->response_. On is_allowed=false, queues a 405 with an Allow
463+
// header. On handler-throw, routes through the safe internal-error
464+
// path (TASK-031 / DR-009).
465+
void dispatch_resource_handler(modded_request* mr,
466+
const std::shared_ptr<::httpserver::http_resource>& hrm);
467+
468+
// Serialize an allowed-method set into the comma-separated value
469+
// expected by the HTTP `Allow:` header. Enum-declaration order
470+
// (TASK-021): GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE,
471+
// PATCH.
472+
std::string serialize_allow_methods(method_set allowed) const;
473+
474+
// Final stage of finalize_answer: build an MHD_Response from
475+
// mr->response_, decorate it, queue it on the connection. Handles
476+
// the belt-and-suspenders fallback when get_raw_response_with_fallback
477+
// itself fails to produce a response.
478+
MHD_Result materialize_and_queue_response(MHD_Connection* connection,
479+
modded_request* mr);
480+
386481
MHD_Result complete_request(MHD_Connection* connection, modded_request* mr,
387482
const char* version, const char* method);
388483
struct MHD_Response* get_raw_response_with_fallback(modded_request* mr);

0 commit comments

Comments
 (0)