Skip to content

Commit 0d698e1

Browse files
etrclaude
andcommitted
refactor: consolidate the ~65-field webserver config into one struct
The full builder configuration previously existed as ~65 parallel data members duplicated across three places: the create_webserver builder's _x fields, an identical const-member block on webserver, and a ~77-line member-initialiser copy in webserver.cpp. Adding one option meant editing 4+ sites. Introduce `struct webserver_config` (in create_webserver.hpp) holding every builder input. The builder owns one and its setters mutate it; the webserver constructor copies it wholesale (`config(params._config)`). Adding an option is now one struct field + one builder setter. Mechanical consequences: - create_webserver setters: _field -> _config.field. - webserver: ~65 const members -> `const webserver_config config;` plus the one derived member (auth_skip_paths_normalized), which is computed at construction and so stays separate. - webserver.cpp ctor: 77-line init list -> config copy + derived field. - all read sites: parent->field / ws->field / dws->field -> ...->config.field; webserver's own methods: bare field -> config.field. - create_webserver::basic_auth_default / digest_auth_default statics -> detail::default_basic_auth_enabled / default_digest_auth_enabled free functions (needed before create_webserver is defined). Net -115 lines. No behavior change: build green, full suite 109/109. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2e73dcb commit 0d698e1

17 files changed

Lines changed: 335 additions & 449 deletions

src/create_webserver.cpp

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,19 @@ namespace httpserver {
4343
// initializer in the header) so the public header carries no
4444
// #ifdef HAVE_BAUTH. The library was compiled with the right
4545
// HAVE_BAUTH state; the consumer TU's HAVE_BAUTH is irrelevant.
46-
bool create_webserver::basic_auth_default() noexcept {
46+
bool detail::default_basic_auth_enabled() noexcept {
4747
#ifdef HAVE_BAUTH
4848
return true;
4949
#else
5050
return false;
5151
#endif
5252
}
5353

54-
// Same pattern as basic_auth_default(). Returns true
54+
// Same pattern as default_basic_auth_enabled(). Returns true
5555
// on HAVE_DAUTH-on builds (preserving historical behaviour) and false
5656
// on HAVE_DAUTH-off builds so an unmodified builder doesn't trip the
5757
// feature_unavailable guard in webserver::webserver().
58-
bool create_webserver::digest_auth_default() noexcept {
58+
bool detail::default_digest_auth_enabled() noexcept {
5959
#ifdef HAVE_DAUTH
6060
return true;
6161
#else
@@ -64,25 +64,25 @@ bool create_webserver::digest_auth_default() noexcept {
6464
}
6565

6666
create_webserver& create_webserver::bind_address(const std::string& ip) {
67-
_bind_address_storage = std::make_shared<struct sockaddr_storage>();
68-
std::memset(_bind_address_storage.get(), 0, sizeof(struct sockaddr_storage));
67+
_config.bind_address_storage = std::make_shared<struct sockaddr_storage>();
68+
std::memset(_config.bind_address_storage.get(), 0, sizeof(struct sockaddr_storage));
6969

7070
// Try IPv4 first
71-
auto* addr4 = reinterpret_cast<struct sockaddr_in*>(_bind_address_storage.get());
71+
auto* addr4 = reinterpret_cast<struct sockaddr_in*>(_config.bind_address_storage.get());
7272
if (inet_pton(AF_INET, ip.c_str(), &(addr4->sin_addr)) == 1) {
7373
addr4->sin_family = AF_INET;
74-
addr4->sin_port = htons(_port);
75-
_bind_address = reinterpret_cast<const struct sockaddr*>(_bind_address_storage.get());
74+
addr4->sin_port = htons(_config.port);
75+
_config.bind_address = reinterpret_cast<const struct sockaddr*>(_config.bind_address_storage.get());
7676
return *this;
7777
}
7878

7979
// Try IPv6
80-
auto* addr6 = reinterpret_cast<struct sockaddr_in6*>(_bind_address_storage.get());
80+
auto* addr6 = reinterpret_cast<struct sockaddr_in6*>(_config.bind_address_storage.get());
8181
if (inet_pton(AF_INET6, ip.c_str(), &(addr6->sin6_addr)) == 1) {
8282
addr6->sin6_family = AF_INET6;
83-
addr6->sin6_port = htons(_port);
84-
_bind_address = reinterpret_cast<const struct sockaddr*>(_bind_address_storage.get());
85-
_use_ipv6 = true;
83+
addr6->sin6_port = htons(_config.port);
84+
_config.bind_address = reinterpret_cast<const struct sockaddr*>(_config.bind_address_storage.get());
85+
_config.use_ipv6 = true;
8686
return *this;
8787
}
8888

src/detail/webserver_aliases.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ void install_log_access_alias(
198198
// 404s) should add a catch-all fallback route or register a
199199
// not_found_handler that applies equivalent auth logic.
200200
void webserver::install_auth_alias_() {
201-
if (auth_handler == nullptr) return;
201+
if (config.auth_handler == nullptr) return;
202202
// Capture both the webserver* (for auth_handler callable) and the
203203
// webserver_impl* (for should_skip_auth, which normalises the path
204204
// before comparing against auth_skip_paths).
@@ -244,7 +244,7 @@ void webserver::install_auth_alias_() {
244244
std::optional<http_response> rejection;
245245
bool auth_threw = false;
246246
try {
247-
rejection = ws_ptr->auth_handler(*ctx.request);
247+
rejection = ws_ptr->config.auth_handler(*ctx.request);
248248
} catch (const std::exception& e) {
249249
impl_ptr->log_dispatch_error(
250250
std::string("auth_handler threw: ").append(e.what())
@@ -285,7 +285,7 @@ void webserver::install_auth_alias_() {
285285
// synthesises a default 405 body), appends the Allow header, and
286286
// returns hook_action::respond_with(...) to short-circuit dispatch.
287287
void webserver::install_method_not_allowed_alias_() {
288-
if (method_not_allowed_handler == nullptr) return;
288+
if (config.method_not_allowed_handler == nullptr) return;
289289
webserver* ws_ptr = this;
290290
add_hook(hook_phase::before_handler,
291291
std::function<hook_action(before_handler_ctx&)>(
@@ -299,7 +299,7 @@ void webserver::install_method_not_allowed_alias_() {
299299
}
300300
// Method not allowed: build the response.
301301
http_response resp =
302-
ws_ptr->method_not_allowed_handler(*ctx.request);
302+
ws_ptr->config.method_not_allowed_handler(*ctx.request);
303303
// Append Allow header from the matched route descriptor.
304304
if (ctx.matched) {
305305
std::string allow_value =
@@ -337,7 +337,7 @@ void webserver::install_method_not_allowed_alias_() {
337337
// body shape is pinned by hooks_not_found_alias_test (default and
338338
// custom branches) and by basic.cpp:custom_not_found_handler.
339339
void webserver::install_not_found_alias_() {
340-
if (not_found_handler == nullptr) return;
340+
if (config.not_found_handler == nullptr) return;
341341
add_hook(hook_phase::route_resolved,
342342
std::function<void(const route_resolved_ctx&)>(
343343
[](const route_resolved_ctx&) {
@@ -390,7 +390,7 @@ void webserver::install_default_alias_hooks_() {
390390
// thereafter. Runtime extension of the
391391
// handler_exception phase is via add_hook(); the alias slot is not
392392
// user-mutable post-construction.
393-
install_internal_error_alias(impl_.get(), internal_error_handler);
393+
install_internal_error_alias(impl_.get(), config.internal_error_handler);
394394

395395
// ----------------------------------------------------------------
396396
// log_access -> response_sent alias slot.
@@ -405,7 +405,7 @@ void webserver::install_default_alias_hooks_() {
405405
//
406406
// Format: '<path> METHOD: <method>' -- mirrors v1 access_log to keep
407407
// basic.cpp log_access_callback test passing without modification.
408-
install_log_access_alias(impl_.get(), log_access);
408+
install_log_access_alias(impl_.get(), config.log_access);
409409
}
410410

411411
} // namespace httpserver

src/detail/webserver_body_pipeline.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -199,11 +199,11 @@ MHD_Result webserver_impl::requests_answer_first_step(MHD_Connection* connection
199199
// MHD_CONNECTION_INFO_SOCKET_CONTEXT, then allocates the http_request_impl
200200
// from that arena. The arena plumbing is therefore implicit at this call
201201
// site but explicit within the constructor.
202-
mr->request.reset(new http_request(connection, parent->unescaper));
203-
mr->request->set_file_cleanup_callback(parent->file_cleanup_callback);
202+
mr->request.reset(new http_request(connection, parent->config.unescaper));
203+
mr->request->set_file_cleanup_callback(parent->config.file_cleanup_callback);
204204
// Propagate the redaction-bypass bit so operator<< honours
205205
// the builder opt-in for every request the webserver dispatches.
206-
mr->request->set_expose_credentials_in_logs(parent->expose_credentials_in_logs);
206+
mr->request->set_expose_credentials_in_logs(parent->config.expose_credentials_in_logs);
207207

208208
// request_received hook. Fires after the http_request is
209209
// populated but before any body bytes are read (and before any
@@ -229,10 +229,10 @@ MHD_Result webserver_impl::requests_answer_first_step(MHD_Connection* connection
229229
return MHD_YES;
230230
}
231231

232-
mr->request->set_content_size_limit(parent->content_size_limit);
232+
mr->request->set_content_size_limit(parent->config.content_size_limit);
233233
const char *encoding = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, http_utils::http_header_content_type);
234234

235-
if (parent->post_process_enabled &&
235+
if (parent->config.post_process_enabled &&
236236
(nullptr != encoding &&
237237
((0 == strncasecmp(http_utils::http_post_encoding_form_urlencoded, encoding, strlen(http_utils::http_post_encoding_form_urlencoded))) ||
238238
(0 == strncasecmp(http_utils::http_post_encoding_multipart_formdata, encoding, strlen(http_utils::http_post_encoding_multipart_formdata)))))) {
@@ -293,7 +293,7 @@ MHD_Result webserver_impl::requests_answer_second_step(MHD_Connection* connectio
293293
// multipart/form-data and application/x-www-form-urlencoded
294294
// all other content (which is indicated by mr-pp == nullptr)
295295
// has to be put to the content even if put_processed_data_to_content is set to false
296-
if (mr->pp == nullptr || parent->put_processed_data_to_content) {
296+
if (mr->pp == nullptr || parent->config.put_processed_data_to_content) {
297297
mr->request->grow_content(upload_data, *upload_data_size);
298298
}
299299
run_post_processor_if_attached(mr, parent, upload_data, *upload_data_size);

src/detail/webserver_callbacks.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,11 @@ int webserver_impl::psk_cred_handler_func(void* cls,
181181
*psk = nullptr;
182182
*psk_size = 0;
183183

184-
if (ws == nullptr || ws->psk_cred_handler == nullptr) {
184+
if (ws == nullptr || ws->config.psk_cred_handler == nullptr) {
185185
return -1;
186186
}
187187

188-
std::string psk_hex = ws->psk_cred_handler(std::string(username));
188+
std::string psk_hex = ws->config.psk_cred_handler(std::string(username));
189189
if (psk_hex.empty()) {
190190
return -1;
191191
}
@@ -225,7 +225,7 @@ int webserver_impl::sni_cert_callback_func(void* cls,
225225
std::ignore = connection;
226226

227227
webserver* ws = static_cast<webserver*>(cls);
228-
if (ws == nullptr || ws->sni_callback == nullptr || server_name == nullptr) {
228+
if (ws == nullptr || ws->config.sni_callback == nullptr || server_name == nullptr) {
229229
return -1;
230230
}
231231

@@ -244,7 +244,7 @@ int webserver_impl::sni_cert_callback_func(void* cls,
244244
}
245245

246246
// Call user's callback to get cert/key pair
247-
auto [cert_pem, key_pem] = ws->sni_callback(name);
247+
auto [cert_pem, key_pem] = ws->config.sni_callback(name);
248248
if (cert_pem.empty() || key_pem.empty()) {
249249
return -1; // Use default certificate
250250
}
@@ -324,7 +324,7 @@ void webserver_impl::error_log(void* cls, const char* fmt, va_list ap) {
324324
va_end(va);
325325
msg.resize(r);
326326

327-
if (dws->log_error != nullptr) dws->log_error(msg);
327+
if (dws->config.log_error != nullptr) dws->config.log_error(msg);
328328
}
329329

330330
size_t webserver_impl::unescaper_func(void * cls, struct MHD_Connection *c, char *s) {
@@ -395,13 +395,13 @@ bool webserver_impl::setup_new_upload_file_info(http::file_info& file,
395395
// otherwise sanitize the client-supplied filename) and prime the
396396
// file_info with content_type / transfer_encoding when MHD gave
397397
// them to us.
398-
if (parent->generate_random_filename_on_upload) {
398+
if (parent->config.generate_random_filename_on_upload) {
399399
file.set_file_system_file_name(
400-
http_utils::generate_random_upload_filename(parent->file_upload_dir));
400+
http_utils::generate_random_upload_filename(parent->config.file_upload_dir));
401401
} else {
402402
std::string safe_name = http_utils::sanitize_upload_filename(filename);
403403
if (safe_name.empty()) return false;
404-
file.set_file_system_file_name(parent->file_upload_dir + "/" + safe_name);
404+
file.set_file_system_file_name(parent->config.file_upload_dir + "/" + safe_name);
405405
}
406406
// Avoid appending to a leftover file from a previous request.
407407
unlink(file.get_file_system_file_name().c_str());
@@ -462,11 +462,11 @@ MHD_Result webserver_impl::post_iterator(void *cls, enum MHD_ValueKind kind,
462462
}
463463

464464
try {
465-
if (mr->ws->file_upload_target != FILE_UPLOAD_DISK_ONLY) {
465+
if (mr->ws->config.file_upload_target != FILE_UPLOAD_DISK_ONLY) {
466466
mr->request->set_arg_flat(key,
467467
std::string(mr->request->get_arg(key)) + std::string(data, size));
468468
}
469-
if (*filename != '\0' && mr->ws->file_upload_target != FILE_UPLOAD_MEMORY_ONLY) {
469+
if (*filename != '\0' && mr->ws->config.file_upload_target != FILE_UPLOAD_MEMORY_ONLY) {
470470
MHD_Result r = mr->ws->impl_->process_file_upload(
471471
mr, key, filename, content_type, transfer_encoding, data, size);
472472
if (r != MHD_YES) return r;

src/detail/webserver_callbacks_lifecycle.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ void webserver_impl::connection_notify(void* cls, struct MHD_Connection* connect
175175
// arguments_accumulator from the socket_context. 0 means
176176
// "use the compile-time defaults" -- see connection_state.hpp.
177177
if (ws != nullptr) {
178-
cs->max_args_count = ws->max_args_count;
179-
cs->max_args_bytes = ws->max_args_bytes;
178+
cs->max_args_count = ws->config.max_args_count;
179+
cs->max_args_bytes = ws->config.max_args_bytes;
180180
}
181181
*socket_context = cs;
182182
// Fire connection_opened. Zero-cost when no hook is
@@ -224,7 +224,7 @@ MHD_Result webserver_impl::policy_callback(void *cls, const struct sockaddr* add
224224
bool accepted = true;
225225
std::optional<std::string_view> reason{};
226226

227-
if (ws->ip_access_control_enabled) {
227+
if (ws->config.ip_access_control_enabled) {
228228
bool is_denied = false;
229229
bool is_allowed = false;
230230
{
@@ -234,7 +234,7 @@ MHD_Result webserver_impl::policy_callback(void *cls, const struct sockaddr* add
234234
is_allowed = impl->allow_list.count(ip_representation(addr));
235235
} // release deny/allow locks before firing the user hook
236236
std::tie(accepted, reason) =
237-
classify_decision(ws->default_policy, is_denied, is_allowed);
237+
classify_decision(ws->config.default_policy, is_denied, is_allowed);
238238
}
239239

240240
const MHD_Result decision = accepted ? MHD_YES : MHD_NO;

src/detail/webserver_dispatch.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ void webserver_impl::dispatch_resource_handler(detail::modded_request* mr,
461461
// Perf: only build the heap string
462462
// when a log_error callback is actually wired; log_dispatch_error
463463
// also checks this but the concatenation happens at the call site.
464-
if (parent->log_error) {
464+
if (parent->config.log_error) {
465465
log_dispatch_error(
466466
std::string("dispatch: handler threw std::exception: ")
467467
.append(e.what()));

src/detail/webserver_error_pages.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,16 @@ using httpserver::http::http_utils;
5050
namespace detail {
5151

5252
http_response webserver_impl::not_found_page(detail::modded_request* mr) const {
53-
if (parent->not_found_handler != nullptr) {
54-
return parent->not_found_handler(*mr->request);
53+
if (parent->config.not_found_handler != nullptr) {
54+
return parent->config.not_found_handler(*mr->request);
5555
}
5656
return http_response::string(std::string{constants::NOT_FOUND_ERROR})
5757
.with_status(http_utils::http_not_found);
5858
}
5959

6060
http_response webserver_impl::method_not_allowed_page(detail::modded_request* mr) const {
61-
if (parent->method_not_allowed_handler != nullptr) {
62-
return parent->method_not_allowed_handler(*mr->request);
61+
if (parent->config.method_not_allowed_handler != nullptr) {
62+
return parent->config.method_not_allowed_handler(*mr->request);
6363
}
6464
return http_response::string(std::string{constants::METHOD_ERROR})
6565
.with_status(http_utils::http_method_not_allowed);
@@ -79,8 +79,8 @@ http_response webserver_impl::internal_error_page(
7979
.with_status(http_utils::http_internal_server_error);
8080
}
8181
// Invoke the user handler with the originating message.
82-
if (parent->internal_error_handler != nullptr) {
83-
return parent->internal_error_handler(*mr->request, msg);
82+
if (parent->config.internal_error_handler != nullptr) {
83+
return parent->config.internal_error_handler(*mr->request, msg);
8484
}
8585
// The default body is the fixed string
8686
// "Internal Server Error" to avoid CWE-209 information disclosure of
@@ -91,7 +91,7 @@ http_response webserver_impl::internal_error_page(
9191
// verbose body (for development) must opt in via
9292
// create_webserver::expose_exception_messages(true).
9393
const auto status = http_utils::http_internal_server_error;
94-
if (parent->expose_exception_messages) {
94+
if (parent->config.expose_exception_messages) {
9595
return http_response::string(std::string{msg}).with_status(status);
9696
}
9797
return http_response::string(
@@ -100,7 +100,7 @@ http_response webserver_impl::internal_error_page(
100100
}
101101

102102
void webserver_impl::log_dispatch_error(std::string_view msg) const noexcept {
103-
if (parent->log_error == nullptr) {
103+
if (parent->config.log_error == nullptr) {
104104
return;
105105
}
106106
// msg is forwarded VERBATIM regardless
@@ -118,7 +118,7 @@ void webserver_impl::log_dispatch_error(std::string_view msg) const noexcept {
118118
// the catch. Swallow any exception it throws; we have no recovery
119119
// beyond dropping the log line.
120120
try {
121-
parent->log_error(std::string(msg));
121+
parent->config.log_error(std::string(msg));
122122
} catch (...) {
123123
// Intentionally suppressed.
124124
}

0 commit comments

Comments
 (0)