Skip to content

Commit 369c2a8

Browse files
etrclaude
andcommitted
Merge TASK-014: webserver_impl skeleton (PIMPL prep, structural only)
Move webserver's backend state (MHD_Daemon*, mutexes, ban set, connection table, route cache) into detail/webserver_impl.hpp behind a unique_ptr impl_ pointer. Public webserver.hpp no longer includes <microhttpd.h> or <pthread.h>; <sys/socket.h> residual is tracked under TASK-020. Status: In Progress — 27 unworked validation findings (3 major, 24 minor) recorded in specs/unworked_review_issues/2026-05-04_115707_task-014.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 parents ce14c7b + 1b14dd1 commit 369c2a8

16 files changed

Lines changed: 684 additions & 343 deletions

specs/tasks/M3-request/TASK-014.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
Move `webserver`'s backend state (`MHD_Daemon*`, mutexes, ban set, connection table) into `detail/webserver_impl.hpp` so the public header carries only `std::unique_ptr<webserver_impl>`. No API rename or behavioral change yet — pure structural move.
99

1010
**Action Items:**
11-
- [ ] Create `src/httpserver/detail/webserver_impl.hpp` (gated `HTTPSERVER_COMPILATION` only).
12-
- [ ] Move from public `webserver.hpp` into `webserver_impl`: `MHD_Daemon* daemon_`, all mutex/cond_var members, ban list, connection-state map, route-table data structures.
13-
- [ ] Public `webserver.hpp` declares `class webserver { ... std::unique_ptr<webserver_impl> impl_; ... };` and forward-declares `class webserver_impl;` in `httpserver::detail` namespace.
14-
- [ ] Implement public methods as one-liners forwarding to `impl_->method()`.
15-
- [ ] Move `<microhttpd.h>` and `<pthread.h>` includes from public `webserver.hpp` into `webserver_impl.hpp` and `webserver.cpp`.
16-
- [ ] Define a `connection_state` struct inside `webserver_impl` (will host the per-connection arena in TASK-016).
11+
- [x] Create `src/httpserver/detail/webserver_impl.hpp` (gated `HTTPSERVER_COMPILATION` only).
12+
- [x] Move from public `webserver.hpp` into `webserver_impl`: `MHD_Daemon* daemon_`, all mutex/cond_var members, ban list, connection-state map, route-table data structures.
13+
- [x] Public `webserver.hpp` declares `class webserver { ... std::unique_ptr<webserver_impl> impl_; ... };` and forward-declares `class webserver_impl;` in `httpserver::detail` namespace.
14+
- [x] Implement public methods as one-liners forwarding to `impl_->method()`.
15+
- [x] Move `<microhttpd.h>` and `<pthread.h>` includes from public `webserver.hpp` into `webserver_impl.hpp` and `webserver.cpp`.
16+
- [x] Define a `connection_state` struct inside `webserver_impl` (will host the per-connection arena in TASK-016).
1717

1818
**Dependencies:**
1919
- Blocked by: TASK-002
@@ -29,4 +29,4 @@ Move `webserver`'s backend state (`MHD_Daemon*`, mutexes, ban set, connection ta
2929
**Related Requirements:** PRD-HDR-REQ-001..004
3030
**Related Decisions:** DR-002, DR-003b, §4.1
3131

32-
**Status:** Not Started
32+
**Status:** In Progress

specs/tasks/_index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ Nominally: **13 sequential tasks**, each S–XL. Most other tasks parallelize of
9696
| TASK-011 | `http_response` const-correct accessors | M2 | Done | TASK-009 |
9797
| TASK-012 | `http_response` fluent `with_*` setters | M2 | Done | TASK-009 |
9898
| TASK-013 | Remove `*_response` subclasses and dispatch virtuals | M2 | Done | TASK-009, TASK-010, TASK-011, TASK-012 |
99-
| TASK-014 | `webserver_impl` skeleton (PIMPL prep) | M3 | Not Started | TASK-002 |
99+
| TASK-014 | `webserver_impl` skeleton (PIMPL prep) | M3 | In Progress | TASK-002 |
100100
| TASK-015 | `http_request_impl` skeleton (PIMPL split) | M3 | Not Started | TASK-002, TASK-014 |
101101
| TASK-016 | Per-connection arena for `http_request_impl` | M3 | Not Started | TASK-014, TASK-015 |
102102
| TASK-017 | `http_request` container getters return `const&` | M3 | Not Started | TASK-015 |

specs/unworked_review_issues/2026-05-04_115707_task-014.md

Lines changed: 117 additions & 0 deletions
Large diffs are not rendered by default.

src/Makefile.am

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp fil
2323
# noinst_HEADERS: shipped in the tarball but NEVER installed under $prefix/include.
2424
# Detail headers (httpserver/detail/*.hpp) live here so they cannot leak to
2525
# downstream consumers — the public surface comes in through <httpserver.hpp>.
26-
noinst_HEADERS = httpserver/string_utilities.hpp httpserver/detail/modded_request.hpp httpserver/detail/http_endpoint.hpp httpserver/detail/body.hpp gettext.h
26+
noinst_HEADERS = httpserver/string_utilities.hpp httpserver/detail/modded_request.hpp httpserver/detail/http_endpoint.hpp httpserver/detail/body.hpp httpserver/detail/webserver_impl.hpp gettext.h
2727
nobase_include_HEADERS = httpserver.hpp httpserver/body_kind.hpp httpserver/constants.hpp httpserver/create_webserver.hpp httpserver/webserver.hpp httpserver/http_utils.hpp httpserver/file_info.hpp httpserver/http_request.hpp httpserver/http_response.hpp httpserver/http_resource.hpp httpserver/feature_unavailable.hpp httpserver/iovec_entry.hpp httpserver/http_arg_value.hpp httpserver/http_method.hpp
2828

2929
if HAVE_WEBSOCKET

src/httpserver/detail/http_endpoint.hpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ namespace httpserver {
3737

3838
namespace detail {
3939

40-
class http_resource;
41-
4240
/**
4341
* Class representing an Http Endpoint. It is an abstraction used by the APIs.
4442
**/
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/*
2+
This file is part of libhttpserver
3+
Copyright (C) 2011-2026 Sebastiano Merlino
4+
5+
This library is free software; you can redistribute it and/or
6+
modify it under the terms of the GNU Lesser General Public
7+
License as published by the Free Software Foundation; either
8+
version 2.1 of the License, or (at your option) any later version.
9+
10+
This library is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public
16+
License along with this library; if not, write to the Free Software
17+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
18+
USA
19+
*/
20+
21+
// TASK-014: webserver PIMPL backing class.
22+
//
23+
// This header is *internal*. It is reachable only when compiling the
24+
// libhttpserver translation units themselves (HTTPSERVER_COMPILATION
25+
// is supplied through src/Makefile.am AM_CPPFLAGS). It is NOT included
26+
// from the public umbrella <httpserver.hpp>, so the gate is the strict
27+
// one-mode form, not the dual-mode form used by other detail headers.
28+
#if !defined(HTTPSERVER_COMPILATION)
29+
#error "webserver_impl.hpp is internal; only reachable when compiling libhttpserver."
30+
#endif
31+
32+
#ifndef SRC_HTTPSERVER_DETAIL_WEBSERVER_IMPL_HPP_
33+
#define SRC_HTTPSERVER_DETAIL_WEBSERVER_IMPL_HPP_
34+
35+
#include <microhttpd.h>
36+
#include <pthread.h>
37+
#include <stdarg.h>
38+
39+
#include <list>
40+
#include <map>
41+
#include <memory>
42+
#include <mutex>
43+
#include <set>
44+
#include <shared_mutex>
45+
#include <string>
46+
#include <unordered_map>
47+
#include <utility>
48+
49+
#ifdef HAVE_GNUTLS
50+
#include <gnutls/gnutls.h>
51+
#endif // HAVE_GNUTLS
52+
53+
#include "httpserver/http_utils.hpp"
54+
#include "httpserver/detail/http_endpoint.hpp"
55+
56+
#if MHD_VERSION < 0x00097002
57+
typedef int MHD_Result;
58+
#endif
59+
60+
namespace httpserver {
61+
62+
class webserver;
63+
class http_resource;
64+
class http_response;
65+
#ifdef HAVE_WEBSOCKET
66+
class websocket_handler;
67+
#endif // HAVE_WEBSOCKET
68+
69+
namespace detail {
70+
71+
struct modded_request;
72+
73+
// connection_state: per-MHD_Connection arena anchor.
74+
//
75+
// Defined as a near-empty type so downstream tasks (TASK-016) can add
76+
// members (e.g. std::pmr::monotonic_buffer_resource arena_) without
77+
// retouching the public header. Copy/move are deleted now so adding
78+
// non-copyable/non-movable members later does not change the trait.
79+
struct connection_state {
80+
connection_state() = default;
81+
connection_state(const connection_state&) = delete;
82+
connection_state& operator=(const connection_state&) = delete;
83+
connection_state(connection_state&&) = delete;
84+
connection_state& operator=(connection_state&&) = delete;
85+
};
86+
87+
// webserver_impl: backing object holding all backend-coupled state of
88+
// `webserver` (MHD daemon, mutexes, ban/allowance sets, route table,
89+
// route cache, websocket registry, optional GnuTLS SNI cache) plus the
90+
// dispatch helpers and MHD trampolines that operate on those.
91+
//
92+
// Members are deliberately public: webserver and the free-function MHD
93+
// callbacks all need direct access. The boundary that matters is between
94+
// the public header and this internal class -- not between webserver and
95+
// its own impl.
96+
class webserver_impl {
97+
public:
98+
explicit webserver_impl(webserver* parent);
99+
~webserver_impl();
100+
webserver_impl(const webserver_impl&) = delete;
101+
webserver_impl& operator=(const webserver_impl&) = delete;
102+
webserver_impl(webserver_impl&&) = delete;
103+
webserver_impl& operator=(webserver_impl&&) = delete;
104+
105+
// Back-pointer used by the dispatch helpers to read the const config
106+
// bag still living on `webserver` (port, max_threads, certs, etc.).
107+
// Set in the constructor to the owning webserver.
108+
webserver* parent = nullptr;
109+
110+
struct MHD_Daemon* daemon = nullptr;
111+
MHD_socket bind_socket = 0;
112+
113+
pthread_mutex_t mutexwait;
114+
pthread_cond_t mutexcond;
115+
116+
bool running = false;
117+
118+
std::shared_mutex registered_resources_mutex;
119+
std::map<detail::http_endpoint, ::httpserver::http_resource*> registered_resources;
120+
std::map<std::string, ::httpserver::http_resource*> registered_resources_str;
121+
std::map<detail::http_endpoint, ::httpserver::http_resource*> registered_resources_regex;
122+
123+
struct route_cache_entry {
124+
detail::http_endpoint matched_endpoint;
125+
::httpserver::http_resource* resource;
126+
};
127+
static constexpr size_t ROUTE_CACHE_MAX_SIZE = 256;
128+
std::mutex route_cache_mutex;
129+
std::list<std::pair<std::string, route_cache_entry>> route_cache_list;
130+
std::unordered_map<std::string,
131+
std::list<std::pair<std::string, route_cache_entry>>::iterator>
132+
route_cache_map;
133+
134+
std::shared_mutex bans_mutex;
135+
std::set<http::ip_representation> bans;
136+
137+
std::shared_mutex allowances_mutex;
138+
std::set<http::ip_representation> allowances;
139+
140+
#ifdef HAVE_WEBSOCKET
141+
std::map<std::string, ::httpserver::websocket_handler*> registered_ws_handlers;
142+
143+
struct ws_upgrade_data {
144+
webserver_impl* impl;
145+
::httpserver::websocket_handler* handler;
146+
};
147+
148+
static void upgrade_handler(void *cls, struct MHD_Connection* connection,
149+
void *req_cls, const char *extra_in,
150+
size_t extra_in_size, MHD_socket sock,
151+
struct MHD_UpgradeResponseHandle *urh);
152+
#endif // HAVE_WEBSOCKET
153+
154+
#if defined(HAVE_GNUTLS) && defined(MHD_OPTION_HTTPS_CERT_CALLBACK)
155+
mutable std::map<std::string, gnutls_certificate_credentials_t>
156+
sni_credentials_cache;
157+
mutable std::shared_mutex sni_credentials_mutex;
158+
#endif // HAVE_GNUTLS && MHD_OPTION_HTTPS_CERT_CALLBACK
159+
160+
// Dispatch helpers (formerly methods on webserver). Each of these
161+
// touches both backend state on this impl and const config on the
162+
// owning webserver (via `parent`).
163+
std::shared_ptr<::httpserver::http_response> not_found_page(modded_request* mr) const;
164+
std::shared_ptr<::httpserver::http_response> method_not_allowed_page(modded_request* mr) const;
165+
std::shared_ptr<::httpserver::http_response> internal_error_page(modded_request* mr,
166+
bool force_our = false) const;
167+
bool should_skip_auth(const std::string& path) const;
168+
void invalidate_route_cache();
169+
170+
MHD_Result requests_answer_first_step(MHD_Connection* connection, modded_request* mr);
171+
MHD_Result requests_answer_second_step(MHD_Connection* connection,
172+
const char* method, const char* version, const char* upload_data,
173+
size_t* upload_data_size, modded_request* mr);
174+
MHD_Result finalize_answer(MHD_Connection* connection, modded_request* mr,
175+
const char* method);
176+
MHD_Result complete_request(MHD_Connection* connection, modded_request* mr,
177+
const char* version, const char* method);
178+
struct MHD_Response* get_raw_response_with_fallback(modded_request* mr);
179+
180+
static struct MHD_Response* materialize_response(::httpserver::http_response* resp);
181+
static void decorate_mhd_response(struct MHD_Response* response,
182+
const ::httpserver::http_response& resp);
183+
184+
// MHD trampolines registered with libmicrohttpd. Closure pointer is
185+
// `this` (webserver_impl*) for answer_to_connection, otherwise the
186+
// owning `webserver*` (so callbacks can read the const config bag).
187+
static void request_completed(void* cls, struct MHD_Connection* connection,
188+
void** con_cls, enum MHD_RequestTerminationCode toe);
189+
static MHD_Result answer_to_connection(void* cls, MHD_Connection* connection,
190+
const char* url, const char* method, const char* version,
191+
const char* upload_data, size_t* upload_data_size, void** con_cls);
192+
static MHD_Result post_iterator(void* cls, enum MHD_ValueKind kind,
193+
const char* key, const char* filename, const char* content_type,
194+
const char* transfer_encoding, const char* data, uint64_t off,
195+
size_t size);
196+
197+
// Auxiliary MHD callbacks (formerly free functions in webserver.cpp).
198+
// Each takes `cls = webserver*` so it can read the const config bag.
199+
static MHD_Result policy_callback(void* cls, const struct sockaddr* addr,
200+
socklen_t addrlen);
201+
static void error_log(void* cls, const char* fmt, va_list ap);
202+
static void* uri_log(void* cls, const char* uri,
203+
struct MHD_Connection* con);
204+
static void access_log(::httpserver::webserver* cls, const std::string& uri);
205+
static size_t unescaper_func(void* cls, struct MHD_Connection* c, char* s);
206+
207+
#ifdef HAVE_GNUTLS
208+
static int psk_cred_handler_func(void* cls, struct MHD_Connection* connection,
209+
const char* username, void** psk,
210+
size_t* psk_size);
211+
#ifdef MHD_OPTION_HTTPS_CERT_CALLBACK
212+
static int sni_cert_callback_func(void* cls, struct MHD_Connection* connection,
213+
const char* server_name,
214+
gnutls_certificate_credentials_t* creds);
215+
#endif // MHD_OPTION_HTTPS_CERT_CALLBACK
216+
#endif // HAVE_GNUTLS
217+
};
218+
219+
} // namespace detail
220+
} // namespace httpserver
221+
222+
#endif // SRC_HTTPSERVER_DETAIL_WEBSERVER_IMPL_HPP_

src/httpserver/file_info.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
namespace httpserver {
3131
class webserver;
32+
namespace detail { class webserver_impl; }
3233

3334
namespace http {
3435

@@ -53,6 +54,7 @@ class file_info {
5354
void grow_file_size(size_t additional_file_size);
5455

5556
friend class httpserver::webserver;
57+
friend class httpserver::detail::webserver_impl; // TASK-014
5658
};
5759

5860
} // namespace http

src/httpserver/http_request.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ struct MHD_Connection;
5353
namespace httpserver {
5454

5555
namespace detail { struct modded_request; }
56+
namespace detail { class webserver_impl; }
5657

5758
/**
5859
* Class representing an abstraction for an Http Request. It is used from classes using these apis to receive information through http protocol.
@@ -534,6 +535,7 @@ class http_request {
534535
}
535536

536537
friend class webserver;
538+
friend class detail::webserver_impl; // TASK-014: PIMPL dispatch path
537539
friend struct detail::modded_request;
538540
};
539541

src/httpserver/http_resource.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737

3838
namespace httpserver { class http_request; }
3939
namespace httpserver { class http_response; }
40+
namespace httpserver { class webserver; }
41+
namespace httpserver { namespace detail { class webserver_impl; } }
4042

4143
namespace httpserver {
4244

@@ -228,6 +230,7 @@ class http_resource {
228230

229231
private:
230232
friend class webserver;
233+
friend class detail::webserver_impl; // TASK-014: dispatch helpers
231234
friend void resource_init(std::map<std::string, bool>* res);
232235
std::map<std::string, bool> method_state;
233236
};

src/httpserver/http_response.hpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,12 @@ namespace detail { class body; }
5252
// without pulling webserver.hpp (and its libmicrohttpd surface) into this
5353
// public header. The friendship lets webserver dispatch reach the private
5454
// body_ pointer to call body_->materialize() without widening the public
55-
// API by one byte (TASK-013).
55+
// API by one byte (TASK-013). After TASK-014 the dispatch helpers moved
56+
// behind the PIMPL boundary, so the friendship is also extended to
57+
// detail::webserver_impl. The pre-TASK-014 `friend class webserver;`
58+
// remains for backward compatibility within the translation unit.
5659
class webserver;
60+
namespace detail { class webserver_impl; }
5761

5862
/**
5963
* Class representing an abstraction for an Http Response. It is used from classes using these apis to send information through http protocol.
@@ -387,7 +391,11 @@ class http_response final {
387391
// cheaper than exposing a public materialize_for_dispatch_() method
388392
// on the value type and keeps the public API minimal. Forward-
389393
// declared as `class webserver;` near the top of this header.
394+
// TASK-014: the dispatch helpers moved behind the PIMPL boundary.
395+
// detail::webserver_impl is the actual reader of body_/kind_/status_
396+
// now; webserver itself no longer touches the response wire path.
390397
friend class webserver;
398+
friend class detail::webserver_impl;
391399
};
392400

393401
std::ostream &operator<<(std::ostream &os, const http_response &r);

0 commit comments

Comments
 (0)