-
-
Notifications
You must be signed in to change notification settings - Fork 191
Expand file tree
/
Copy pathwebserver.hpp
More file actions
407 lines (377 loc) · 18.4 KB
/
Copy pathwebserver.hpp
File metadata and controls
407 lines (377 loc) · 18.4 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
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
/*
This file is part of libhttpserver
Copyright (C) 2011-2019 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
*/
#if !defined (_HTTPSERVER_HPP_INSIDE_) && !defined (HTTPSERVER_COMPILATION)
#error "Only <httpserver.hpp> or <httpserverpp> can be included directly."
#endif
#ifndef SRC_HTTPSERVER_WEBSERVER_HPP_
#define SRC_HTTPSERVER_WEBSERVER_HPP_
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
#include "httpserver/constants.hpp"
#include "httpserver/hook_action.hpp"
#include "httpserver/hook_context.hpp"
#include "httpserver/hook_handle.hpp"
#include "httpserver/hook_phase.hpp"
#include "httpserver/http_method.hpp"
#include "httpserver/http_utils.hpp"
#include "httpserver/create_webserver.hpp"
// Socket-layer types kept minimal in this public header.
// `struct sockaddr` is a POSIX-tagged struct and can be forward-declared
// per-use, but `fd_set` is a typedef of an unnamed struct in glibc, so
// it must be a complete type when get_fdset's signature is parsed.
// Pull in only the system header that defines fd_set; the rest of the
// BSD-socket / select API does not leak into consumer TUs.
#if defined(_WIN32) && !defined(__CYGWIN__)
#include <winsock2.h>
#else
#include <sys/select.h>
#endif
// Forward declarations: backend (MHD) types are intentionally NOT pulled in.
// The libmicrohttpd and pthread headers live behind the PIMPL
// boundary in detail/webserver_impl.hpp.
namespace httpserver {
class http_resource;
class http_response;
// Forward-declared unconditionally so the public surface of
// webserver is identical in HAVE_WEBSOCKET-on and HAVE_WEBSOCKET-off
// builds. When HAVE_WEBSOCKET is undefined the
// class definition in websocket_handler.hpp is still included via the
// umbrella header; member-function bodies live in src/websocket_handler.cpp.
class websocket_handler;
namespace detail {
struct connection_context;
class webserver_impl;
class daemon_lifecycle;
class http_endpoint;
} // namespace detail
} // namespace httpserver
namespace httpserver {
/**
* Class representing the webserver. Main class of the apis.
*
* ### Threading contract
*
* The webserver dispatches each request on one of libmicrohttpd's worker
* threads. The thread-safety contract:
*
* 1. Public registration / un-registration methods (@ref register_path,
* @ref register_prefix, the @ref on_get
* family, @ref route, @ref unregister_path, @ref unregister_prefix,
* @ref unregister_resource, @ref register_ws_resource,
* @ref unregister_ws_resource, @ref deny_ip, @ref remove_denied_ip,
* @ref allow_ip, @ref remove_allowed_ip) are
* thread-safe and re-entrant from inside a request handler.
* The route lookup LRU cache (`detail::route_lru_cache`) is consulted
* outside `route_table_mutex_`, so an in-flight request that hit the
* cache may still be served by a resource whose @ref unregister_path /
* @ref unregister_resource call already completed concurrently. No
* use-after-free results -- the cache holds a `shared_ptr` that keeps
* the resource alive -- but the response for that one request may be
* logically stale (served by the just-unregistered handler).
* 2. The exceptions are @ref stop, @ref stop_and_wait, and the
* destructor: each joins libmicrohttpd's worker threads and
* therefore deadlocks (or aborts with "Failed to join a thread."
* on some libmicrohttpd versions) when called from within a
* handler thread. Call them from the thread that owns the
* webserver instance.
* 3. `http_request` is single-threaded per request: it is owned by
* the worker thread servicing that request and MUST NOT be
* retained beyond the handler's return.
* 4. `http_response` is a value type with exclusive ownership; no
* cross-thread sharing.
* 5. User-supplied callbacks invoked from MHD worker threads --
* @ref create_webserver::log_access, @ref create_webserver::log_error,
* @ref create_webserver::not_found_handler,
* @ref create_webserver::method_not_allowed_handler,
* @ref create_webserver::internal_error_handler,
* @ref create_webserver::file_cleanup_callback, the PSK / SNI / ALPN
* callbacks, any registered @ref http_resource render method, and
* any registered lifecycle hook (@ref add_hook / @ref http_resource::add_hook )
* -- may run concurrently on multiple threads. Implementations MUST be thread-safe.
*
* ### Handler error-propagation contract
*
* Every registered request handler is invoked from the dispatch path under
* a two-branch try/catch. The contract:
*
* 1. The handler call is wrapped in
* `try { ... } catch (const std::exception& e) { ... } catch (...) { ... }`.
* 2. On `std::exception`: the message is logged via the configured
* `log_error` callback, then `internal_error_handler` is invoked with
* `e.what()`. The response it returns is sent on the wire. When no
* handler is configured, the default 500 carries the fixed body
* `"Internal Server Error"` (CWE-209 fix);
* the originating message is still surfaced via the
* `log_error` callback. The verbose v1 body (message in the body)
* is opt-in via @ref create_webserver::expose_exception_messages.
* 3. On non-`std::exception` (e.g. `throw 42`): same path with the
* message replaced by the literal string `"unknown exception"`.
* 4. If `internal_error_handler` itself throws while servicing 2 or 3,
* the failure is logged generically and a hardcoded 500 with an
* EMPTY body is sent. No exception ever escapes into libmicrohttpd.
* 5. `feature_unavailable` (a `std::runtime_error` subclass) is NOT
* mapped to a special status: it lands as a generic 500 like any
* other `std::exception`.
* 6. The `log_error` callback may be invoked concurrently from multiple
* MHD worker threads; user implementations MUST be thread-safe.
* 7. Hook layering:
* @ref hook_phase::handler_exception hooks fire BEFORE this alias;
* throwing hooks are caught and the chain continues; (4) fires
* without re-invoking the alias on full chain failure.
*
* Resources are encouraged to throw rather than synthesise 500s.
**/
class webserver {
public:
// Explicit to forbid implicit conversion from
// create_webserver. Callers must direct-init: webserver ws{cw};
explicit webserver(const create_webserver& params);
/**
* Destructor.
*
* Calls stop() unconditionally, which joins libmicrohttpd's
* worker threads. For the same reason as stop(), destroying a
* webserver from inside a handler thread deadlocks (or, on some
* libmicrohttpd versions, aborts with "Failed to join a thread.").
* Destroy the webserver from the thread that constructed it.
*
* @see stop() for the threading constraints that apply equally here.
**/
~webserver();
// PIMPL-owned: copy/move would slice the backing impl object.
webserver(const webserver&) = delete;
webserver& operator=(const webserver&) = delete;
webserver(webserver&&) = delete;
webserver& operator=(webserver&&) = delete;
/**
* Method used to start the webserver.
* This method can be blocking or not.
* @param blocking param indicating if the method is blocking or not
* @return a boolean indicating if the webserver is running or not.
**/
bool start(bool blocking = false);
/**
* Stop the webserver.
*
* Joins libmicrohttpd's worker threads before returning. Safe to
* call from any thread *except* a handler thread: stop() blocks
* until every worker (including the calling one) drains, so a
* call from inside a handler self-joins and deadlocks (or, on
* some libmicrohttpd versions, aborts with "Failed to join a
* thread.").
*
* For the same reason, ~webserver() (which calls stop())
* deadlocks if it runs on a handler thread; destroy the
* webserver from the thread that constructed it.
*
* @return true if the daemon was running and is now stopped;
* false if it was already stopped.
**/
bool stop();
/**
* Method used to evaluate if the server is running or not.
* @return true if the webserver is running
**/
bool is_running();
// Registration, on_* shortcuts, route(), and unregister_* live in
// a sibling header to keep this class 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_WEBSERVER_HPP_INSIDE_CLASS_
#include "httpserver/webserver_routes.hpp"
#undef SRC_HTTPSERVER_WEBSERVER_HPP_INSIDE_CLASS_
/**
* Add @p ip (or a range, e.g. "127.0.0.*") to the IP deny list.
* Connections from a matching address are refused at the policy
* callback. This is the exception list under the default ACCEPT
* policy (deny only these; permit everyone else).
*
* Precedence: a matching @ref allow_ip entry overrides a matching
* deny entry, so an allow-listed address is admitted even under
* ACCEPT. No-op semantics are preserved when the same IP is added
* twice; a more specific entry replaces a previously-recorded
* wildcard.
*
* @param ip an IP literal or wildcard pattern.
* @see remove_denied_ip, allow_ip
**/
void deny_ip(std::string_view ip);
/**
* Remove @p ip from the IP deny list. Idempotent: removing an IP
* that is not currently denied is a no-op.
*
* @param ip an IP literal or wildcard pattern previously passed to @ref deny_ip.
* @see deny_ip
**/
void remove_denied_ip(std::string_view ip);
/**
* Add @p ip (or a range, e.g. "127.0.0.*") to the IP allow list.
* This is the exception list under the REJECT policy
* (@ref create_webserver::default_policy): permit only these,
* refuse everyone else. Under the default ACCEPT policy an allow
* entry overrides a matching @ref deny_ip entry (allow wins).
*
* No-op semantics are preserved when the same IP is added twice;
* a more specific entry replaces a previously-recorded wildcard.
*
* @param ip an IP literal or wildcard pattern.
* @see remove_allowed_ip, deny_ip
**/
void allow_ip(std::string_view ip);
/**
* Remove @p ip from the IP allow list. Idempotent: removing an IP
* that is not currently allowed is a no-op.
*
* @param ip an IP literal or wildcard pattern previously passed to @ref allow_ip.
* @see allow_ip
**/
void remove_allowed_ip(std::string_view ip);
/// Returns the configured access-log callback; null if none was set.
/// The callback may be invoked concurrently from MHD worker threads.
log_access_ptr get_access_logger() const {
return config.log_access;
}
/// Returns the configured error-log callback; null if none was set.
/// The callback may be invoked concurrently from MHD worker threads.
log_error_ptr get_error_logger() const {
return config.log_error;
}
/// Returns the configured request-validator callback; null if none was set.
/// The callback may be invoked concurrently from MHD worker threads.
[[deprecated("validator callback is not invoked by v2 dispatch; use webserver::add_hook(hook_phase::request_received, ...) instead")]]
validator_ptr get_request_validator() const {
return config.validator;
}
/// Returns the configured URL-unescaper callback; null if none was set.
/// The callback may be invoked concurrently from MHD worker threads.
unescaper_ptr get_unescaper() const {
return config.unescaper;
}
// Event-loop, connection-management, and feature-reporting methods
// live in a sibling header to keep this class 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_WEBSERVER_HPP_INSIDE_CLASS_
#include "httpserver/webserver_runtime.hpp"
#undef SRC_HTTPSERVER_WEBSERVER_HPP_INSIDE_CLASS_
// Websocket registration surface and lifecycle hook bus
// live in sibling headers to keep this class under the project
// per-file LOC ceiling.
#define SRC_HTTPSERVER_WEBSERVER_HPP_INSIDE_CLASS_
#include "httpserver/webserver_websocket.hpp"
#include "httpserver/webserver_hooks.hpp"
#undef SRC_HTTPSERVER_WEBSERVER_HPP_INSIDE_CLASS_
private:
// All builder inputs, copied wholesale from the create_webserver
// builder at construction (see webserver_config in
// create_webserver.hpp). Immutable for the server's lifetime.
// webserver_impl and the dispatch path read options as
// parent->config.<field>.
const webserver_config config;
// Pre-normalized form of @ref config.auth_skip_paths, populated once
// at construction (a derived value, not a builder input, so it is not
// part of webserver_config). webserver_impl::should_skip_auth compares
// request paths against this list (not config.auth_skip_paths) so
// non-canonical entries like "/public/" or "/a/../b" match the
// canonical request path the dispatch surface produces. Built by
// detail::normalize_auth_skip_paths in webserver_request.cpp.
const std::vector<std::string> auth_skip_paths_normalized;
// Shared registration helper. Both register_path and register_prefix
// funnel through here so the validation/insertion logic lives in one
// place. `family=true` is prefix-matching; `family=false` is
// exact-matching.
void register_impl_(const std::string& path,
std::shared_ptr<http_resource> res,
bool family);
// register_impl_ helpers carved out so the parent stays under the
// project-wide CCN gate.
void validate_register_inputs_(const std::string& path,
const std::shared_ptr<http_resource>& res,
bool family) const;
// Shared unregistration helper. Erases a single registration of the
// requested kind.
void unregister_impl_(const std::string& path, bool family);
// Shared lambda-registration helper. Builds-or-
// merges a hidden detail::lambda_resource shim at @p path, sets every
// bit in @p methods on it, and stores @p handler into each of those
// method slots. All seven public on_* overloads and both public
// route() overloads forward to this single entry point so the
// merge-and-conflict logic lives in one place. Validation is
// atomic: if any requested method already has a slot on the path,
// no slot is mutated and the call throws -- callers therefore see
// either a fully-installed registration or no change at all.
// Throws std::invalid_argument if @p methods is empty, if @p
// handler is empty, if the path conflicts with single_resource
// mode, if a class-based resource is already registered at the
// path, or if a lambda is already registered for any requested
// (method, path).
void on_methods_(method_set methods,
const std::string& path,
std::function<http_response(const http_request&)> handler);
// on_methods_ helpers carved out so the parent stays under the
// project-wide CCN gate.
void validate_on_methods_inputs_(method_set methods,
const std::string& path,
const std::function<http_response(const http_request&)>& handler) const;
// PIMPL: backend-coupled state (MHD daemon, pthread mutexes, route
// table, ban set, route cache, websocket registry, GnuTLS SNI cache,
// and the dispatch helpers / MHD trampolines that operate on those)
// lives behind this pointer in detail/webserver_impl.hpp. The public
// header carries no <microhttpd.h>/<pthread.h>/<gnutls/...> baggage.
std::unique_ptr<detail::webserver_impl> impl_;
// detail::webserver_impl reads the const config bag above (tcp_nodelay,
// unescaper, regex_checking, auth_handler, etc.) when servicing
// requests, and houses the MHD trampolines / dispatch helpers so
// <microhttpd.h> stays out of this public header. Granting friendship
// is preferable to introducing a long list of trivial public getters
// that cross the PIMPL boundary in both directions.
friend class detail::webserver_impl;
// daemon_lifecycle (the extracted MHD daemon-construction collaborator)
// reads the same const config bag when building the option array and
// start flags, so it needs the same friendship as webserver_impl.
friend class detail::daemon_lifecycle;
friend class http_response;
#if defined(HTTPSERVER_COMPILATION)
// Test-only hook so unit tests in test/unit/ can poke
// at the v2 route-table impl (lookup_v2, the three tier maps)
// without widening the public API. The pattern matches the SBO
// test access friend used by http_response. Gated on
// HTTPSERVER_COMPILATION so it never appears in installed headers.
friend struct webserver_test_access;
#endif
};
#if defined(HTTPSERVER_COMPILATION)
// Forward-declared friend giving test code (which compiles with
// HTTPSERVER_COMPILATION via test/Makefile.am AM_CPPFLAGS) a thin
// pointer to the otherwise-private impl_. Defined inline so any TU
// including this header in COMPILATION mode can use it.
struct webserver_test_access {
static detail::webserver_impl* impl(webserver& w) noexcept {
return w.impl_.get();
}
};
#endif
} // namespace httpserver
#endif // SRC_HTTPSERVER_WEBSERVER_HPP_