Boost C++ Libraries

PrevUpHomeNext

Class template basic_http

boost::taar::session::basic_http — Per-connection HTTP session coroutine.

Synopsis

// In header: <boost/taar/session/http.hpp>

template<typename Context = no_context, 
         typename Stream = boost::beast::tcp_stream> 
class basic_http {
public:
  // types
  typedef Context context_type;  // The per-connection context type carried by this session. 

  // public member functions
  basic_http();
  void set_method_not_allowed(bool);
  void set_head_from_get(bool);
  void set_response_filter(unspecified);
  void set_date_header(bool);
  void set_idle_timeout(std::chrono::steady_clock::duration);
  void set_header_timeout(std::chrono::steady_clock::duration);
  void set_body_timeout(std::chrono::steady_clock::duration);
  void set_send_timeout(std::chrono::steady_clock::duration);
  void set_request_header_limit(std::uint32_t);
  void set_request_body_limit(boost::optional< std::uint64_t >);
  basic_http(basic_http const &) = delete;
  basic_http(basic_http &&) = default;
  basic_http & operator=(basic_http const &) = delete;
  basic_http & operator=(basic_http &&) = default;
  ~basic_http() = default;
  awaitable< void > 
  operator()(boost::asio::ip::tcp::socket, cancellation_signals &);
  awaitable< void > operator()(Stream, cancellation_signals &);
  template<typename MatcherType, typename RequestHandler> 
    auto register_request_handler(MatcherType &&, RequestHandler);
  template<typename MatcherType, typename RequestHandler> 
    auto register_request_handler(MatcherType &&, RequestHandler);
  template<typename Operand, typename RequestHandler> 
    auto install_handler(Operand, RequestHandler);
  template<typename MatcherType, typename RequestHandler, 
           typename FirstObserver, typename... Observers> 
    auto register_request_handler(MatcherType &&, RequestHandler, 
                                  FirstObserver &&, Observers &&...);
  template<typename MatcherType, typename ObjectType, typename ResultType, 
           typename... ArgsType> 
    auto register_request_handler(MatcherType &&, 
                                  ResultType(ObjectType::*)(ArgsType...), 
                                  ObjectType *);
  template<typename MatcherType, typename ObjectType, typename ResultType, 
           typename... ArgsType> 
    auto register_request_handler(MatcherType &&, 
                                  ResultType(ObjectType::*)(ArgsType...) const, 
                                  ObjectType const *);
  template<typename MatcherType, typename RawHandler> 
    auto register_raw_handler(MatcherType &&, RawHandler);
  template<typename Interceptor> void add_interceptor(Interceptor);
  template<typename MatcherType, typename Interceptor> 
    void add_interceptor(MatcherType &&, Interceptor);
  template<unspecified HandlerType> void set_soft_error_handler(HandlerType);
  template<unspecified HandlerType> void set_hard_error_handler(HandlerType);
};

Description

A single http instance is shared across every TCP connection accepted by boost::taar::server::tcp. Each accepted socket is dispatched to the call operator, which reads requests in a keep-alive loop, walks the registered matchers in order, runs the first matching handler, writes the response, and continues with the next request on the same connection until the client closes, the response opts out of keep-alive, or cancellation is requested.

Register routes with register_request_handler. Each route is a matcher (see the boost::taar::matcher namespace) paired with a handler. The handler can be:

Cross-cutting checks (authentication, request ids, rate limits) are registered with add_interceptor; they run on every request header before routing and can reject it before its body is read.

Protocol upgrades (WebSocket, HTTP/2 etc.) are supported through register_raw_handler, which hands the socket, the parsed header, and any post-header bytes to the handler and then closes the session cleanly.

Two error-reporting hooks are exposed:

Slow or idle clients are bounded by four timeouts: set_idle_timeout (between requests), set_header_timeout (a request header), set_body_timeout (a request body) and set_send_timeout (a blocked write). Every response carries a Date header unless set_date_header disables it.

http is move-constructible but not copyable; share one instance among spawned sessions by reference.

Template Parameters

  1. typename Context = no_context

    Per-connection state type. When set to anything other than no_context, the session creates one Context (via std::make_shared) for every accepted TCP connection and keeps it alive across all keep-alive requests on that connection. Handlers opt in through leading parameters of type Context&, Context const& or std::shared_ptr<Context>; handlers without such a parameter are unaffected. The default alias, http, uses no_context and creates nothing per connection.

  2. typename Stream = boost::beast::tcp_stream

    The connection stream type. boost::beast::tcp_stream for plain HTTP (the default); session::tls_stream from <boost/taar/session/https.hpp> for HTTPS, where the boost::taar::server::tls acceptor performs the handshake before handing the stream over.

basic_http public member functions

  1. basic_http();
  2. void set_method_not_allowed(bool enabled);
    Answer 405 Method Not Allowed (with an Allow header) instead of 404 when no route matches but some route accepts the path under another method.

    Enabled by default.

    Only routes whose matcher is a conjunction of one target == ... and method == ... constraints contribute; opaque or negated matchers neither cause a 405 nor appear in Allow.

  3. void set_head_from_get(bool enabled);
    Serve HEAD requests through a matching GET route when no route matches HEAD itself.

    Enabled by default.

    The handler runs with the original HEAD request and only the response header is written, so Content-Length reflects the GET body. Handlers producing a message_generator write what they produce; a streaming handler's chunked header is sent without chunks. GET routes list HEAD in Allow.

  4. void set_response_filter(unspecified filter);
    Install a hook that runs on every response header before it is written.

    Applies to handler responses, interceptor rejections, soft-error responses, the stock 404 and the header of chunked responses; raw handlers that return a message_generator are opaque and bypass it. Use it for CORS (taar::cors::response_filter()), a Server header, or security headers.

  5. void set_date_header(bool enabled);
    Add a Date header (RFC 9110 6.6.1) to every response that does not carry one.

    Enabled by default.

    Applies wherever the response filter does (handler responses, interceptor rejections, soft-error and protocol-error answers, 404/405, 408, 304 and HEAD responses, the header of chunked responses) and runs after the filter, so a Date set by the handler or the filter is kept. A message_generator is opaque and written as produced. The value is formatted at most once per second per thread.

  6. void set_idle_timeout(std::chrono::steady_clock::duration timeout);
    Close a connection that stays idle this long waiting for the first byte of a request: the first one, or the next on a keep-alive connection (nginx keepalive_timeout).

    Expiry closes the connection silently. Default 60 seconds; zero disables it.

  7. void set_header_timeout(std::chrono::steady_clock::duration timeout);
    Bound the time from a request's first byte until its header is complete (nginx client_header_timeout).

    Expiry answers 408 Request Timeout with Connection: close (through the response filter) and closes. Default 30 seconds; zero disables it.

  8. void set_body_timeout(std::chrono::steady_clock::duration timeout);
    Bound the inactivity while a request body is read: each read, buffered by the session or made through body_reader / multipart_reader by a streaming handler, waits at most this long for data (nginx client_body_timeout).

    Expiry answers 408 Request Timeout and closes; a streaming handler sees an exception carrying beast::error::timeout, which the default soft error handler answers with 408. Body reads are shielded from shutdown, so this also bounds how long an upload stalled by its client can hold up a graceful shutdown. Default 60 seconds; zero disables it.

  9. void set_send_timeout(std::chrono::steady_clock::duration timeout);
    Bound how long a single write may stay blocked, for example on a client that stopped reading (nginx send_timeout).

    Expiry closes the connection. Only time spent in a write counts: neither the handler's execution nor the time a streaming generator takes between chunks does. Default 60 seconds; zero disables it.

  10. void set_request_header_limit(std::uint32_t limit);
    Cap the bytes consumed by a request header. Default 8 KiB.
  11. void set_request_body_limit(boost::optional< std::uint64_t > limit);
    Cap the bytes consumed by a request body. boost::none disables the cap. Default 1 MiB.
  12. basic_http(basic_http const &) = delete;
  13. basic_http(basic_http &&) = default;
    Movable until the first connection is served: registered handlers and error-handler wrappers do not refer to the object they were installed on.
  14. basic_http & operator=(basic_http const &) = delete;
  15. basic_http & operator=(basic_http &&) = default;
  16. ~basic_http() = default;
  17. awaitable< void > 
    operator()(boost::asio::ip::tcp::socket socket, 
               cancellation_signals & signals);
    Drive one accepted connection to completion.

    Called by boost::taar::server::tcp for every accepted socket. Owns the socket, parses request headers, dispatches to matching handlers, writes responses, honours keep-alive, and terminates on cancellation, peer close, or after a non-keep-alive response. The session runs on the socket's executor (the connection strand when called by the server).

  18. awaitable< void > operator()(Stream stream, cancellation_signals & signals);
    Drive one connection on an already established stream.

    Used by boost::taar::server::tls after the handshake, and by any transport that builds its own stream (Stream is the class template parameter, boost::beast::tcp_stream by default). On exit a TLS stream receives close_notify before the TCP shutdown.

  19. template<typename MatcherType, typename RequestHandler> 
      auto register_request_handler(MatcherType &&, RequestHandler);
  20. template<typename MatcherType, typename RequestHandler> 
      auto register_request_handler(MatcherType && matcher, 
                                    RequestHandler request_handler);
    Register a request handler paired with a matcher.

    Routes are evaluated in registration order on every request. The first matcher that returns true wins, the corresponding handler runs, and its response is written back to the client.

    The handler may be:

    • A rest adapter,

    • A htdocs handler,

    • A raw callable accepting (boost::beast::http::request<Body> const&, taar::matcher::context const&) and returning a message_generator (synchronously or as an awaitable). The body type drives how the session reads the request body.

    Parameters:

    matcher

    A matcher object satisfying the boost::taar::matcher::is_matcher concept.

    request_handler

    The handler callable; must be move-constructible.

  21. template<typename Operand, typename RequestHandler> 
      auto install_handler(Operand operand, RequestHandler request_handler);
    Install a route whose registration has already been validated.
  22. template<typename MatcherType, typename RequestHandler, 
             typename FirstObserver, typename... Observers> 
      auto register_request_handler(MatcherType && matcher, 
                                    RequestHandler request_handler, 
                                    FirstObserver && first_observer, 
                                    Observers &&... observers);
    Register a handler and notify registration observers.

    Each trailing observer is invoked once, at registration time, with (matcher::operand const&, RequestHandler const&), before the route is installed. Documentation generators use this to record the route's matcher metadata and the handler's parameters:

    session.register_request_handler(
        method == http::verb::get && target == "/users/{id}",
        taar::handler::rest(&get_user, taar::handler::path_arg("id")),
        openapi.describe("Fetch a user"));
    

  23. template<typename MatcherType, typename ObjectType, typename ResultType, 
             typename... ArgsType> 
      auto register_request_handler(MatcherType && matcher, 
                                    ResultType(ObjectType::*)(ArgsType...) memfn, 
                                    ObjectType * object);
    Convenience overload that binds a non-const member-function pointer to object.
  24. template<typename MatcherType, typename ObjectType, typename ResultType, 
             typename... ArgsType> 
      auto register_request_handler(MatcherType && matcher, 
                                    ResultType(ObjectType::*)(ArgsType...) const memfn, 
                                    ObjectType const * object);
    Convenience overload that binds a const member-function pointer to object.
  25. template<typename MatcherType, typename RawHandler> 
      auto register_raw_handler(MatcherType && matcher, RawHandler raw_handler);
    Register a "raw" handler that takes ownership of the connection on match.

    Used for protocol upgrades such as WebSocket. On a match the handler receives the parsed request header, the underlying stream (a plain boost::beast::tcp_stream, or the plain boost::beast::ssl_stream<boost::beast::tcp_stream> named session::tls_stream for an https session), and the flat_buffer holding any bytes already read past the header. The session performs no further I/O on the stream and ends the session as soon as the handler returns. The stream's default completion token is Asio's, as is that of a beast::websocket::stream<beast::tcp_stream> built on it: pass a token such as taar::use_awaitable explicitly.

    The handler signature must be compatible with:

    awaitable<void>(
        matcher::context const& context,
        boost::beast::http::request<boost::beast::http::empty_body>&& request,
        Stream&& stream,
        boost::beast::flat_buffer&& buffer,
        cancellation_signals& signals)
    
    [Note] Note

    boost::beast::flat_buffer is value-semantic. Passing it directly to asio::async_read_until / asio::async_read would read into a copy rather than the buffer itself. Pair the buffer with beast::websocket::stream::async_accept (the natural use case) or drain pipelined bytes into a std::string and use boost::asio::dynamic_buffer for further reads.

  26. template<typename Interceptor> void add_interceptor(Interceptor interceptor);
    Run interceptor on every request, before routing.

    Interceptors run in registration order right after the request header is parsed and validated: before a route is selected, before the 405/404 answers and HEAD-from-GET handling, before any body byte is read, and for raw handlers too. The callable takes leading context parameters injected by type (the session's Context&, Context const& or std::shared_ptr<Context>), then (boost::beast::http::request_header<> const&, matcher::context&), and returns taar::interceptor_result, directly or as an awaitable. It must not be a generic lambda.

    • taar::proceed() (std::nullopt) passes the request on to the next interceptor and then to routing. Attributes stored in matcher::context::attributes are visible to the handler that runs.

    • taar::reject(status, text) or any response short-circuits: later interceptors and the handler do not run, and the response is written with its version and keep-alive aligned to the request and the response filter applied (so CORS headers reach a browser on a 401). If the request declares a body, the body is not read and the connection closes after the rejection.

    • An exception goes to the soft error handler, exactly as a handler exception does.

    Interceptors run shielded from cancellation like plain handlers; a shutdown requested meanwhile makes the request's answer close the connection.

    session.add_interceptor([](http::request_header<> const& req, taar::matcher::context& ctx)
    {
        auto const auth = req[http::field::authorization];
        if (!auth.starts_with("Bearer "))
            return taar::reject(http::status::unauthorized);
        ctx.attributes.set(principal {std::string {auth.substr(7)}});
        return taar::proceed();
    });
    

    The registered callable does not refer to the session, which stays movable.

  27. template<typename MatcherType, typename Interceptor> 
      void add_interceptor(MatcherType && matcher, Interceptor interceptor);
    Run interceptor on the requests matcher accepts.

    Same as the one-argument overload, but the interceptor runs only when the matcher accepts the request header. The matcher is evaluated the way routes are; path arguments a target template extracts go into a scratch context that is discarded, and the interceptor receives the request's own context. Typical uses:

    // Let CORS preflights through unauthenticated.
    session.add_interceptor(!(method == http::verb::options), require_token);
    // Keep the health check public.
    session.add_interceptor(!(target == "/health"), require_token);
    

  28. template<unspecified HandlerType> 
      void set_soft_error_handler(HandlerType handler);
    Install a custom soft-error handler.

    The handler is invoked as handler(taar::failure const&) when a registered request handler throws. The session wraps the escaped exception with boost::taar::failure::from_exception once: a boost::taar::exception yields its own failure, a user's exception is kept and can be recognised with boost::taar::failure::as. The handler returns anything boost::taar::response_from accepts, synchronously or as an awaitable; the response is written to the client and determines whether the session continues (keep_alive() == true) or closes. A handler that throws escalates to the hard error handler.

    session.set_soft_error_handler([](taar::failure const& f)
    {
        if (auto const e = f.as<not_found>())
            return taar::response_builder(e->what()).set_status(http::status::not_found);
        return taar::response_builder(f.text()).set_status(taar::http_status_for(f));
    });
    

    The default handler answers failures that boost::taar::http_status_for maps to a 4xx status with that status and f.text() as a text/plain body, and escalates every other failure (user exceptions, internal codes) to the hard error handler.

  29. template<unspecified HandlerType> 
      void set_hard_error_handler(HandlerType handler);
    Install a custom hard-error handler.

    Invoked as handler(taar::failure const&) when the session aborts because of a connection-level failure (a transport error, a header that is too large, an I/O exception), when a streamed response fails after its header was sent, or when the soft error handler escalates (the default does so for user exceptions; the hard handler then sees the same failure). Transport errors carry their error_code; exceptions are wrapped by boost::taar::failure::from_exception. The handler is expected to log or otherwise report the failure (f.chain() renders it with category tags); the connection is already being torn down by the time the handler runs.

    The default handler silently discards the exception, which is almost never the right choice in production; install at least a logging hook.


PrevUpHomeNext