Skip to content

Commit 60b4c64

Browse files
etrclaude
andcommitted
webserver: decompose start() under the CCN 10 bar (51 -> 8)
webserver::start() was a 217-line monolith building an MHD option array, composing a daemon-flag bitmask, and invoking the daemon. CCN 51 -- by far the worst offender in the codebase. The MHD option-array build and the daemon-flag composition both live on detail::webserver_impl now (the PIMPL already friends webserver and includes <microhttpd.h>), split along logical groupings: build_mhd_option_array -- orchestrator, appends sub-groups + END add_base_mhd_options callbacks, thread/connection/memory limits add_tls_mhd_options HTTPS cert + DAUTH random add_gnutls_mhd_options GNUTLS cred type, PSK, SNI add_extended_mhd_options backlog, reuse, increment, fastopen, sigpipe, ALPN, discipline add_https_extra_options dhparams, key password, priorities append compose_start_flags -- orchestrator compose_transport_flags SSL/IPv6/dual-stack compose_runtime_flags debug/pedantic/deferred/turbo/... start() now reads as: pre-condition -> build options -> compose flags -> MHD_start_daemon -> handle blocking. CCN 8. The local `gen` struct-with-operator() is replaced by a file-scope `make_option` helper in namespace detail so every builder pushes options uniformly. Behavioural notes: * Reordering: add_https_extra_options runs after add_extended_mhd_options. In v1 order the HTTPS-extra trio (dhparams/key_password/priorities_append) was interleaved with the extended options. MHD treats MHD_OPTION_ARRAY as a set keyed by option tag, so the relative ordering among independent value-setters is not observable. * The THREAD_PER_CONNECTION precondition stays in start() so it remains visible at the entry point of the public API. * MHD_OPTION_END is appended by build_mhd_option_array and the bind_address branch still passes a trailing MHD_OPTION_SOCK_ADDR. Verified locally: full `make check` (48/48 pass) plus all check-local invariants (headers, hygiene, install-layout, examples, readme, release-notes, doxygen). scripts/check-complexity.sh CCN_MAX ratcheted 52 -> 47 (the new worst offender is webserver_impl::finalize_answer at CCN 46). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b176669 commit 60b4c64

3 files changed

Lines changed: 160 additions & 139 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:-52}"
30+
CCN_MAX="${CCN_MAX:-47}"
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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,21 @@ class webserver_impl {
360360
bool should_skip_auth(const std::string& path) const;
361361
void invalidate_route_cache();
362362

363+
// Helpers for webserver::start(). Each appends a logical subset of
364+
// libmicrohttpd's option array, or composes a logical subset of the
365+
// daemon start-flag bitmask, reading the const config bag from
366+
// `parent`. Split for readability and to keep each function under
367+
// the project's cyclomatic-complexity bar.
368+
void build_mhd_option_array(std::vector<MHD_OptionItem>& iov) const;
369+
void add_base_mhd_options(std::vector<MHD_OptionItem>& iov) const;
370+
void add_tls_mhd_options(std::vector<MHD_OptionItem>& iov) const;
371+
void add_gnutls_mhd_options(std::vector<MHD_OptionItem>& iov) const;
372+
void add_extended_mhd_options(std::vector<MHD_OptionItem>& iov) const;
373+
void add_https_extra_options(std::vector<MHD_OptionItem>& iov) const;
374+
int compose_start_flags() const;
375+
int compose_transport_flags() const;
376+
int compose_runtime_flags() const;
377+
363378
MHD_Result requests_answer_first_step(MHD_Connection* connection, modded_request* mr);
364379
MHD_Result requests_answer_second_step(MHD_Connection* connection,
365380
const char* method, const char* version, const char* upload_data,

src/webserver.cpp

Lines changed: 144 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -763,193 +763,201 @@ void webserver::unregister_ws_resource(const std::string& resource) {
763763
#endif
764764
}
765765

766-
bool webserver::start(bool blocking) {
767-
struct {
768-
MHD_OptionItem operator ()(enum MHD_OPTION opt, intptr_t val, void *ptr = nullptr) {
769-
MHD_OptionItem x = {opt, val, ptr};
770-
return x;
771-
}
772-
} gen;
773-
vector<struct MHD_OptionItem> iov;
766+
namespace detail {
774767

775-
iov.push_back(gen(MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) &detail::webserver_impl::request_completed, nullptr));
768+
// Wrap MHD_OptionItem aggregate-init so each push reads uniformly
769+
// across the option-array builders below. Replaces the local
770+
// struct-with-operator() that used to live inside webserver::start().
771+
static MHD_OptionItem make_option(enum MHD_OPTION opt, intptr_t val,
772+
void* ptr = nullptr) {
773+
MHD_OptionItem x = {opt, val, ptr};
774+
return x;
775+
}
776+
777+
void webserver_impl::add_base_mhd_options(std::vector<MHD_OptionItem>& iov) const {
778+
iov.push_back(make_option(MHD_OPTION_NOTIFY_COMPLETED,
779+
(intptr_t) &webserver_impl::request_completed, nullptr));
776780
// TASK-016: per-connection arena anchor. MHD_OPTION_NOTIFY_CONNECTION
777781
// hands us a per-connection void** (socket_context) on STARTED, where
778782
// we new a detail::connection_state (which owns the arena), and on
779783
// CLOSED, where we delete it. This makes the arena's lifetime equal
780784
// to the MHD_Connection's lifetime; request_completed reuses the
781785
// arena across keep-alive request boundaries via arena_.release().
782-
iov.push_back(gen(MHD_OPTION_NOTIFY_CONNECTION, (intptr_t) &detail::webserver_impl::connection_notify, nullptr));
783-
iov.push_back(gen(MHD_OPTION_URI_LOG_CALLBACK, (intptr_t) &detail::webserver_impl::uri_log, this));
784-
iov.push_back(gen(MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) &detail::webserver_impl::error_log, this));
785-
iov.push_back(gen(MHD_OPTION_UNESCAPE_CALLBACK, (intptr_t) &detail::webserver_impl::unescaper_func, this));
786-
iov.push_back(gen(MHD_OPTION_CONNECTION_TIMEOUT, connection_timeout));
787-
if (impl_->bind_socket != 0) {
788-
iov.push_back(gen(MHD_OPTION_LISTEN_SOCKET, impl_->bind_socket));
789-
}
790-
791-
if (start_method == http_utils::THREAD_PER_CONNECTION && (max_threads != 0 || max_thread_stack_size != 0)) {
792-
throw std::invalid_argument("Cannot specify maximum number of threads when using a thread per connection");
786+
iov.push_back(make_option(MHD_OPTION_NOTIFY_CONNECTION,
787+
(intptr_t) &webserver_impl::connection_notify, nullptr));
788+
iov.push_back(make_option(MHD_OPTION_URI_LOG_CALLBACK,
789+
(intptr_t) &webserver_impl::uri_log, parent));
790+
iov.push_back(make_option(MHD_OPTION_EXTERNAL_LOGGER,
791+
(intptr_t) &webserver_impl::error_log, parent));
792+
iov.push_back(make_option(MHD_OPTION_UNESCAPE_CALLBACK,
793+
(intptr_t) &webserver_impl::unescaper_func, parent));
794+
iov.push_back(make_option(MHD_OPTION_CONNECTION_TIMEOUT, parent->connection_timeout));
795+
if (bind_socket != 0) {
796+
iov.push_back(make_option(MHD_OPTION_LISTEN_SOCKET, bind_socket));
793797
}
794-
795-
if (max_threads != 0) {
796-
iov.push_back(gen(MHD_OPTION_THREAD_POOL_SIZE, max_threads));
798+
if (parent->max_threads != 0) {
799+
iov.push_back(make_option(MHD_OPTION_THREAD_POOL_SIZE, parent->max_threads));
797800
}
798-
799-
if (max_connections != 0) {
800-
iov.push_back(gen(MHD_OPTION_CONNECTION_LIMIT, max_connections));
801+
if (parent->max_connections != 0) {
802+
iov.push_back(make_option(MHD_OPTION_CONNECTION_LIMIT, parent->max_connections));
801803
}
802-
803-
if (memory_limit != 0) {
804-
iov.push_back(gen(MHD_OPTION_CONNECTION_MEMORY_LIMIT, memory_limit));
804+
if (parent->memory_limit != 0) {
805+
iov.push_back(make_option(MHD_OPTION_CONNECTION_MEMORY_LIMIT, parent->memory_limit));
805806
}
806-
807-
if (per_IP_connection_limit != 0) {
808-
iov.push_back(gen(MHD_OPTION_PER_IP_CONNECTION_LIMIT, per_IP_connection_limit));
807+
if (parent->per_IP_connection_limit != 0) {
808+
iov.push_back(make_option(MHD_OPTION_PER_IP_CONNECTION_LIMIT, parent->per_IP_connection_limit));
809809
}
810-
811-
if (max_thread_stack_size != 0) {
812-
iov.push_back(gen(MHD_OPTION_THREAD_STACK_SIZE, max_thread_stack_size));
810+
if (parent->max_thread_stack_size != 0) {
811+
iov.push_back(make_option(MHD_OPTION_THREAD_STACK_SIZE, parent->max_thread_stack_size));
813812
}
814-
815813
#ifdef HAVE_DAUTH
816-
if (nonce_nc_size != 0) {
817-
iov.push_back(gen(MHD_OPTION_NONCE_NC_SIZE, nonce_nc_size));
814+
if (parent->nonce_nc_size != 0) {
815+
iov.push_back(make_option(MHD_OPTION_NONCE_NC_SIZE, parent->nonce_nc_size));
818816
}
819817
#endif // HAVE_DAUTH
818+
}
820819

821-
if (use_ssl) {
822-
// Need for const_cast to respect MHD interface that needs a void*
823-
iov.push_back(gen(MHD_OPTION_HTTPS_MEM_KEY, 0, reinterpret_cast<void*>(const_cast<char*>(https_mem_key.c_str()))));
824-
iov.push_back(gen(MHD_OPTION_HTTPS_MEM_CERT, 0, reinterpret_cast<void*>(const_cast<char*>(https_mem_cert.c_str()))));
825-
826-
if (!https_mem_trust.empty()) {
827-
iov.push_back(gen(MHD_OPTION_HTTPS_MEM_TRUST, 0, reinterpret_cast<void*>(const_cast<char*>(https_mem_trust.c_str()))));
820+
void webserver_impl::add_tls_mhd_options(std::vector<MHD_OptionItem>& iov) const {
821+
if (parent->use_ssl) {
822+
// const_cast respects the MHD C interface, which takes a void*
823+
// even though the data is read-only at the library boundary.
824+
iov.push_back(make_option(MHD_OPTION_HTTPS_MEM_KEY, 0,
825+
reinterpret_cast<void*>(const_cast<char*>(parent->https_mem_key.c_str()))));
826+
iov.push_back(make_option(MHD_OPTION_HTTPS_MEM_CERT, 0,
827+
reinterpret_cast<void*>(const_cast<char*>(parent->https_mem_cert.c_str()))));
828+
if (!parent->https_mem_trust.empty()) {
829+
iov.push_back(make_option(MHD_OPTION_HTTPS_MEM_TRUST, 0,
830+
reinterpret_cast<void*>(const_cast<char*>(parent->https_mem_trust.c_str()))));
828831
}
829-
830-
if (!https_priorities.empty()) {
831-
iov.push_back(gen(MHD_OPTION_HTTPS_PRIORITIES, 0, reinterpret_cast<void*>(const_cast<char*>(https_priorities.c_str()))));
832+
if (!parent->https_priorities.empty()) {
833+
iov.push_back(make_option(MHD_OPTION_HTTPS_PRIORITIES, 0,
834+
reinterpret_cast<void*>(const_cast<char*>(parent->https_priorities.c_str()))));
832835
}
833836
}
834-
835837
#ifdef HAVE_DAUTH
836-
if (digest_auth_random != "") {
837-
// Need for const_cast to respect MHD interface that needs a char*
838-
iov.push_back(gen(MHD_OPTION_DIGEST_AUTH_RANDOM, digest_auth_random.size(), const_cast<char*>(digest_auth_random.c_str())));
838+
if (parent->digest_auth_random != "") {
839+
iov.push_back(make_option(MHD_OPTION_DIGEST_AUTH_RANDOM,
840+
parent->digest_auth_random.size(),
841+
const_cast<char*>(parent->digest_auth_random.c_str())));
839842
}
840843
#endif // HAVE_DAUTH
844+
}
841845

846+
void webserver_impl::add_gnutls_mhd_options(std::vector<MHD_OptionItem>& iov) const {
842847
#ifdef HAVE_GNUTLS
843-
if (cred_type != http_utils::NONE) {
844-
iov.push_back(gen(MHD_OPTION_HTTPS_CRED_TYPE, cred_type));
848+
if (parent->cred_type != http_utils::NONE) {
849+
iov.push_back(make_option(MHD_OPTION_HTTPS_CRED_TYPE, parent->cred_type));
845850
}
846-
847-
if (psk_cred_handler != nullptr && use_ssl) {
848-
iov.push_back(gen(MHD_OPTION_GNUTLS_PSK_CRED_HANDLER,
849-
(intptr_t)&detail::webserver_impl::psk_cred_handler_func, this));
851+
if (parent->psk_cred_handler != nullptr && parent->use_ssl) {
852+
iov.push_back(make_option(MHD_OPTION_GNUTLS_PSK_CRED_HANDLER,
853+
(intptr_t)&webserver_impl::psk_cred_handler_func, parent));
850854
}
851-
852855
#ifdef MHD_OPTION_HTTPS_CERT_CALLBACK
853-
if (sni_callback != nullptr && use_ssl) {
854-
iov.push_back(gen(MHD_OPTION_HTTPS_CERT_CALLBACK,
855-
(intptr_t)&detail::webserver_impl::sni_cert_callback_func, this));
856+
if (parent->sni_callback != nullptr && parent->use_ssl) {
857+
iov.push_back(make_option(MHD_OPTION_HTTPS_CERT_CALLBACK,
858+
(intptr_t)&webserver_impl::sni_cert_callback_func, parent));
856859
}
857860
#endif // MHD_OPTION_HTTPS_CERT_CALLBACK
861+
#else // HAVE_GNUTLS
862+
(void)iov;
858863
#endif // HAVE_GNUTLS
864+
}
859865

860-
if (listen_backlog > 0) {
861-
iov.push_back(gen(MHD_OPTION_LISTEN_BACKLOG_SIZE, listen_backlog));
862-
}
863-
864-
if (address_reuse != 0) {
865-
iov.push_back(gen(MHD_OPTION_LISTENING_ADDRESS_REUSE, address_reuse));
866-
}
867-
868-
if (connection_memory_increment > 0) {
869-
iov.push_back(gen(MHD_OPTION_CONNECTION_MEMORY_INCREMENT, connection_memory_increment));
866+
void webserver_impl::add_extended_mhd_options(std::vector<MHD_OptionItem>& iov) const {
867+
if (parent->listen_backlog > 0) {
868+
iov.push_back(make_option(MHD_OPTION_LISTEN_BACKLOG_SIZE, parent->listen_backlog));
870869
}
871-
872-
if (tcp_fastopen_queue_size > 0) {
873-
iov.push_back(gen(MHD_OPTION_TCP_FASTOPEN_QUEUE_SIZE, tcp_fastopen_queue_size));
870+
if (parent->address_reuse != 0) {
871+
iov.push_back(make_option(MHD_OPTION_LISTENING_ADDRESS_REUSE, parent->address_reuse));
874872
}
875-
876-
if (sigpipe_handled_by_app) {
877-
iov.push_back(gen(MHD_OPTION_SIGPIPE_HANDLED_BY_APP, 1));
873+
if (parent->connection_memory_increment > 0) {
874+
iov.push_back(make_option(MHD_OPTION_CONNECTION_MEMORY_INCREMENT,
875+
parent->connection_memory_increment));
878876
}
879-
880-
if (!https_mem_dhparams.empty()) {
881-
iov.push_back(gen(MHD_OPTION_HTTPS_MEM_DHPARAMS, 0, const_cast<char*>(https_mem_dhparams.c_str())));
877+
if (parent->tcp_fastopen_queue_size > 0) {
878+
iov.push_back(make_option(MHD_OPTION_TCP_FASTOPEN_QUEUE_SIZE,
879+
parent->tcp_fastopen_queue_size));
882880
}
883-
884-
if (!https_key_password.empty()) {
885-
iov.push_back(gen(MHD_OPTION_HTTPS_KEY_PASSWORD, 0, const_cast<char*>(https_key_password.c_str())));
881+
if (parent->sigpipe_handled_by_app) {
882+
iov.push_back(make_option(MHD_OPTION_SIGPIPE_HANDLED_BY_APP, 1));
886883
}
887-
888-
if (!https_priorities_append.empty()) {
889-
iov.push_back(gen(MHD_OPTION_HTTPS_PRIORITIES_APPEND, 0, const_cast<char*>(https_priorities_append.c_str())));
884+
if (parent->no_alpn) {
885+
iov.push_back(make_option(MHD_OPTION_TLS_NO_ALPN, 1));
890886
}
891-
892-
if (no_alpn) {
893-
iov.push_back(gen(MHD_OPTION_TLS_NO_ALPN, 1));
887+
if (parent->client_discipline_level >= 0) {
888+
iov.push_back(make_option(MHD_OPTION_CLIENT_DISCIPLINE_LVL, parent->client_discipline_level));
894889
}
890+
}
895891

896-
if (client_discipline_level >= 0) {
897-
iov.push_back(gen(MHD_OPTION_CLIENT_DISCIPLINE_LVL, client_discipline_level));
892+
void webserver_impl::add_https_extra_options(std::vector<MHD_OptionItem>& iov) const {
893+
if (!parent->https_mem_dhparams.empty()) {
894+
iov.push_back(make_option(MHD_OPTION_HTTPS_MEM_DHPARAMS, 0,
895+
const_cast<char*>(parent->https_mem_dhparams.c_str())));
898896
}
899-
900-
iov.push_back(gen(MHD_OPTION_END, 0, nullptr));
901-
902-
int start_conf = start_method;
903-
904-
if (use_ssl) {
905-
start_conf |= MHD_USE_SSL;
897+
if (!parent->https_key_password.empty()) {
898+
iov.push_back(make_option(MHD_OPTION_HTTPS_KEY_PASSWORD, 0,
899+
const_cast<char*>(parent->https_key_password.c_str())));
906900
}
907-
908-
if (use_ipv6) {
909-
start_conf |= MHD_USE_IPv6;
901+
if (!parent->https_priorities_append.empty()) {
902+
iov.push_back(make_option(MHD_OPTION_HTTPS_PRIORITIES_APPEND, 0,
903+
const_cast<char*>(parent->https_priorities_append.c_str())));
910904
}
905+
}
911906

912-
if (use_dual_stack) {
913-
start_conf |= MHD_USE_DUAL_STACK;
914-
}
907+
void webserver_impl::build_mhd_option_array(std::vector<MHD_OptionItem>& iov) const {
908+
add_base_mhd_options(iov);
909+
add_tls_mhd_options(iov);
910+
add_gnutls_mhd_options(iov);
911+
add_extended_mhd_options(iov);
912+
add_https_extra_options(iov);
913+
iov.push_back(make_option(MHD_OPTION_END, 0, nullptr));
914+
}
915915

916-
if (debug) {
917-
start_conf |= MHD_USE_DEBUG;
918-
}
919-
if (pedantic) {
920-
start_conf |= MHD_USE_PEDANTIC_CHECKS;
921-
}
916+
int webserver_impl::compose_transport_flags() const {
917+
int flags = 0;
918+
if (parent->use_ssl) flags |= MHD_USE_SSL;
919+
if (parent->use_ipv6) flags |= MHD_USE_IPv6;
920+
if (parent->use_dual_stack) flags |= MHD_USE_DUAL_STACK;
921+
return flags;
922+
}
922923

923-
if (deferred_enabled) {
924-
start_conf |= MHD_USE_SUSPEND_RESUME;
925-
}
924+
int webserver_impl::compose_runtime_flags() const {
925+
int flags = 0;
926+
if (parent->debug) flags |= MHD_USE_DEBUG;
927+
if (parent->pedantic) flags |= MHD_USE_PEDANTIC_CHECKS;
928+
if (parent->deferred_enabled) flags |= MHD_USE_SUSPEND_RESUME;
929+
if (parent->no_listen_socket) flags |= MHD_USE_NO_LISTEN_SOCKET;
930+
if (parent->no_thread_safety) flags |= MHD_USE_NO_THREAD_SAFETY;
931+
if (parent->turbo) flags |= MHD_USE_TURBO;
932+
if (parent->suppress_date_header) flags |= MHD_USE_SUPPRESS_DATE_NO_CLOCK;
933+
#ifdef HAVE_WEBSOCKET
934+
if (!registered_ws_handlers.empty()) flags |= MHD_ALLOW_UPGRADE;
935+
#endif // HAVE_WEBSOCKET
936+
return flags;
937+
}
926938

939+
int webserver_impl::compose_start_flags() const {
940+
int flags = parent->start_method;
941+
flags |= compose_transport_flags();
942+
flags |= compose_runtime_flags();
927943
#ifdef USE_FASTOPEN
928-
start_conf |= MHD_USE_TCP_FASTOPEN;
944+
flags |= MHD_USE_TCP_FASTOPEN;
929945
#endif
946+
return flags;
947+
}
930948

931-
if (no_listen_socket) {
932-
start_conf |= MHD_USE_NO_LISTEN_SOCKET;
933-
}
934-
935-
if (no_thread_safety) {
936-
start_conf |= MHD_USE_NO_THREAD_SAFETY;
937-
}
938-
939-
if (turbo) {
940-
start_conf |= MHD_USE_TURBO;
941-
}
942-
943-
if (suppress_date_header) {
944-
start_conf |= MHD_USE_SUPPRESS_DATE_NO_CLOCK;
945-
}
949+
} // namespace detail
946950

947-
#ifdef HAVE_WEBSOCKET
948-
if (!impl_->registered_ws_handlers.empty()) {
949-
start_conf |= MHD_ALLOW_UPGRADE;
951+
bool webserver::start(bool blocking) {
952+
if (start_method == http_utils::THREAD_PER_CONNECTION
953+
&& (max_threads != 0 || max_thread_stack_size != 0)) {
954+
throw std::invalid_argument(
955+
"Cannot specify maximum number of threads when using a thread per connection");
950956
}
951-
#endif // HAVE_WEBSOCKET
952957

958+
vector<struct MHD_OptionItem> iov;
959+
impl_->build_mhd_option_array(iov);
960+
const int start_conf = impl_->compose_start_flags();
953961

954962
impl_->daemon = nullptr;
955963
if (bind_address == nullptr) {
@@ -966,8 +974,6 @@ bool webserver::start(bool blocking) {
966974
throw std::invalid_argument("Unable to connect daemon to port: " + std::to_string(port));
967975
}
968976

969-
bool value_onclose = false;
970-
971977
impl_->running = true;
972978

973979
if (blocking) {
@@ -976,9 +982,9 @@ bool webserver::start(bool blocking) {
976982
pthread_cond_wait(&impl_->mutexcond, &impl_->mutexwait);
977983
}
978984
pthread_mutex_unlock(&impl_->mutexwait);
979-
value_onclose = true;
985+
return true;
980986
}
981-
return value_onclose;
987+
return false;
982988
}
983989

984990
bool webserver::is_running() {

0 commit comments

Comments
 (0)