Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"spdlog",
"SSLKEYLOGFILE",
"STREQUAL",
"sveccnt",
"testcases",
"TLSEXT",
"TSAN",
Expand Down
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ add_compile_definitions(BOOST_PROCESS_V2_DISABLE_NOTIFY_FORK)
# we don't use fmtlib any more
add_compile_definitions(SPDLOG_USE_STD_FORMAT)

# don't allow use of deprecated ASIO interfaces
add_compile_definitions(BOOST_ASIO_NO_DEPRECATED)

# needed for ranges/v3 starting with clang 19
add_compile_options(-Wno-deprecated-declarations)

Expand Down
2 changes: 2 additions & 0 deletions include/anyhttp/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ inline Fields fields(std::initializer_list<std::pair<std::string_view, FieldValu
return result;
}

// =================================================================================================

using ReadSome = void(boost::system::error_code, size_t);
using ReadSomeHandler = asio::any_completion_handler<ReadSome>;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
#pragma once

//
// Definitions of the HTTP/2 session templates, instantiated only by src/h2_session.cpp -- the
// factories in anyhttp/h2_backend.hpp are what the generic server and client use instead.
//

#include "anyhttp/any_async_stream.hpp"
#include "anyhttp/nghttp2_session.hpp"
#include "anyhttp/h2_session.hpp"
#include "anyhttp/session.hpp"

#include <boost/asio/basic_stream_socket.hpp>
Expand Down Expand Up @@ -39,7 +45,7 @@ void NGHttp2SessionImpl<Stream>::destroy() noexcept
{
// post(get_executor(), [this, self]() mutable {
boost::system::error_code ec;
std::ignore = get_socket(m_stream).shutdown(socket_base::shutdown_both, ec);
get_socket(m_stream).shutdown(socket_base::shutdown_both, ec);
logwi(ec, "[{}] destroy: socket shutdown: {}", m_logPrefix, ec.message());
// });
}
Expand Down
56 changes: 0 additions & 56 deletions include/anyhttp/formatter.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#pragma once

#include <nghttp2/nghttp2.h>

#include <boost/asio/cancellation_type.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ip/udp.hpp>
Expand Down Expand Up @@ -122,57 +120,3 @@ struct std::formatter<boost::asio::cancellation_type> : std::formatter<std::stri
};

// =================================================================================================

/// Formats an HTTP/2 name-value pair (nghttp2_nv).
/// Format specifiers: 'n' = name only, 'v' = value only, default = "name=value".
/// Example: {:n} → "content-type", {:v} → "application/json", {} → "content-type=application/json"
template <>
struct std::formatter<nghttp2_nv>
{
enum class part
{
name_and_value,
name,
value
} what = part::name_and_value;

constexpr auto parse(std::format_parse_context& ctx)
{
auto it = ctx.begin();
if (it == ctx.end())
return it;

if (*it == 'n')
{
what = part::name;
++it;
}
else if (*it == 'v')
{
what = part::value;
++it;
}

if (it != ctx.end() && *it != '}')
throw std::format_error("invalid format args for nghttp2_nv, expected 'n' or 'v'");

return it;
}

auto format(const nghttp2_nv& nv, std::format_context& ctx) const
{
auto out = ctx.out();
if (what == part::name || what == part::name_and_value)
std::ranges::copy(rv::counted(nv.name, nv.namelen), out);

if (what == part::name_and_value)
*out++ = '=';

if (what == part::value || what == part::name_and_value)
std::ranges::copy(rv::counted(nv.value, nv.valuelen), out);

return out;
}
};

// =================================================================================================
46 changes: 46 additions & 0 deletions include/anyhttp/h1_backend.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#pragma once

//
// The HTTP/1.1 backend as seen from the generic server and client: factories that turn a stream
// that is ready to carry HTTP/1.1 into a Session::Impl. Everything else about the backend --
// beast's parser, serializer and the session templates driving them -- stays in h1_session.hpp
// and h1_session.cpp, which are the only places instantiating them.
//

#include "anyhttp/any_async_stream.hpp"
#include "anyhttp/client_impl.hpp"
#include "anyhttp/server_impl.hpp"
#include "anyhttp/session_impl.hpp"

#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>

#include <memory>

namespace anyhttp::beast_impl
{

// =================================================================================================

using SslStream = boost::asio::ssl::stream<boost::asio::ip::tcp::socket>;

std::shared_ptr<Session::Impl> make_server_session(server::Server::Impl& server,
boost::asio::any_io_executor executor,
SslStream&& stream);

std::shared_ptr<Session::Impl> make_server_session(server::Server::Impl& server,
boost::asio::any_io_executor executor,
AnyAsyncStream&& stream);

std::shared_ptr<Session::Impl> make_server_session(server::Server::Impl& server,
boost::asio::any_io_executor executor,
boost::asio::ip::tcp::socket&& socket);

std::shared_ptr<Session::Impl> make_client_session(client::Client::Impl& client,
boost::asio::any_io_executor executor,
boost::asio::ip::tcp::socket&& socket);

// =================================================================================================

} // namespace anyhttp::beast_impl
File renamed without changes.
46 changes: 46 additions & 0 deletions include/anyhttp/h2_backend.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#pragma once

//
// The HTTP/2 backend as seen from the generic server and client: factories that turn a stream
// that is ready to carry HTTP/2 into a Session::Impl. Everything else about the backend --
// nghttp2 and the session templates driving it -- stays behind h2_session.hpp
// and h2_session.cpp, so that dispatching to HTTP/2 needs no nghttp2 type here.
//

#include "anyhttp/any_async_stream.hpp"
#include "anyhttp/client_impl.hpp"
#include "anyhttp/server_impl.hpp"
#include "anyhttp/session_impl.hpp"

#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>

#include <memory>

namespace anyhttp::nghttp2
{

// =================================================================================================

using SslStream = boost::asio::ssl::stream<boost::asio::ip::tcp::socket>;

std::shared_ptr<Session::Impl> make_server_session(server::Server::Impl& server,
boost::asio::any_io_executor executor,
SslStream&& stream);

std::shared_ptr<Session::Impl> make_server_session(server::Server::Impl& server,
boost::asio::any_io_executor executor,
AnyAsyncStream&& stream);

std::shared_ptr<Session::Impl> make_server_session(server::Server::Impl& server,
boost::asio::any_io_executor executor,
boost::asio::ip::tcp::socket&& socket);

std::shared_ptr<Session::Impl> make_client_session(client::Client::Impl& client,
boost::asio::any_io_executor executor,
boost::asio::ip::tcp::socket&& socket);

// =================================================================================================

} // namespace anyhttp::nghttp2
95 changes: 95 additions & 0 deletions include/anyhttp/h2_common.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#pragma once

//
// Shared HTTP/2 building blocks: the bits of nghttp2 glue that more than one of the h2_* files
// needs. This is the only place outside them where <nghttp2/nghttp2.h> is pulled in -- the
// generic server, client and formatter code knows nothing about nghttp2.
//

#include <nghttp2/nghttp2.h>

#include <algorithm>
#include <format>
#include <ranges>
#include <string_view>

namespace anyhttp
{

// =================================================================================================

// Create nghttp2_nv from string literal |name| and std::string |value|.
// FIXME: don't use this, it is dangerous (prone to dangling string references)
template <size_t N>
nghttp2_nv make_nv_ls(const char (&name)[N], std::string_view value)
{
return {(uint8_t*)name, (uint8_t*)value.data(), N - 1, value.size(),
NGHTTP2_NV_FLAG_NO_COPY_NAME};
}

inline nghttp2_nv make_nv_ls(std::string_view key, std::string_view value)
{
return {(uint8_t*)key.data(), (uint8_t*)value.data(), key.size(), value.size(), 0};
}

// =================================================================================================

} // namespace anyhttp

// =================================================================================================

/// Formats an HTTP/2 name-value pair (nghttp2_nv).
/// Format specifiers: 'n' = name only, 'v' = value only, default = "name=value".
/// Example: {:n} → "content-type", {:v} → "application/json", {} → "content-type=application/json"
template <>
struct std::formatter<nghttp2_nv>
{
enum class part
{
name_and_value,
name,
value
} what = part::name_and_value;

constexpr auto parse(std::format_parse_context& ctx)
{
auto it = ctx.begin();
if (it == ctx.end())
return it;

if (*it == 'n')
{
what = part::name;
++it;
}
else if (*it == 'v')
{
what = part::value;
++it;
}

if (it != ctx.end() && *it != '}')
throw std::format_error("invalid format args for nghttp2_nv, expected 'n' or 'v'");

return it;
}

auto format(const nghttp2_nv& nv, std::format_context& ctx) const
{
namespace rv = std::ranges::views;

auto out = ctx.out();
if (what == part::name || what == part::name_and_value)
std::ranges::copy(rv::counted(nv.name, nv.namelen), out);

if (what == part::name_and_value)
*out++ = '=';

if (what == part::value || what == part::name_and_value)
std::ranges::copy(rv::counted(nv.value, nv.valuelen), out);

return out;
}
};

// =================================================================================================
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#include "anyhttp/common.hpp"
#include "client_impl.hpp"
#include "nghttp2_stream.hpp"
#include "h2_stream.hpp"
#include "server_impl.hpp"
#include "session_impl.hpp"

Expand Down
File renamed without changes.
65 changes: 65 additions & 0 deletions include/anyhttp/h3_backend.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#pragma once

//
// The HTTP/3 backend as seen from the generic server and client. ngtcp2 and nghttp3 stay behind
// this header, inside the h3_* files: on the server side as an opaque Http3Server owning the
// shared UDP socket, its receive loop and the QUIC connection-ID demux (src/h3_server.cpp), on
// the client side as a single coroutine that establishes a QUIC connection and hands back the
// session running on it (src/h3_client.cpp).
//

#include "anyhttp/server_impl.hpp"
#include "anyhttp/session_impl.hpp"

#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/ip/udp.hpp>

#include <memory>
#include <string>

namespace anyhttp::server
{

// =================================================================================================

//
// The server's HTTP/3 half: one UDP socket shared by all QUIC connections, the receive loop
// de-multiplexing datagrams onto them by connection ID, and the connections themselves.
//
// Sessions register with the owning Server::Impl just like the TCP-based ones, so they
// take part in server-wide shutdown.
//
class Http3Server
{
public:
virtual ~Http3Server() = default;

/// Starts the UDP receive loop.
virtual void start() = 0;

/// Closes the socket and tears down all QUIC connections, each sending a CONNECTION_CLOSE.
virtual void destroy() = 0;
};

/// Binds the UDP socket for HTTP/3 to `endpoint`, usually the address and port the TCP acceptor
/// is already listening on, so that all three protocols share one endpoint.
std::shared_ptr<Http3Server> make_http3_server(Server::Impl& server,
const boost::asio::ip::udp::endpoint& endpoint);

// =================================================================================================

} // namespace anyhttp::server

namespace anyhttp::client
{

// =================================================================================================

/// Connects to `host`:`port` over QUIC and returns the HTTP/3 session running on it.
boost::asio::awaitable<std::shared_ptr<Session::Impl>>
async_connect_http3(boost::asio::any_io_executor executor, std::string host, std::string port);

// =================================================================================================

} // namespace anyhttp::client
Loading
Loading