[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/etr/libhttpserver/master/src/httpserver/webserver.hpp [Back]  [Original]

/*
     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_

Web Proxy Viewer  |  New URL  |  Original Page