-
-
Notifications
You must be signed in to change notification settings - Fork 191
Expand file tree
/
Copy pathwebserver_impl.hpp
More file actions
328 lines (288 loc) · 14.6 KB
/
Copy pathwebserver_impl.hpp
File metadata and controls
328 lines (288 loc) · 14.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
/*
This file is part of libhttpserver
Copyright (C) 2011-2026 Sebastiano Merlino
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
*/
// webserver PIMPL backing class.
//
// This header is *internal*. It is reachable only when compiling the
// libhttpserver translation units themselves (HTTPSERVER_COMPILATION
// is supplied through src/Makefile.am AM_CPPFLAGS). It is NOT included
// from the public umbrella <httpserver.hpp>, so the gate is the strict
// one-mode form, not the dual-mode form used by other detail headers.
//
// Members below are accessed from src/webserver.cpp; cppcheck analyses
// each TU in isolation and cannot see the uses, so the unusedStructMember
// check must be suppressed at the file level.
// cppcheck-suppress-file unusedStructMember
#if !defined(HTTPSERVER_COMPILATION)
#error "webserver_impl.hpp is internal; only reachable when compiling libhttpserver."
#endif
#ifndef SRC_HTTPSERVER_DETAIL_WEBSERVER_IMPL_HPP_
#define SRC_HTTPSERVER_DETAIL_WEBSERVER_IMPL_HPP_
#include <microhttpd.h>
#include <pthread.h>
#include <stdarg.h>
#include <array>
#include <atomic>
#include <cstddef>
#include <cstring>
#include <functional>
#include <list>
#include <map>
#include <memory>
#include <memory_resource>
#include <mutex>
#include <optional>
#include <regex>
#include <set>
#include <shared_mutex>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
#ifdef HAVE_GNUTLS
#include <gnutls/gnutls.h>
#endif // HAVE_GNUTLS
#include "httpserver/file_info.hpp"
#include "httpserver/http_utils.hpp"
#include "httpserver/hook_action.hpp"
#include "httpserver/hook_context.hpp"
#include "httpserver/hook_phase.hpp"
#include "httpserver/detail/connection_state.hpp"
#include "httpserver/detail/daemon_lifecycle.hpp"
#include "httpserver/detail/error_pages.hpp"
#include "httpserver/detail/hook_bus.hpp"
#include "httpserver/detail/hook_dispatcher.hpp"
#include "httpserver/detail/http_endpoint.hpp"
#include "httpserver/detail/ip_access_control.hpp"
#include "httpserver/detail/request_dispatcher.hpp"
#include "httpserver/detail/request_pipeline.hpp"
#include "httpserver/detail/response_materializer.hpp"
#include "httpserver/detail/route_table.hpp"
#include "httpserver/detail/upload_pipeline.hpp"
#include "httpserver/detail/websocket_upgrader.hpp"
#include "httpserver/detail/ws_registry.hpp"
#if MHD_VERSION < 0x00097002
typedef int MHD_Result;
#endif
namespace httpserver {
class webserver;
class http_resource;
class http_response;
// Forward declaration is unconditional to match the public webserver.hpp
// surface. The class body and member functions remain
// conditionally compiled; only the declaration is always present.
class websocket_handler;
namespace detail {
struct connection_context;
class lambda_resource;
// connection_state lives in its own header to keep webserver_impl.hpp
// under the project per-file LOC ceiling. See
// httpserver/detail/connection_state.hpp for documentation and
// rationale.
/**
* @brief Whether the runtime opt-in for raw-body debug dumping is in
* effect for this process.
*
* Reads the env var LIBHTTPSERVER_DEBUG_DUMP_REQUEST_BODY once on
* first call via a function-local static; subsequent setenv() calls
* are intentionally ignored. Returns true iff the variable is set to
* any non-empty, non-`"0"` value. Default behaviour is silent on both
* RELEASE and DEBUG builds.
*/
bool debug_dump_request_body_opted_in();
/**
* @brief Emit a one-shot SECURITY WARNING when raw-body dumping is
* opted in.
*
* Called from webserver::start() before MHD_start_daemon. If the env
* var opt-in is active, writes a single line naming the env var, the
* SECURITY WARNING marker, and the credential / PII risk to stderr,
* and (when wired) forwards the same line to the owning webserver's
* log_error callback. Process-wide idempotent: multiple webservers in
* the same process produce exactly one stderr emission.
*
* @param parent Owning webserver pointer (used to find log_error).
* May be nullptr; the stderr emission still fires.
*/
void maybe_warn_debug_dump_request_body(const webserver* parent);
// webserver_impl: backing object holding all backend-coupled state of
// `webserver` (MHD daemon, mutexes, deny/allow IP sets, route table,
// route cache, websocket registry, optional GnuTLS SNI cache) plus the
// dispatch helpers and MHD trampolines that operate on those.
//
// Members are deliberately public: webserver and the free-function MHD
// callbacks all need direct access. The boundary that matters is between
// the public header and this internal class -- not between webserver and
// its own impl.
class webserver_impl {
public:
// `bind_socket_val` is the caller-supplied pre-bound socket from
// create_webserver().bind_socket(), or MHD_INVALID_SOCKET if none was
// provided. Initialised here so the impl is fully constructed from the
// parent's member-initialiser list with no post-construction mutations.
explicit webserver_impl(webserver* parent,
MHD_socket bind_socket_val = MHD_INVALID_SOCKET);
~webserver_impl();
webserver_impl(const webserver_impl&) = delete;
webserver_impl& operator=(const webserver_impl&) = delete;
webserver_impl(webserver_impl&&) = delete;
webserver_impl& operator=(webserver_impl&&) = delete;
// Back-pointer used by the dispatch helpers to read the const config
// bag still living on `webserver` (port, max_threads, certs, etc.).
// Set in the constructor to the owning webserver.
webserver* parent = nullptr;
// MHD daemon handle + start/stop threading state + the daemon-
// construction builders (MHD option array + start-flag composers) live
// behind this collaborator. webserver::start/stop/is_running/
// get_bound_port/run/... (webserver_lifecycle.cpp) drive it via
// impl_->daemon_.{daemon,running,mutexwait,mutexcond,bind_socket} and
// daemon_.build_mhd_option_array / compose_start_flags. Constructed with
// a back-pointer to this impl so the builders can read parent config +
// the ws registry. Its pthread primitives are RAII (ctor init / dtor
// destroy).
daemon_lifecycle daemon_;
// Per-webserver-instance `opaque` value handed to
// MHD_queue_auth_required_response3 when the user's
// digest_challenge factory leaves the field empty. Generated once
// at construction from std::random_device (16 bytes hex-encoded ->
// 32 chars), but ONLY on HAVE_DAUTH builds (the ctor-body assignment
// is gated). Declared unconditionally -- an always-empty std::string
// on non-DAUTH builds -- so response_materializer can bind a plain
// `const std::string&` reference without HAVE_DAUTH in its signature
// (DR-014); the empty value is never read since the digest queueing
// branch is itself HAVE_DAUTH-gated. Single-writer-at-construction,
// lock-free read on the dispatch hot path. RFC 7616 §5.10: opaque is
// an identifier, not a secret nor a replay token; reuse is allowed.
std::string digest_opaque_;
// v2 3-tier route table + LRU cache collaborator. Owns
// route_table_mutex_, the three tiers (exact_routes_,
// param_and_prefix_routes_, regex_routes_) and route_lru_cache. The
// dispatch hot path resolves through it (resolve_resource_for_request
// -> routes_.lookup_v2); on_*/route + register/unregister mutate it
// (register_v2_route / the lock_for_write() + upsert primitives). The
// lambda_resource shim-creation POLICY for on_*/route stays on this
// class (prepare_or_create_lambda_shim / commit_handlers_to_shim); the
// orchestration holds routes_.lock_for_write() across the probe +
// mutation. Its mutex is independent of every other cluster's; no call
// site holds two of them at once.
route_table routes_;
// tier_hit / lookup_result moved into route_table; these aliases keep
// the many white-box tests that name webserver_impl::tier_hit /
// ::lookup_result compiling unchanged.
using tier_hit = route_table::tier_hit;
using lookup_result = route_table::lookup_result;
// Thin forwarders to the collaborator so the dispatch call sites and
// the white-box tests read `lookup_v2(...)` / `invalidate_route_cache()`
// on webserver_impl unchanged.
lookup_result lookup_v2(http_method method, const std::string& path) {
return routes_.lookup_v2(method, path);
}
void invalidate_route_cache() { routes_.invalidate_route_cache(); }
// Lifecycle hook bus. Owns the eleven server-wide phase vectors, the
// shared registration mutex, the advisory any_hooks_ gate array, and
// the handler_exception / log_access alias slots. webserver::add_hook
// registers into it (hooks_.add); hook_handle::remove erases
// (hooks_.remove); the fire_* / has_hooks_for / phase_hook_count
// forwarders below delegate to it, binding log_dispatch_error as the
// per-call error logger. Its mutex is independent of every other
// cluster's; no call site holds two of them at once.
hook_bus hooks_;
// Thin forwarder to the collaborator (see hook_bus::has_hooks_for).
// Kept on webserver_impl so the many gate-check call sites and the
// webserver_test_access bridge read `has_hooks_for(hook_phase::X)`
// unchanged.
bool has_hooks_for(::httpserver::hook_phase p) const noexcept {
return hooks_.has_hooks_for(p);
}
// IP allow/deny access control (deny/allow sets + their mutexes) lives
// behind this collaborator. policy_callback consults it via classify();
// webserver::{deny,allow,remove_denied,remove_allowed}_ip mutate it. No
// other state cluster on webserver_impl shares these mutexes.
ip_access_control acl_;
#ifdef HAVE_WEBSOCKET
// WebSocket handler registry (URL -> handler map + its mutex) lives
// behind this collaborator. register/unregister_ws_resource mutate it;
// complete_websocket_upgrade resolves a handler via find() (taking a
// shared_ptr copy that keeps the handler alive across the MHD upgrade
// callback even if unregister races mid-upgrade); start() consults
// empty() for MHD_ALLOW_UPGRADE. Its mutex is independent of every
// other cluster's; no call site holds two of them at once.
ws_registry ws_;
// Behavior service (DR-014 §4.11): RFC-6455 upgrade handshake + the
// per-connection frame receive loop. Holds ws_ (the handler registry).
// The webserver_impl try_handle_websocket_upgrade method forwards here.
// Gated with ws_ since both require HAVE_WEBSOCKET.
websocket_upgrader ws_upgrader_;
#endif // HAVE_WEBSOCKET
#if defined(HAVE_GNUTLS) && defined(MHD_OPTION_HTTPS_CERT_CALLBACK)
mutable std::map<std::string, gnutls_certificate_credentials_t>
sni_credentials_cache;
mutable std::shared_mutex sni_credentials_mutex;
#endif // HAVE_GNUTLS && MHD_OPTION_HTTPS_CERT_CALLBACK
// Per-phase registration count. Thin forwarder to the collaborator
// (see hook_bus::phase_hook_count). Kept on webserver_impl so the
// HTTPSERVER_COMPILATION friend bridge in webserver.hpp
// (webserver_test_access) reaches it as impl->phase_hook_count(p)
// unchanged.
[[nodiscard]] std::size_t phase_hook_count(
::httpserver::hook_phase p) const noexcept {
return hooks_.phase_hook_count(p);
}
// Behavior service (DR-014 §4.11): synthesises 404/405/500 responses.
// A leaf — reads only the const config bag (via parent->config, bound
// at construction) and conn->request. The webserver_impl error-page
// methods (not_found_page / internal_error_page / ...) forward here.
error_pages errors_;
// Behavior service (DR-014 §4.11): dispatch-time hook firing. Owns the
// eleven per-phase forwarders + the four gated helpers; holds hooks_
// (the state) and parent->config (only to reach log_dispatch_error).
// The webserver_impl fire_* / fire_*_gated methods forward here.
hook_dispatcher hooks_dispatch_;
// Behavior service (DR-014 §4.11): http_response -> MHD_Response wire
// construction, decoration, kind-dispatched queueing, response_sent
// firing, and the belt-and-suspenders fallback chain. Holds errors_ +
// hooks_dispatch_ + digest_opaque_ + parent->config. The webserver_impl
// materialize_and_queue_response method forwards here.
response_materializer response_mat_;
// Behavior service (DR-014 §4.11): multipart / file-upload handling.
// Holds only parent->config. The post_iterator MHD trampoline forwards
// here (impl_->upload_.iterate_file / upload_pipeline::handle_post_form_arg).
upload_pipeline upload_;
// Behavior service (DR-014 §4.11): the routing + auth + handler-invocation
// stage (finalize_answer / resolve_resource_for_request /
// dispatch_resource_handler). Declared last: it references routes_,
// hooks_dispatch_, errors_, response_mat_, ws_upgrader_ (HAVE_WEBSOCKET)
// and parent->config, so it must be constructed after all of them.
// complete_request hands off to dispatcher_.finalize_answer.
request_dispatcher dispatcher_;
// Behavior service (DR-014 §4.11): the MHD re-entrant body-accumulation
// state machine (requests_answer_first_step / _second_step /
// complete_request). References hooks_dispatch_, dispatcher_ and
// parent->config, so it is declared after dispatcher_. The
// answer_to_connection MHD trampoline forwards into pipeline_.
request_pipeline pipeline_;
// Dispatch helpers, start helpers, MHD trampolines, and the route /
// upload sub-types live in a sibling header to keep this class
// definition under the project per-file LOC ceiling. The inner gate
// forces the header to be included only from within this class body.
#define SRC_HTTPSERVER_DETAIL_WEBSERVER_IMPL_HPP_INSIDE_CLASS_
#include "httpserver/detail/webserver_impl_dispatch.hpp"
#undef SRC_HTTPSERVER_DETAIL_WEBSERVER_IMPL_HPP_INSIDE_CLASS_
};
} // namespace detail
} // namespace httpserver
#endif // SRC_HTTPSERVER_DETAIL_WEBSERVER_IMPL_HPP_