Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0834e4f
Fix PKI intermediate chains.
pgit Aug 1, 2026
cd0512a
HTTP/3: initial ngtcp2/nghttp3 server (handshake + hardcoded 200 OK)
pgit Aug 1, 2026
ba6c134
HTTP/3: wire ngtcp2/nghttp3 into anyhttp Session::Impl abstraction
pgit Aug 1, 2026
5ec9118
HTTP/3: fix listen_udp address family + add ExternalH3 tests
pgit Aug 1, 2026
15c0633
devcontainer: bump QUIC/TLS lib versions, enable HTTP/3 curl build
pgit Aug 2, 2026
7a278b9
PKI: add 127.0.0.2 to server/client SAN list, regenerate certs
pgit Aug 2, 2026
fa06d29
HTTP/3: graceful QUIC connection close + UDP_GRO datagram splitting
pgit Aug 2, 2026
09d8bd3
HTTP/3: extend ExternalH3 tests, point at built curl
pgit Aug 2, 2026
0ed3791
Update to cpp-devcontainer 0.26
pgit Aug 3, 2026
dd76ef9
Use anyhttp:testing and build custom curl for HTTP/3
pgit Aug 3, 2026
fb2d767
HTTP/3: implement client, parametrize ClientAsync tests over h3
pgit Aug 3, 2026
ec5c82e
HTTP/3: merge ExternalH3 into External, split out single-protocol tests
pgit Aug 3, 2026
654ffcc
Use --cacert instead of -k for all curl_https/curl_multiple_https pro…
pgit Aug 3, 2026
385c938
Make h2load_http3 uniform with h2load_http11/http2 (n=100, c=4, m=3)
pgit Aug 3, 2026
c9af54a
Move h2load into External's Protocol::* parametrization
pgit Aug 3, 2026
7a5e166
Inline check_h2load_output into the h2load test
pgit Aug 3, 2026
c2c624b
Split TLS-only/all-protocol external tests into ExternalTLS
pgit Aug 3, 2026
3e962b6
Manual testcase cleanup. Separated TLS from non-TLS tests (HTTP/3 alw…
pgit Aug 3, 2026
8038bd0
Revert protocol names (http11, h2, h3)
pgit Aug 3, 2026
4f06c1e
Implement GSO support for UDP packet sending and update write_streams…
pgit Aug 4, 2026
ebc0ae8
Fix ASAN heap-use-after-free errors in HTTP/3 stream teardown
pgit Aug 4, 2026
addb547
Disable address sanitizer options in Debug build configuration
pgit Aug 4, 2026
b655806
HTTP/3: implement real write/read backpressure and fix stream write d…
pgit Aug 4, 2026
843593f
HTTP/3: chunked write path, one write at a time, honest stream teardown
pgit Aug 4, 2026
fa19836
HTTP/3: fix use-after-frees and hangs in stream teardown
pgit Aug 4, 2026
f6bda77
Add fields() helper for building small header sets inline
pgit Aug 19, 2026
8b4afbc
HTTP/2: log submitted headers with stream prefix and colour
pgit Aug 19, 2026
fba8a0b
tests: tighten SendMoreThanContentLength, tidy fixture and dead code
pgit Aug 19, 2026
8dc152f
Ignore keylog.log, spell-check "demuxed", whitespace
pgit Aug 19, 2026
c3177ef
PKI: regenerate server certificate
pgit Aug 19, 2026
7bba07c
add generate function for creating payloads of specified length
pgit Aug 19, 2026
34865be
update Dockerfile base images to version 0.27 and AWS_LC_VERSION to 5…
pgit Aug 19, 2026
274ff05
Merge branch 'master' into http3
pgit Aug 19, 2026
fff6730
github actions: update Dockerfile base image to version 0.27
pgit Aug 19, 2026
5ca366a
CI: update container image to 0.27, fix ASAN options
pgit Aug 19, 2026
d56d226
Generate test PKI at build time instead of committing certificates
pgit Aug 19, 2026
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
13 changes: 12 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@
"ASAN",
"ASIO",
"callgrind",
"ccerr",
"cfssl",
"coro",
"darkbase",
"dcid",
"dcidlen",
"demuxed",
"ECDH",
"Eisenlohr",
"epoll",
"gencert",
Expand All @@ -32,22 +37,28 @@
"mylogd",
"nghttp",
"ngtcp",
"ossl",
"PROFDATA",
"PROFRAW",
"psedoc",
"pynghttp",
"qpack",
"QUIC",
"rcbuf",
"RECVTOS",
"respawn",
"RESPAWNED",
"respawning",
"scid",
"SCIDLEN",
"scids",
"spdlog",
"SSLKEYLOGFILE",
"STREQUAL",
"testcases",
"TLSEXT",
"TSAN"
"TSAN",
"WOULDBLOCK"
],
"ignorePaths": [
".devcontainer/*",
Expand Down
12 changes: 6 additions & 6 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# The dependencies of this project have been moved into another Dockerfile that can be built
# separately. This image can be pushed to the HUB, speeding up codespace creation considerably.
#
FROM docker.io/psedoc/anyhttp:0.26
FROM docker.io/psedoc/anyhttp:0.27

#
# install some more interactive utils in the devcontainer
Expand All @@ -16,13 +16,13 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && \
# TEST: claude code
#
# Install Node.js, npm, and git
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && \
apt-get -y install nodejs npm git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && \
# apt-get -y install nodejs npm git && \
# apt-get clean && \
# rm -rf /var/lib/apt/lists/*

# Install Claude Code
RUN npm install -g @anthropic-ai/claude-code
# RUN npm install -g @anthropic-ai/claude-code

#
# enable persistent bash history in container
Expand Down
28 changes: 14 additions & 14 deletions .devcontainer/base/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# https://github.com/microsoft/vscode-dev-containers/tree/main/containers/debian
# https://github.com/pgit/cpp-devcontainer
#
FROM docker.io/psedoc/cpp-devcontainer:0.26
FROM docker.io/psedoc/cpp-devcontainer:0.27

#
# GDB libc++ pretty printer (works for string, but not for map with llvm-18)
Expand Down Expand Up @@ -66,7 +66,7 @@ ENV LD_LIBRARY_PATH=/usr/local/lib
# With debian trixie, we now have OpenSSL 3 with QUIC support. Whether or not that is any good,
# seems to be up for debate: https://www.haproxy.com/blog/state-of-ssl-stacks
#
ARG AWS_LC_VERSION=v1.72.0
ARG AWS_LC_VERSION=v5.5.0
RUN git clone --depth 1 -b ${AWS_LC_VERSION} https://github.com/aws/aws-lc && \
cd aws-lc && \
cmake -B build -DDISABLE_GO=ON --install-prefix=/opt/boringssl && \
Expand All @@ -75,7 +75,7 @@ RUN git clone --depth 1 -b ${AWS_LC_VERSION} https://github.com/aws/aws-lc && \
cd .. && \
rm -rf aws-lc

ARG NGHTTP3_VERSION=v1.15.0
ARG NGHTTP3_VERSION=v1.18.0
RUN git clone --recursive --depth 1 --branch ${NGHTTP3_VERSION} https://github.com/ngtcp2/nghttp3 && \
cd nghttp3 && \
autoreconf -i && \
Expand All @@ -86,7 +86,7 @@ RUN git clone --recursive --depth 1 --branch ${NGHTTP3_VERSION} https://github.c
#
# nghtcp2 is built with both 'boringssl' (actually, aws-lc) and 'ossl' support
#
ARG NGTCP2_VERSION=v1.22.1
ARG NGTCP2_VERSION=v1.25.0
RUN git clone --recursive --depth 1 --branch ${NGTCP2_VERSION} https://github.com/ngtcp2/ngtcp2 && \
cd ngtcp2 && \
autoreconf -i && \
Expand All @@ -112,7 +112,7 @@ RUN git clone --depth 1 -b v1.6.3 https://github.com/libbpf/libbpf && \
#
# nghttp2 with HTTP/3 support (based on OpenSSL)
#
ARG NGHTTP2_VERSION=v1.69.0
ARG NGHTTP2_VERSION=v1.70.0
RUN git clone --recursive --depth 1 --branch ${NGHTTP2_VERSION} https://github.com/nghttp2/nghttp2.git && \
cd nghttp2 && \
autoreconf -i && \
Expand All @@ -130,15 +130,15 @@ RUN git clone --recursive --depth 1 --branch ${NGHTTP2_VERSION} https://github.c
# https://curl.se/docs/http3.html
#

# RUN cd opt && \
# git clone --depth 1 https://github.com/curl/curl.git && \
# cd curl && autoreconf -fi && \
# ./configure --with-openssl --without-libpsl --with-nghttp2 \
# --with-ngtcp2 --with-nghttp3 \
# --prefix /usr/local && \
# make -j$(nproc) && make install && \
# cd .. && rm -rf curl
# RUN apt-get -y remove curl
RUN cd opt && \
git clone --depth 1 https://github.com/curl/curl.git && \
cd curl && autoreconf -fi && \
./configure --with-openssl --without-libpsl --with-nghttp2 \
--with-ngtcp2 --with-nghttp3 \
--prefix /usr/local && \
make -j$(nproc) && make install && \
cd .. && rm -rf curl
RUN apt-get -y remove curl

# ==================================================================================================

Expand Down
10 changes: 6 additions & 4 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
"usernamehw.errorlens",
"vadimcn.vscode-lldb",
"xaver.clang-format",
"github.vscode-github-actions"
"github.vscode-github-actions",
"anthropic.claude-code"
]
}
},
Expand All @@ -55,15 +56,16 @@
// "postCreateCommand": "gcc -v",

"mounts": [
"source=cpp-devcontainer-bashhistory,target=/commandhistory,type=volume"
"source=cpp-devcontainer-bashhistory,target=/commandhistory,type=volume",
// "source=${localEnv:HOME}/src/nghttp2,target=/workspaces/nghttp2,type=bind,consistency=cached",
// "source=${localEnv:HOME}/src/ngtcp2,target=/workspaces/ngtcp2,type=bind,consistency=cached"
// "source=/home/peter/.claude,target=/home/vscode/.claude,type=bind,consistency=cached"
"source=${localEnv:HOME}/.claude,target=/home/vscode/.claude,type=bind,consistency=cached",
"source=${localEnv:HOME}/.claude.json,target=/home/vscode/.claude.json,type=bind,consistency=cached"
],

// Comment out this line to run as root instead.
"remoteUser": "vscode",
"remoteEnv": {
// "PATH": "${containerEnv:PATH}:/workspaces/nghttp2/install/bin"
}
}
}
10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
name: RelWithDebInfo
runs-on: ubuntu-latest
container:
image: docker.io/psedoc/anyhttp:0.25
image: docker.io/psedoc/anyhttp:0.27

steps:
- &checkout
Expand All @@ -37,7 +37,7 @@ jobs:
name: ASAN
runs-on: ubuntu-latest
container:
image: docker.io/psedoc/anyhttp:0.25
image: docker.io/psedoc/anyhttp:0.27

steps:
- *checkout
Expand All @@ -50,13 +50,13 @@ jobs:

- name: Run tests (if available)
run: |
ASAN_OPTIONS=detect_leaks=1 build/test/test_all
ASAN_OPTIONS=detect_leaks=1:detect_container_overflow=0 build/test/test_all

tsan:
name: TSAN
runs-on: ubuntu-latest
container:
image: docker.io/psedoc/anyhttp:0.25
image: docker.io/psedoc/anyhttp:0.27

steps:
- *checkout
Expand All @@ -75,7 +75,7 @@ jobs:
name: Coverage
runs-on: ubuntu-latest
container:
image: docker.io/psedoc/anyhttp:0.25
image: docker.io/psedoc/anyhttp:0.27
permissions:
contents: read
pages: write
Expand Down
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
.cache
.vscode
pki/out/*
!pki/out/server.pem
!pki/out/server-key.pem
docs/_build
gtest-parallel-logs
report.xml
Expand All @@ -15,3 +13,4 @@ Testing
callgrind.*
cmake_test_discovery_*.json
googletest_discovery_*.json
keylog.log
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ check_include_file("linux/rtnetlink.h" HAVE_LINUX_RTNETLINK_H)
# --------------------------------------------------------------------------------------------------

enable_testing(false)
include(cmake/pki.cmake)
add_subdirectory(src)
add_subdirectory(test)

Expand Down
9 changes: 9 additions & 0 deletions HTTP3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# HTTP/3 Manual Testing

```sh
cmake --build build && build/src/ngtcp2/ngtcp-server ::1 8080 pki/out/server-key.pem pki/out/server-chain.pem
```

```sh
curl --http3-only --cacert pki/out/root.pem https://[::1]:8080/CMakeLists.txt -vv
```
51 changes: 51 additions & 0 deletions cmake/pki.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#
# Test PKI generation.
#
# The server and the tests open their certificates through paths relative to the project root
# (see src/server_impl.cpp and test/test_server.cpp), so the PKI has to be generated into the
# source tree at pki/out rather than into the build directory.
#
# Nothing below pki/out is tracked by git: the whole chain is generated here at build time.
#
find_program(CFSSL_EXECUTABLE cfssl REQUIRED)
find_program(CFSSLJSON_EXECUTABLE cfssljson REQUIRED)

set(PKI_DIR "${CMAKE_SOURCE_DIR}/pki")
set(PKI_OUT_DIR "${PKI_DIR}/out")

set(PKI_INPUTS
"${PKI_DIR}/create.sh"
"${PKI_DIR}/config.json"
"${PKI_DIR}/root.json"
"${PKI_DIR}/intermediate.json"
"${PKI_DIR}/server.json"
"${PKI_DIR}/client.json"
)

set(PKI_OUTPUTS
"${PKI_OUT_DIR}/root.pem"
"${PKI_OUT_DIR}/intermediate.pem"
"${PKI_OUT_DIR}/server.pem"
"${PKI_OUT_DIR}/server-key.pem"
"${PKI_OUT_DIR}/server-chain.pem"
"${PKI_OUT_DIR}/client.pem"
"${PKI_OUT_DIR}/client-key.pem"
)

#
# create.sh skips any stage whose output already exists, which would leave a stale leaf signed by
# a CA that is no longer around after one of the .json profiles changes. Wipe pki/out first so a
# regeneration always produces one self-consistent chain.
#
add_custom_command(
OUTPUT ${PKI_OUTPUTS}
COMMAND ${CMAKE_COMMAND} -E rm -rf "${PKI_OUT_DIR}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${PKI_OUT_DIR}"
COMMAND "${PKI_DIR}/create.sh"
WORKING_DIRECTORY "${PKI_DIR}"
DEPENDS ${PKI_INPUTS}
COMMENT "Generating test PKI in pki/out"
VERBATIM
)

add_custom_target(pki ALL DEPENDS ${PKI_OUTPUTS})
54 changes: 51 additions & 3 deletions include/anyhttp/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
#include <boost/url/pct_string_view.hpp>

#include <chrono>
#include <format>
#include <iostream>
#include <memory>
#include <string_view>

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

Expand All @@ -36,8 +38,7 @@ using error_code = boost::system::error_code;

enum class Protocol
{
h1,
http11 = h1,
http11,
h2,
h3
};
Expand All @@ -50,6 +51,53 @@ std::ostream& operator<<(std::ostream& str, Protocol protocol);
using Fields = boost::beast::http::fields;
static_assert(boost::beast::http::is_fields<Fields>::value);

//
// A header value as passed to fields() below: either something string-like, or anything
// std::format can turn into a string, so sizes and counts need no conversion at the call site.
//
// String-like values are only referenced, never copied: a FieldValue lives just long enough for
// fields() to hand the bytes to Beast, which copies them. Only formatted values need storage.
//
class FieldValue
{
public:
template <typename T>
requires std::formattable<const T&, char>
FieldValue(const T& value)
{
if constexpr (std::convertible_to<const T&, std::string_view>)
view = value;
else
{
buffer = std::format("{}", value);
view = buffer;
}
}

/// 'view' may point into 'buffer', so a copy would alias the original's storage.
FieldValue(const FieldValue&) = delete;

operator std::string_view() const noexcept { return view; }

private:
std::string_view view;
std::string buffer; // only ever used for a value that had to be formatted
};

//
// Beast's fields have no initializer-list constructor, so building a small, fixed set of headers
// takes a statement per header. This lets it be spelled inline:
//
// fields({{"Content-Length", body.size()}, {"Content-Type", "text/plain"}})
//
inline Fields fields(std::initializer_list<std::pair<std::string_view, FieldValue>> headers)
{
Fields result;
for (auto&& [name, value] : headers)
result.set(name, std::string_view(value));
return result;
}

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

Expand Down Expand Up @@ -94,7 +142,7 @@ class Reader : public std::enable_shared_from_this<Reader>
virtual void destroy() {};
};

class Writer: public std::enable_shared_from_this<Writer>
class Writer : public std::enable_shared_from_this<Writer>
{
public:
virtual ~Writer() = default;
Expand Down
Loading
Loading