Skip to content
Open
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
42 changes: 42 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ option(BUILD_BROTLI "Build Brotli" ON)
option(BUILD_YAML_CONFIG "Build yaml config" ON)
option(USE_SUBMODULE "Use trantor as a submodule" ON)
option(USE_STATIC_LIBS_ONLY "Use only static libraries as dependencies" OFF)
option(BUILD_HTTP3 "Build with HTTP/3 (QUIC) support via ngtcp2/nghttp3" OFF)

include(CMakeDependentOption)
CMAKE_DEPENDENT_OPTION(BUILD_POSTGRESQL "Build with postgresql support" ON "BUILD_ORM" OFF)
Expand Down Expand Up @@ -252,6 +253,34 @@ if (BUILD_BROTLI)
endif (Brotli_FOUND)
endif (BUILD_BROTLI)

if (BUILD_HTTP3)
find_package(PkgConfig QUIET)
if (PkgConfig_FOUND)
pkg_check_modules(NGTCP2 IMPORTED_TARGET libngtcp2)
# ngtcp2 v1.x renamed crypto_quictls to crypto_ossl — try both
pkg_check_modules(NGTCP2_CRYPTO IMPORTED_TARGET libngtcp2_crypto_quictls)
if (NOT NGTCP2_CRYPTO_FOUND)
pkg_check_modules(NGTCP2_CRYPTO IMPORTED_TARGET libngtcp2_crypto_ossl)
endif()
pkg_check_modules(NGHTTP3 IMPORTED_TARGET libnghttp3)
endif()
if (NGTCP2_FOUND AND NGTCP2_CRYPTO_FOUND AND NGHTTP3_FOUND)
message(STATUS "HTTP/3 (QUIC) support enabled")
message(STATUS " ngtcp2: ${NGTCP2_VERSION}")
message(STATUS " ngtcp2_crypto: ${NGTCP2_CRYPTO_VERSION}")
message(STATUS " nghttp3: ${NGHTTP3_VERSION}")
target_compile_definitions(${PROJECT_NAME} PUBLIC DROGON_HAS_HTTP3=1)
target_link_libraries(${PROJECT_NAME} PRIVATE
PkgConfig::NGTCP2
PkgConfig::NGTCP2_CRYPTO
PkgConfig::NGHTTP3)
else()
message(WARNING "HTTP/3 requested but ngtcp2/nghttp3 not found. "
"Install libngtcp2, libngtcp2_crypto_quictls, and libnghttp3. "
"HTTP/3 support will be disabled.")
endif()
endif (BUILD_HTTP3)

set(DROGON_SOURCES
lib/src/AOPAdvice.cc
lib/src/AccessLogger.cc
Expand Down Expand Up @@ -502,6 +531,19 @@ if (NOT Hiredis_FOUND)
lib/src/RedisClientManager.h)
endif (NOT Hiredis_FOUND)

if (BUILD_HTTP3 AND NGTCP2_FOUND AND NGTCP2_CRYPTO_FOUND AND NGHTTP3_FOUND)
set(DROGON_SOURCES
${DROGON_SOURCES}
lib/src/QuicServer.cc
lib/src/QuicConnection.cc
lib/src/Http3Handler.cc)
set(private_headers
${private_headers}
lib/src/QuicServer.h
lib/src/QuicConnection.h
lib/src/Http3Handler.h)
endif()

if (BUILD_TESTING)
add_subdirectory(nosql_lib/redis/tests)
endif (BUILD_TESTING)
Expand Down
3 changes: 2 additions & 1 deletion lib/inc/drogon/HttpTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ enum class Version
{
kUnknown = 0,
kHttp10,
kHttp11
kHttp11,
kHttp3
};

enum ContentType
Expand Down
83 changes: 83 additions & 0 deletions lib/src/Http3AltSvcMiddleware.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
*
* @file Http3AltSvcMiddleware.h
* @author S Bala Vignesh
*
* Copyright 2026, S Bala Vignesh. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/

#pragma once

#ifdef DROGON_HAS_HTTP3

#include <drogon/HttpMiddleware.h>
#include <string>

namespace drogon
{

/**
* @brief Middleware that injects the Alt-Svc header into HTTP/1.1 and HTTP/2
* responses to advertise HTTP/3 support to clients.
*
* When a browser receives an "Alt-Svc: h3=\":443\"; ma=86400" header,
* it knows it can upgrade to HTTP/3 for subsequent requests.
*
* Usage in config.json:
* @code
* {
* "middlewares": [
* {
* "name": "drogon::Http3AltSvcMiddleware",
* "config": {
* "port": 443
* }
* }
* ]
* }
* @endcode
*/
class Http3AltSvcMiddleware
: public HttpMiddleware<Http3AltSvcMiddleware>
{
public:
Http3AltSvcMiddleware() = default;

/**
* @brief Set the QUIC port for the Alt-Svc header.
* @param port UDP port where HTTP/3 is available
*/
void setPort(uint16_t port)
{
altSvcValue_ = "h3=\":" + std::to_string(port) + "\"; ma=86400";
}

void invoke(const HttpRequestPtr &req,
MiddlewareNextCallback &&nextCb,
MiddlewareCallback &&mcb) override
{
auto altSvc = altSvcValue_;
nextCb([altSvc = std::move(altSvc),
mcb = std::move(mcb)](const HttpResponsePtr &resp) {
// Only inject Alt-Svc on non-HTTP/3 responses
if (resp && resp->getHeader("alt-svc").empty())
{
resp->addHeader("alt-svc", altSvc);
}
mcb(resp);
});
}

private:
std::string altSvcValue_{"h3=\":443\"; ma=86400"};
};

} // namespace drogon

#endif // DROGON_HAS_HTTP3
120 changes: 120 additions & 0 deletions lib/src/Http3Handler.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
*
* @file Http3Handler.cc
* @author S Bala Vignesh
*
* Copyright 2026, S Bala Vignesh. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/

#ifdef DROGON_HAS_HTTP3

#include "Http3Handler.h"
#include "HttpUtils.h"
#include <trantor/utils/Logger.h>
#include <sstream>

namespace drogon
{
namespace Http3Handler
{

HttpRequestImplPtr createRequest(const std::string &method,
const std::string &path,
const std::string &authority,
const std::string &scheme)
{
auto req = std::make_shared<HttpRequestImpl>(nullptr);
req->setMethod(methodFromString(method));
req->setPath(path);
req->addHeader("host", authority);
req->addHeader(":scheme", scheme);
req->setVersion(drogon::Version::kHttp3);
return req;
}

SerializedHeaders serializeResponseHeaders(const HttpResponsePtr &resp)
{
SerializedHeaders result;

// Status pseudo-header
result.statusStr = std::to_string(resp->statusCode());

// Build nghttp3_nv for :status
nghttp3_nv statusNv;
statusNv.name = reinterpret_cast<uint8_t *>(
const_cast<char *>(":status"));
statusNv.namelen = 7;
statusNv.value = reinterpret_cast<uint8_t *>(
const_cast<char *>(result.statusStr.c_str()));
statusNv.valuelen = result.statusStr.size();
statusNv.flags = NGHTTP3_NV_FLAG_NONE;
result.nva.push_back(statusNv);

// Regular headers
auto respImpl =
std::dynamic_pointer_cast<HttpResponseImpl>(resp);
if (respImpl)
{
for (auto &[name, value] : respImpl->headers())
{
result.headerStorage.emplace_back(name, value);
auto &stored = result.headerStorage.back();

nghttp3_nv nv;
nv.name = reinterpret_cast<uint8_t *>(
const_cast<char *>(stored.first.c_str()));
nv.namelen = stored.first.size();
nv.value = reinterpret_cast<uint8_t *>(
const_cast<char *>(stored.second.c_str()));
nv.valuelen = stored.second.size();
nv.flags = NGHTTP3_NV_FLAG_NONE;
result.nva.push_back(nv);
}
}

return result;
}

std::pair<const uint8_t *, size_t> getResponseBody(
const HttpResponsePtr &resp)
{
auto body = resp->body();
return {reinterpret_cast<const uint8_t *>(body.data()), body.size()};
}

HttpMethod methodFromString(const std::string &method)
{
if (method == "GET")
return HttpMethod::Get;
if (method == "POST")
return HttpMethod::Post;
if (method == "PUT")
return HttpMethod::Put;
if (method == "DELETE")
return HttpMethod::Delete;
if (method == "PATCH")
return HttpMethod::Patch;
if (method == "HEAD")
return HttpMethod::Head;
if (method == "OPTIONS")
return HttpMethod::Options;
return HttpMethod::Invalid;
}

std::string getAltSvcHeaderValue(uint16_t port)
{
std::ostringstream oss;
oss << "h3=\":" << port << "\"; ma=86400";
return oss.str();
}

} // namespace Http3Handler
} // namespace drogon

#endif // DROGON_HAS_HTTP3
96 changes: 96 additions & 0 deletions lib/src/Http3Handler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
*
* @file Http3Handler.h
* @author S Bala Vignesh
*
* Copyright 2026, S Bala Vignesh. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/

#pragma once

#ifdef DROGON_HAS_HTTP3

#include "HttpRequestImpl.h"
#include "HttpResponseImpl.h"
#include <nghttp3/nghttp3.h>
#include <string>
#include <vector>
#include <utility>

namespace drogon
{

/**
* @brief Http3Handler provides utility functions for converting between
* HTTP/3 (nghttp3) representations and Drogon's HttpRequest/HttpResponse.
*
* It acts as the "glue" between the QUIC/HTTP/3 protocol layer and
* Drogon's existing request processing pipeline.
*/
namespace Http3Handler
{

/**
* @brief Create a Drogon HttpRequestImpl from HTTP/3 pseudo-headers.
* @param method The HTTP method (e.g., "GET", "POST")
* @param path The request path
* @param authority The authority (host) header
* @param scheme The scheme ("https")
* @return A new HttpRequestImpl ready for header population
*/
HttpRequestImplPtr createRequest(const std::string &method,
const std::string &path,
const std::string &authority,
const std::string &scheme);

/**
* @brief Serialize an HttpResponse into nghttp3 header name-value pairs
* for transmission over HTTP/3.
* @param resp The response to serialize
* @return A vector of nghttp3_nv header pairs
* @note The returned nv pairs reference memory in the returned strings.
* The caller must keep the strings alive until headers are sent.
*/
struct SerializedHeaders
{
std::vector<nghttp3_nv> nva;
// Storage for header strings to keep them alive
std::string statusStr;
std::vector<std::pair<std::string, std::string>> headerStorage;
};

SerializedHeaders serializeResponseHeaders(const HttpResponsePtr &resp);

/**
* @brief Get the response body as a contiguous buffer.
* @param resp The response
* @return Pointer and length of the body data
*/
std::pair<const uint8_t *, size_t> getResponseBody(
const HttpResponsePtr &resp);

/**
* @brief Convert an HTTP method string to Drogon's HttpMethod enum.
* @param method The method string (e.g., "GET")
* @return The corresponding HttpMethod
*/
HttpMethod methodFromString(const std::string &method);

/**
* @brief Get the Alt-Svc header value for advertising HTTP/3 support.
* @param port The UDP port for HTTP/3
* @return The Alt-Svc header value string (e.g., 'h3=":443"')
*/
std::string getAltSvcHeaderValue(uint16_t port);

} // namespace Http3Handler

} // namespace drogon

#endif // DROGON_HAS_HTTP3
6 changes: 6 additions & 0 deletions lib/src/HttpRequestImpl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,12 @@ const char *HttpRequestImpl::versionString() const
result = "HTTP/1.1";
break;

#ifdef DROGON_HAS_HTTP3
case Version::kHttp3:
result = "HTTP/3";
break;
#endif

default:
break;
}
Expand Down
Loading