/*
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 or can be included directly."
#endif
#ifndef SRC_HTTPSERVER_WEBSERVER_HPP_
#define SRC_HTTPSERVER_WEBSERVER_HPP_
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
#else
#include
#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..
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 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 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& 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 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& 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 // baggage.
std::unique_ptr 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
// 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_