mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-12 10:22:39 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed97a6edba | ||
|
|
06b8b91589 | ||
|
|
873d701972 | ||
|
|
b40937cea8 | ||
|
|
568d434e72 | ||
|
|
0fa4912891 | ||
|
|
32abac3de5 | ||
|
|
2132205e1a | ||
|
|
a4d7066c2c | ||
|
|
bd455f8b91 | ||
|
|
9a5321aadb | ||
|
|
32ff75e355 | ||
|
|
45da614ddd | ||
|
|
f5c8c982df | ||
|
|
9e8b960e44 | ||
|
|
8149bb38fc | ||
|
|
3fe32b63b4 | ||
|
|
0ae93881b4 | ||
|
|
a7b886b9cb | ||
|
|
9d159bb412 | ||
|
|
9ac64e90db | ||
|
|
ba390f2399 | ||
|
|
7307c41aa8 | ||
|
|
df0b7d243b | ||
|
|
28d95937b5 | ||
|
|
7532932276 | ||
|
|
8829fac98e | ||
|
|
fe332fa06b | ||
|
|
fa981cedae | ||
|
|
39ec7d0508 | ||
|
|
e7e7bf7b44 | ||
|
|
78ff94e604 | ||
|
|
018ce8e4ca | ||
|
|
77bdf7921a | ||
|
|
79d83feb18 | ||
|
|
fe56a07da5 | ||
|
|
907257f51d | ||
|
|
0c2f535b74 | ||
|
|
c7ba963a17 | ||
|
|
4465e81b9f | ||
|
|
44215e23e9 | ||
|
|
91219d4508 | ||
|
|
c86c192f3e | ||
|
|
008e107d0f | ||
|
|
d278f965cc | ||
|
|
4c4b62dd7e | ||
|
|
b1792ef29c | ||
|
|
0f3d063f0a | ||
|
|
0d7d637466 | ||
|
|
1ff0c8588d | ||
|
|
b1cc8095a8 | ||
|
|
28f8264d13 | ||
|
|
91271c062d | ||
|
|
d755c43d58 | ||
|
|
5c9285776e | ||
|
|
811dd0b6f2 | ||
|
|
e8e652824b | ||
|
|
fbb031ed85 | ||
|
|
7d5082cc0e | ||
|
|
600d220c84 | ||
|
|
87d62db46b | ||
|
|
a1fdc07f34 | ||
|
|
eb49a304b6 | ||
|
|
a9bfe5914b | ||
|
|
ec5ce17929 | ||
|
|
f6524c0802 | ||
|
|
35c4026c7f | ||
|
|
40e18460bc | ||
|
|
92aecf85d8 | ||
|
|
b223e29778 | ||
|
|
2d2efe46da | ||
|
|
cae753425e | ||
|
|
d412e98c62 | ||
|
|
806fcb8268 | ||
|
|
c2678f0186 | ||
|
|
0cbeafe6a4 | ||
|
|
13e866bdb0 | ||
|
|
db6c9ef27b | ||
|
|
887837c65b | ||
|
|
3d56762d5c | ||
|
|
109e331068 | ||
|
|
2ea632264d | ||
|
|
511cc02278 | ||
|
|
f50bd311fb | ||
|
|
b0866cff8f | ||
|
|
5ebbfeef0b | ||
|
|
d14e4fc05f |
@@ -20,6 +20,9 @@ jobs:
|
|||||||
url: ${{ steps.deployment.outputs.page_url }}
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
# Full history so sitemap <lastmod> reflects each page's real last commit date
|
||||||
|
fetch-depth: 0
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
- name: Install docs-gen
|
- name: Install docs-gen
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ jobs:
|
|||||||
- name: Build (Win32)
|
- name: Build (Win32)
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
|
for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath`) do set VSDIR=%%i
|
||||||
|
call "%VSDIR%\VC\Auxiliary\Build\vcvarsall.bat" x86 || exit /b 1
|
||||||
cl /std:c++14 /EHsc /W4 /WX /c /Fo:NUL test\test_32bit_build.cpp
|
cl /std:c++14 /EHsc /W4 /WX /c /Fo:NUL test\test_32bit_build.cpp
|
||||||
|
|
||||||
test-arm32:
|
test-arm32:
|
||||||
|
|||||||
+303
-6
@@ -25,7 +25,9 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GTEST_FILTER: ${{ github.event.inputs.gtest_filter || '*' }}
|
# Exclude *_Online tests by default — they hit external services and flake on
|
||||||
|
# CI runners. Run with workflow_dispatch + a custom filter to include them.
|
||||||
|
GTEST_FILTER: ${{ github.event.inputs.gtest_filter || '-*_Online' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
style-check:
|
style-check:
|
||||||
@@ -75,6 +77,7 @@ jobs:
|
|||||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
tls_backend: [openssl, mbedtls, wolfssl]
|
tls_backend: [openssl, mbedtls, wolfssl]
|
||||||
name: ubuntu (${{ matrix.tls_backend }})
|
name: ubuntu (${{ matrix.tls_backend }})
|
||||||
@@ -101,7 +104,10 @@ jobs:
|
|||||||
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
|
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
|
||||||
- name: build and run tests (Mbed TLS)
|
- name: build and run tests (Mbed TLS)
|
||||||
if: matrix.tls_backend == 'mbedtls'
|
if: matrix.tls_backend == 'mbedtls'
|
||||||
run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
|
# Run mbedTLS shards with reduced parallelism — under ASAN+mbedTLS the
|
||||||
|
# default 4 shards overload CI runners enough that timing-sensitive
|
||||||
|
# ServerTest cases flake on first-request keep-alive reuse.
|
||||||
|
run: cd test && make test_split_mbedtls && SHARDS=2 make test_mbedtls_parallel
|
||||||
- name: build and run tests (wolfSSL)
|
- name: build and run tests (wolfSSL)
|
||||||
if: matrix.tls_backend == 'wolfssl'
|
if: matrix.tls_backend == 'wolfssl'
|
||||||
run: cd test && make test_split_wolfssl && make test_wolfssl_parallel
|
run: cd test && make test_split_wolfssl && make test_wolfssl_parallel
|
||||||
@@ -114,6 +120,241 @@ jobs:
|
|||||||
- name: build and run ThreadPool test
|
- name: build and run ThreadPool test
|
||||||
run: cd test && make test_thread_pool && ./test_thread_pool
|
run: cd test && make test_thread_pool && ./test_thread_pool
|
||||||
|
|
||||||
|
# BoringSSL is Google's fork of OpenSSL. It has no API stability guarantee
|
||||||
|
# and is not packaged by distros, so we build it from source. cpp-httplib
|
||||||
|
# treats it as an OpenSSL backend variant via the OPENSSL_IS_BORINGSSL
|
||||||
|
# macro (see httplib.h). This job is best-effort: continue-on-error keeps
|
||||||
|
# upstream API drift from blocking PRs while still surfacing breakage.
|
||||||
|
ubuntu-boringssl:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >
|
||||||
|
(github.event_name == 'push') ||
|
||||||
|
(github.event_name == 'pull_request' &&
|
||||||
|
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
||||||
|
continue-on-error: true
|
||||||
|
name: ubuntu (boringssl, best-effort)
|
||||||
|
env:
|
||||||
|
# Tracking HEAD keeps us honest about upstream churn. If breakage
|
||||||
|
# becomes routine, replace HEAD with a 40-char commit SHA; the
|
||||||
|
# resolve step uses the SHA directly when it matches that shape.
|
||||||
|
BORINGSSL_REF: HEAD
|
||||||
|
BORINGSSL_PREFIX: ${{ github.workspace }}/boringssl-install
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: install common libraries
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev libbrotli-dev libzstd-dev
|
||||||
|
- name: resolve BoringSSL commit
|
||||||
|
id: boringssl-rev
|
||||||
|
# Accept either a ref name (resolved via git ls-remote) or a full
|
||||||
|
# 40-char SHA used directly. ls-remote does not list arbitrary
|
||||||
|
# commit SHAs, so pinning requires the second path.
|
||||||
|
run: |
|
||||||
|
if [[ "${BORINGSSL_REF}" =~ ^[0-9a-f]{40}$ ]]; then
|
||||||
|
sha="${BORINGSSL_REF}"
|
||||||
|
echo "Using pinned BoringSSL SHA: ${sha}"
|
||||||
|
else
|
||||||
|
sha=$(git ls-remote https://boringssl.googlesource.com/boringssl "${BORINGSSL_REF}" | awk '{print $1}')
|
||||||
|
if [ -z "$sha" ]; then
|
||||||
|
echo "Failed to resolve BoringSSL ref ${BORINGSSL_REF}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Resolved ${BORINGSSL_REF} -> ${sha}"
|
||||||
|
fi
|
||||||
|
echo "sha=${sha}" >> "$GITHUB_OUTPUT"
|
||||||
|
- name: cache BoringSSL build
|
||||||
|
id: boringssl-cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ env.BORINGSSL_PREFIX }}
|
||||||
|
key: boringssl-${{ runner.os }}-${{ steps.boringssl-rev.outputs.sha }}
|
||||||
|
- name: build BoringSSL
|
||||||
|
if: steps.boringssl-cache.outputs.cache-hit != 'true'
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
git clone https://boringssl.googlesource.com/boringssl boringssl
|
||||||
|
cd boringssl
|
||||||
|
git checkout "${{ steps.boringssl-rev.outputs.sha }}"
|
||||||
|
cmake -S . -B build \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DBUILD_SHARED_LIBS=OFF \
|
||||||
|
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
|
||||||
|
-DCMAKE_INSTALL_PREFIX="${BORINGSSL_PREFIX}"
|
||||||
|
cmake --build build -j"$(nproc)" --target install
|
||||||
|
- name: build and run tests (BoringSSL)
|
||||||
|
# Override OPENSSL_SUPPORT to point the existing OpenSSL Makefile path
|
||||||
|
# at BoringSSL's prefix. BoringSSL defines OPENSSL_IS_BORINGSSL in
|
||||||
|
# <openssl/base.h>, which httplib.h and test.cc use to switch on API
|
||||||
|
# differences (e.g. SAN-only hostname verification, no CN fallback).
|
||||||
|
#
|
||||||
|
# BoringSSL's public headers (<openssl/stack.h>) use std::enable_if_t,
|
||||||
|
# so consumers must compile with C++14 or later. cpp-httplib itself
|
||||||
|
# supports C++11, but anyone pairing it with BoringSSL inherits this
|
||||||
|
# constraint. EXTRA_CXXFLAGS appends after the Makefile's -std=c++11
|
||||||
|
# and the later flag wins.
|
||||||
|
run: |
|
||||||
|
cd test
|
||||||
|
BORINGSSL_FLAGS="-DCPPHTTPLIB_OPENSSL_SUPPORT -I${BORINGSSL_PREFIX}/include -L${BORINGSSL_PREFIX}/lib -lssl -lcrypto -lpthread"
|
||||||
|
make test_split OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17"
|
||||||
|
make test_openssl_parallel OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17"
|
||||||
|
env:
|
||||||
|
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
|
||||||
|
|
||||||
|
# macOS counterpart of the BoringSSL job. Same best-effort posture; the
|
||||||
|
# extra framework links cover the macOS Keychain integration that
|
||||||
|
# httplib.h auto-enables for any TLS backend on macOS.
|
||||||
|
macos-boringssl:
|
||||||
|
runs-on: macos-latest
|
||||||
|
if: >
|
||||||
|
(github.event_name == 'push') ||
|
||||||
|
(github.event_name == 'pull_request' &&
|
||||||
|
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true')
|
||||||
|
continue-on-error: true
|
||||||
|
name: macos (boringssl, best-effort)
|
||||||
|
env:
|
||||||
|
BORINGSSL_REF: HEAD
|
||||||
|
BORINGSSL_PREFIX: ${{ github.workspace }}/boringssl-install
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: resolve BoringSSL commit
|
||||||
|
id: boringssl-rev
|
||||||
|
# Accept either a ref name (resolved via git ls-remote) or a full
|
||||||
|
# 40-char SHA used directly. ls-remote does not list arbitrary
|
||||||
|
# commit SHAs, so pinning requires the second path.
|
||||||
|
run: |
|
||||||
|
if [[ "${BORINGSSL_REF}" =~ ^[0-9a-f]{40}$ ]]; then
|
||||||
|
sha="${BORINGSSL_REF}"
|
||||||
|
echo "Using pinned BoringSSL SHA: ${sha}"
|
||||||
|
else
|
||||||
|
sha=$(git ls-remote https://boringssl.googlesource.com/boringssl "${BORINGSSL_REF}" | awk '{print $1}')
|
||||||
|
if [ -z "$sha" ]; then
|
||||||
|
echo "Failed to resolve BoringSSL ref ${BORINGSSL_REF}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Resolved ${BORINGSSL_REF} -> ${sha}"
|
||||||
|
fi
|
||||||
|
echo "sha=${sha}" >> "$GITHUB_OUTPUT"
|
||||||
|
- name: cache BoringSSL build
|
||||||
|
id: boringssl-cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ env.BORINGSSL_PREFIX }}
|
||||||
|
key: boringssl-${{ runner.os }}-${{ steps.boringssl-rev.outputs.sha }}
|
||||||
|
- name: build BoringSSL
|
||||||
|
if: steps.boringssl-cache.outputs.cache-hit != 'true'
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
git clone https://boringssl.googlesource.com/boringssl boringssl
|
||||||
|
cd boringssl
|
||||||
|
git checkout "${{ steps.boringssl-rev.outputs.sha }}"
|
||||||
|
cmake -S . -B build \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DBUILD_SHARED_LIBS=OFF \
|
||||||
|
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
|
||||||
|
-DCMAKE_INSTALL_PREFIX="${BORINGSSL_PREFIX}"
|
||||||
|
cmake --build build -j"$(sysctl -n hw.ncpu)" --target install
|
||||||
|
- name: build and run tests (BoringSSL)
|
||||||
|
run: |
|
||||||
|
cd test
|
||||||
|
# CoreFoundation/Security frameworks satisfy the Keychain integration
|
||||||
|
# auto-enabled in httplib.h for macOS TLS builds.
|
||||||
|
BORINGSSL_FLAGS="-DCPPHTTPLIB_OPENSSL_SUPPORT -I${BORINGSSL_PREFIX}/include -L${BORINGSSL_PREFIX}/lib -lssl -lcrypto -framework CoreFoundation -framework Security"
|
||||||
|
make test_split OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17"
|
||||||
|
make test_openssl_parallel OPENSSL_SUPPORT="${BORINGSSL_FLAGS}" EXTRA_CXXFLAGS="-std=c++17"
|
||||||
|
env:
|
||||||
|
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
|
||||||
|
|
||||||
|
# Reproducer for https://github.com/yhirose/cpp-httplib/issues/2431.
|
||||||
|
# On Linux/glibc, getaddrinfo_with_timeout() schedules an asynchronous
|
||||||
|
# DNS lookup with getaddrinfo_a(GAI_NOWAIT) using a stack-local gaicb.
|
||||||
|
# When gai_suspend() hits the connection timeout, gai_cancel() is called
|
||||||
|
# but does not block; the resolver worker can later write back into the
|
||||||
|
# destroyed stack frame. To make the worker actually reach that write,
|
||||||
|
# the test job runs a loopback UDP responder (test/dns_test_fixture.py)
|
||||||
|
# that delays its reply past the test's 1s timeout, and uses an iptables
|
||||||
|
# NAT rule so glibc's lookups land on that fixture instead of a real
|
||||||
|
# nameserver. With ASAN's detect_stack_use_after_return enabled, the
|
||||||
|
# late write-back is reported as a stack-use-after-return.
|
||||||
|
issue-2431-repro:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >
|
||||||
|
(github.event_name == 'push') ||
|
||||||
|
(github.event_name == 'pull_request' &&
|
||||||
|
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
||||||
|
name: issue-2431 repro (Linux + ASAN)
|
||||||
|
# Bound the whole job in case anything in the test harness hangs
|
||||||
|
# unexpectedly. With the fixture in place a normal run is well under
|
||||||
|
# a minute either way (ASAN abort on broken HEAD, clean pass on fix).
|
||||||
|
timeout-minutes: 5
|
||||||
|
env:
|
||||||
|
DNS_FIXTURE_PORT: "15353"
|
||||||
|
DNS_FIXTURE_DELAY: "3"
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: install libraries
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libssl-dev zlib1g-dev libbrotli-dev \
|
||||||
|
libzstd-dev libcurl4-openssl-dev iptables util-linux iproute2
|
||||||
|
- name: start loopback DNS test fixture
|
||||||
|
run: |
|
||||||
|
# Force glibc through its DNS code path: Ubuntu's default
|
||||||
|
# nsswitch short-circuits to NOTFOUND through mdns4_minimal,
|
||||||
|
# which would skip the buggy code entirely.
|
||||||
|
sudo sed -i 's/^hosts:.*/hosts: dns/' /etc/nsswitch.conf
|
||||||
|
# Run the loopback fixture (delayed UDP responder).
|
||||||
|
python3 test/dns_test_fixture.py "$DNS_FIXTURE_PORT" "$DNS_FIXTURE_DELAY" \
|
||||||
|
>/tmp/dns_fixture.log 2>&1 &
|
||||||
|
echo $! | sudo tee /tmp/dns_fixture.pid >/dev/null
|
||||||
|
# Wait for the fixture to start listening.
|
||||||
|
for _ in $(seq 1 50); do
|
||||||
|
if ss -lun "( sport = :$DNS_FIXTURE_PORT )" | grep -q ":$DNS_FIXTURE_PORT"; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
ss -lun "( sport = :$DNS_FIXTURE_PORT )" | grep -q ":$DNS_FIXTURE_PORT" \
|
||||||
|
|| { echo "fixture failed to start"; cat /tmp/dns_fixture.log; exit 1; }
|
||||||
|
# Send the test process's DNS lookups to the loopback fixture.
|
||||||
|
# NAT only the local OUTPUT chain; conntrack handles the reply path.
|
||||||
|
sudo iptables -t nat -I OUTPUT -p udp --dport 53 \
|
||||||
|
-j REDIRECT --to-port "$DNS_FIXTURE_PORT"
|
||||||
|
# Sanity check: a query must take at least the fixture delay
|
||||||
|
# and resolve to NXDOMAIN (proving traffic reaches the fixture).
|
||||||
|
start=$(date +%s)
|
||||||
|
getent hosts unresolvable-host.invalid >/dev/null 2>&1 || true
|
||||||
|
elapsed=$(( $(date +%s) - start ))
|
||||||
|
if [ "$elapsed" -lt 2 ]; then
|
||||||
|
echo "ERROR: lookup returned in ${elapsed}s; fixture not in path" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "[ok] DNS lookups are routed to the test fixture (took ${elapsed}s)"
|
||||||
|
- name: build test binary
|
||||||
|
run: cd test && make test
|
||||||
|
- name: run GetAddrInfoAsyncCancelTest
|
||||||
|
run: |
|
||||||
|
cd test
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
CPPHTTPLIB_TEST_ISSUE_2431=1 \
|
||||||
|
ASAN_OPTIONS=detect_stack_use_after_return=1 \
|
||||||
|
LSAN_OPTIONS=suppressions=lsan_suppressions.txt \
|
||||||
|
setarch "$ARCH" -R \
|
||||||
|
./test --gtest_filter='GetAddrInfoAsyncCancelTest.*'
|
||||||
|
- name: tear down test fixture
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
sudo iptables -t nat -F OUTPUT || true
|
||||||
|
if [ -f /tmp/dns_fixture.pid ]; then
|
||||||
|
sudo kill "$(cat /tmp/dns_fixture.pid)" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
macos:
|
macos:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
if: >
|
if: >
|
||||||
@@ -122,6 +363,7 @@ jobs:
|
|||||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true')
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true')
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
tls_backend: [openssl, mbedtls, wolfssl]
|
tls_backend: [openssl, mbedtls, wolfssl]
|
||||||
name: macos (${{ matrix.tls_backend }})
|
name: macos (${{ matrix.tls_backend }})
|
||||||
@@ -141,7 +383,10 @@ jobs:
|
|||||||
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
|
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
|
||||||
- name: build and run tests (Mbed TLS)
|
- name: build and run tests (Mbed TLS)
|
||||||
if: matrix.tls_backend == 'mbedtls'
|
if: matrix.tls_backend == 'mbedtls'
|
||||||
run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
|
# macOS runners under ASAN+mbedTLS still flake at SHARDS=2 (rapid
|
||||||
|
# bind/connect on the fixture's fixed port races on the slower
|
||||||
|
# macos-latest runner). Serialize fully here; ubuntu stays at 2.
|
||||||
|
run: cd test && make test_split_mbedtls && SHARDS=1 make test_mbedtls_parallel
|
||||||
- name: build and run tests (wolfSSL)
|
- name: build and run tests (wolfSSL)
|
||||||
if: matrix.tls_backend == 'wolfssl'
|
if: matrix.tls_backend == 'wolfssl'
|
||||||
run: cd test && make test_split_wolfssl && make test_wolfssl_parallel
|
run: cd test && make test_split_wolfssl && make test_wolfssl_parallel
|
||||||
@@ -154,6 +399,54 @@ jobs:
|
|||||||
- name: build and run ThreadPool test
|
- name: build and run ThreadPool test
|
||||||
run: cd test && make test_thread_pool && ./test_thread_pool
|
run: cd test && make test_thread_pool && ./test_thread_pool
|
||||||
|
|
||||||
|
ios-parse-check:
|
||||||
|
runs-on: macos-latest
|
||||||
|
if: >
|
||||||
|
(github.event_name == 'push') ||
|
||||||
|
(github.event_name == 'pull_request' &&
|
||||||
|
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true')
|
||||||
|
name: ios header parse check (not officially supported)
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: install OpenSSL headers
|
||||||
|
run: brew install openssl@3
|
||||||
|
- name: verify header parses on iOS target
|
||||||
|
run: |
|
||||||
|
IOS_SDK=$(xcrun --sdk iphoneos --show-sdk-path)
|
||||||
|
OPENSSL_INC=$(brew --prefix openssl@3)/include
|
||||||
|
echo "Using iOS SDK: $IOS_SDK"
|
||||||
|
echo '#include "httplib.h"' | clang++ \
|
||||||
|
-isysroot "$IOS_SDK" \
|
||||||
|
-target arm64-apple-ios16.0 \
|
||||||
|
-std=c++11 \
|
||||||
|
-DCPPHTTPLIB_OPENSSL_SUPPORT \
|
||||||
|
-I"$OPENSSL_INC" \
|
||||||
|
-I. -Wall -Wextra \
|
||||||
|
-fsyntax-only -x c++ -
|
||||||
|
- name: verify CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN is rejected on iOS
|
||||||
|
run: |
|
||||||
|
IOS_SDK=$(xcrun --sdk iphoneos --show-sdk-path)
|
||||||
|
OPENSSL_INC=$(brew --prefix openssl@3)/include
|
||||||
|
out=$(echo '#include "httplib.h"' | clang++ \
|
||||||
|
-isysroot "$IOS_SDK" \
|
||||||
|
-target arm64-apple-ios16.0 \
|
||||||
|
-std=c++11 \
|
||||||
|
-DCPPHTTPLIB_OPENSSL_SUPPORT \
|
||||||
|
-DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN \
|
||||||
|
-I"$OPENSSL_INC" \
|
||||||
|
-I. \
|
||||||
|
-fsyntax-only -x c++ - 2>&1 || true)
|
||||||
|
if echo "$out" | grep -q "only supported on macOS"; then
|
||||||
|
echo "OK: #error fired as expected"
|
||||||
|
else
|
||||||
|
echo "FAIL: expected #error did not fire"
|
||||||
|
echo "--- compiler output ---"
|
||||||
|
echo "$out"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
if: >
|
if: >
|
||||||
@@ -162,6 +455,7 @@ jobs:
|
|||||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_windows == 'true')
|
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_windows == 'true')
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
config:
|
config:
|
||||||
- with_ssl: false
|
- with_ssl: false
|
||||||
@@ -228,7 +522,7 @@ jobs:
|
|||||||
for ($i = 0; $i -lt $shards; $i++) {
|
for ($i = 0; $i -lt $shards; $i++) {
|
||||||
$log = "shard_${i}.log"
|
$log = "shard_${i}.log"
|
||||||
$procs += Start-Process -FilePath ./Release/httplib-test.exe `
|
$procs += Start-Process -FilePath ./Release/httplib-test.exe `
|
||||||
-ArgumentList "--gtest_color=yes","--gtest_filter=${{ github.event.inputs.gtest_filter || '*' }}" `
|
-ArgumentList "--gtest_color=yes","--gtest_filter=${{ github.event.inputs.gtest_filter || '-*_Online' }}" `
|
||||||
-NoNewWindow -PassThru -RedirectStandardOutput $log -RedirectStandardError "${log}.err" `
|
-NoNewWindow -PassThru -RedirectStandardOutput $log -RedirectStandardError "${log}.err" `
|
||||||
-Environment @{ GTEST_TOTAL_SHARDS="$shards"; GTEST_SHARD_INDEX="$i" }
|
-Environment @{ GTEST_TOTAL_SHARDS="$shards"; GTEST_SHARD_INDEX="$i" }
|
||||||
}
|
}
|
||||||
@@ -236,11 +530,14 @@ jobs:
|
|||||||
$failed = $false
|
$failed = $false
|
||||||
for ($i = 0; $i -lt $shards; $i++) {
|
for ($i = 0; $i -lt $shards; $i++) {
|
||||||
$log = "shard_${i}.log"
|
$log = "shard_${i}.log"
|
||||||
if (Select-String -Path $log -Pattern "\[ PASSED \]" -Quiet) {
|
$proc = $procs[$i]
|
||||||
|
$hasPassed = Select-String -Path $log -Pattern "\[ PASSED \]" -Quiet
|
||||||
|
$hasFailed = Select-String -Path $log -Pattern "\[ FAILED \]" -Quiet
|
||||||
|
if ($hasPassed -and -not $hasFailed -and $proc.ExitCode -eq 0) {
|
||||||
$passed = (Select-String -Path $log -Pattern "\[ PASSED \]").Line
|
$passed = (Select-String -Path $log -Pattern "\[ PASSED \]").Line
|
||||||
Write-Host "Shard ${i}: $passed"
|
Write-Host "Shard ${i}: $passed"
|
||||||
} else {
|
} else {
|
||||||
Write-Host "=== Shard $i FAILED ==="
|
Write-Host "=== Shard $i FAILED (exit=$($proc.ExitCode)) ==="
|
||||||
Get-Content $log
|
Get-Content $log
|
||||||
if (Test-Path "${log}.err") { Get-Content "${log}.err" }
|
if (Test-Path "${log}.err") { Get-Content "${log}.err" }
|
||||||
$failed = $true
|
$failed = $true
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ test/test_mbedtls
|
|||||||
test/test_wolfssl
|
test/test_wolfssl
|
||||||
test/test_no_tls
|
test/test_no_tls
|
||||||
test/server_fuzzer
|
test/server_fuzzer
|
||||||
|
test/client_fuzzer
|
||||||
|
test/header_parser_fuzzer
|
||||||
|
test/url_parser_fuzzer
|
||||||
test/test_proxy
|
test/test_proxy
|
||||||
test/test_proxy_mbedtls
|
test/test_proxy_mbedtls
|
||||||
test/test_proxy_wolfssl
|
test/test_proxy_wolfssl
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ cpp-httplib supports multiple TLS backends through an abstraction layer:
|
|||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> **Mbed TLS / wolfSSL limitation:** `get_ca_certs()` and `get_ca_names()` only reflect CA certificates loaded via `load_ca_cert_store()`. Certificates loaded through `set_ca_cert_path()` or system certificates (`load_system_certs`) are not enumerable.
|
> **Mbed TLS / wolfSSL limitation:** `get_ca_certs()` and `get_ca_names()` only reflect CA certificates loaded via `load_ca_cert_store()`. Certificates loaded through `set_ca_cert_path()` or system certificates (`load_system_certs`) are not enumerable.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **BoringSSL (best-effort):** BoringSSL builds under `CPPHTTPLIB_OPENSSL_SUPPORT` and is exercised by CI against current upstream. Because BoringSSL does not guarantee API stability, support is best-effort — breakage may occasionally land. Two known behavioral differences vs OpenSSL: (1) BoringSSL's public headers require C++14 or later, so consumers must compile accordingly; (2) hostname verification is SAN-only per RFC 6125 §6.4.4 (no CN fallback).
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
// Use either OpenSSL, Mbed TLS, or wolfSSL
|
// Use either OpenSSL, Mbed TLS, or wolfSSL
|
||||||
#define CPPHTTPLIB_OPENSSL_SUPPORT // or CPPHTTPLIB_MBEDTLS_SUPPORT or CPPHTTPLIB_WOLFSSL_SUPPORT
|
#define CPPHTTPLIB_OPENSSL_SUPPORT // or CPPHTTPLIB_MBEDTLS_SUPPORT or CPPHTTPLIB_WOLFSSL_SUPPORT
|
||||||
@@ -447,6 +450,8 @@ svr.set_post_routing_handler([](const auto& req, auto& res) {
|
|||||||
|
|
||||||
### Pre request handler
|
### Pre request handler
|
||||||
|
|
||||||
|
The pre-request handler runs after the route has been matched (so `req.matched_route` and `req.path_params` are available) but **before the request body is read**. This means you can reject a request — for example on a failed authentication or authorization check — without forcing the server to buffer a potentially large body.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
svr.set_pre_request_handler([](const auto& req, auto& res) {
|
svr.set_pre_request_handler([](const auto& req, auto& res) {
|
||||||
if (req.matched_route == "/user/:user") {
|
if (req.matched_route == "/user/:user") {
|
||||||
@@ -461,6 +466,38 @@ svr.set_pre_request_handler([](const auto& req, auto& res) {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Because the body has not been read yet, `req.body` and form fields parsed from the body are not available in the pre-request handler. Inspect headers, the path, query parameters, or `req.matched_route` instead.
|
||||||
|
|
||||||
|
### Handler execution order
|
||||||
|
|
||||||
|
`set_start_handler` runs once when the server starts. For each request, handlers run in the following order:
|
||||||
|
|
||||||
|
```
|
||||||
|
Request received
|
||||||
|
│
|
||||||
|
├─ pre_routing_handler route not matched yet, body not read
|
||||||
|
│ └─ returns Handled → stop here
|
||||||
|
│
|
||||||
|
├─ file_request_handler (GET/HEAD, static file serving)
|
||||||
|
│
|
||||||
|
├─ expect_100_continue_handler (when the request has "Expect: 100-continue")
|
||||||
|
│
|
||||||
|
├─ route matching → req.matched_route is set
|
||||||
|
│
|
||||||
|
├─ pre_request_handler route matched, body NOT read yet
|
||||||
|
│ └─ returns Handled → stop here (route handler is skipped)
|
||||||
|
│
|
||||||
|
├─ route handler Get/Post/...; the request body is read first
|
||||||
|
│
|
||||||
|
└─ post_routing_handler after routing completes
|
||||||
|
|
||||||
|
On a thrown exception → exception_handler
|
||||||
|
On an error status (4xx/5xx) → error_handler
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `pre_routing_handler` to reject a request as early as possible, before the route is known. Use `pre_request_handler` for route-specific checks, since `req.matched_route` is available and the body has not been read yet.
|
||||||
|
|
||||||
### Response user data
|
### Response user data
|
||||||
|
|
||||||
`res.user_data` is a type-safe key-value store that lets pre-routing or pre-request handlers pass arbitrary data to route handlers.
|
`res.user_data` is a type-safe key-value store that lets pre-routing or pre-request handlers pass arbitrary data to route handlers.
|
||||||
@@ -787,6 +824,15 @@ svr.new_task_queue = [] { return new ThreadPool(/*base_threads=*/12, /*max_threa
|
|||||||
Default limit is 0 (unlimited). Once the limit is reached, the listener
|
Default limit is 0 (unlimited). Once the limit is reached, the listener
|
||||||
will shutdown the client connection.
|
will shutdown the client connection.
|
||||||
|
|
||||||
|
#### Idle timeout for dynamic threads
|
||||||
|
|
||||||
|
The idle timeout for dynamic threads can also be set at runtime via the
|
||||||
|
fourth parameter (in seconds):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
svr.new_task_queue = [] { return new ThreadPool(/*base_threads=*/8, /*max_threads=*/64, /*max_queued_requests=*/0, /*idle_timeout_sec=*/10); };
|
||||||
|
```
|
||||||
|
|
||||||
### Override the default thread pool with yours
|
### Override the default thread pool with yours
|
||||||
|
|
||||||
You can supply your own thread pool implementation according to your need.
|
You can supply your own thread pool implementation according to your need.
|
||||||
@@ -950,7 +996,7 @@ auto res = cli.Get("/hi", headers);
|
|||||||
or
|
or
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
auto res = cli.Get("/hi", {{"Hello", "World!"}});
|
auto res = cli.Get("/hi", httplib::Headers{{"Hello", "World!"}});
|
||||||
```
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
@@ -1178,12 +1224,23 @@ cli.set_proxy_bearer_token_auth("pass");
|
|||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> OpenSSL is required for Digest Authentication.
|
> OpenSSL is required for Digest Authentication.
|
||||||
|
|
||||||
|
#### Bypass the proxy for specific hosts (`NO_PROXY`)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
cli.set_no_proxy({"internal.corp", "10.0.0.0/8", "*.dev.local"});
|
||||||
|
```
|
||||||
|
|
||||||
|
Each pattern is `*`, a hostname suffix, an IP literal, or a CIDR block.
|
||||||
|
Hostname matching is case-insensitive with a dot-boundary rule. See the
|
||||||
|
[NO_PROXY cookbook](https://yhirose.github.io/cpp-httplib/en/cookbook/c16-proxy)
|
||||||
|
for details and for reading the variable from the environment.
|
||||||
|
|
||||||
### Range
|
### Range
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
httplib::Client cli("httpcan.org");
|
httplib::Client cli("httpcan.org");
|
||||||
|
|
||||||
auto res = cli.Get("/range/32", {
|
auto res = cli.Get("/range/32", httplib::Headers{
|
||||||
httplib::make_range_header({{1, 10}}) // 'Range: bytes=1-10'
|
httplib::make_range_header({{1, 10}}) // 'Range: bytes=1-10'
|
||||||
});
|
});
|
||||||
// res->status should be 206.
|
// res->status should be 206.
|
||||||
@@ -1307,13 +1364,13 @@ The default `Accept-Encoding` value contains all possible compression types. So,
|
|||||||
|
|
||||||
```c++
|
```c++
|
||||||
res = cli.Get("/resource/foo");
|
res = cli.Get("/resource/foo");
|
||||||
res = cli.Get("/resource/foo", {{"Accept-Encoding", "br, gzip, deflate, zstd"}});
|
res = cli.Get("/resource/foo", httplib::Headers{{"Accept-Encoding", "br, gzip, deflate, zstd"}});
|
||||||
```
|
```
|
||||||
|
|
||||||
If we don't want a response without compression, we have to set `Accept-Encoding` to an empty string. This behavior is similar to curl.
|
If we don't want a response without compression, we have to set `Accept-Encoding` to an empty string. This behavior is similar to curl.
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
res = cli.Get("/resource/foo", {{"Accept-Encoding", ""}});
|
res = cli.Get("/resource/foo", httplib::Headers{{"Accept-Encoding", ""}});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compress request body on client
|
### Compress request body on client
|
||||||
@@ -1433,11 +1490,9 @@ See [README-sse.md](README-sse.md) for more details.
|
|||||||
httplib::Server svr;
|
httplib::Server svr;
|
||||||
|
|
||||||
svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
|
svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
|
||||||
httplib::ws::Message msg;
|
std::string msg;
|
||||||
while (ws.read(msg)) {
|
while (ws.read(msg)) {
|
||||||
if (msg.is_text()) {
|
ws.send("Echo: " + msg);
|
||||||
ws.send("Echo: " + msg.data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -7,11 +7,12 @@ CPPHTTPLIB_FLAGS = -DCPPHTTPLIB_THREAD_POOL_COUNT=16
|
|||||||
|
|
||||||
BENCH = bombardier -c 10 -d 5s localhost:8080
|
BENCH = bombardier -c 10 -d 5s localhost:8080
|
||||||
MONITOR = ali http://localhost:8080
|
MONITOR = ali http://localhost:8080
|
||||||
|
WAIT = while ! nc -z localhost 8080 >/dev/null 2>&1; do sleep 0.05; done
|
||||||
|
|
||||||
# cpp-httplib
|
# cpp-httplib
|
||||||
bench: server
|
bench: server
|
||||||
@echo "--------------------\n cpp-httplib latest\n--------------------\n"
|
@echo "--------------------\n cpp-httplib latest\n--------------------\n"
|
||||||
@./server & export PID=$$!; $(BENCH); kill $${PID}
|
@./server & export PID=$$!; $(WAIT); $(BENCH); kill $${PID}
|
||||||
@echo ""
|
@echo ""
|
||||||
|
|
||||||
monitor: server
|
monitor: server
|
||||||
@@ -26,7 +27,7 @@ server : cpp-httplib/main.cpp ../httplib.h
|
|||||||
# crow
|
# crow
|
||||||
bench-crow: server-crow
|
bench-crow: server-crow
|
||||||
@echo "-------------\n Crow v1.3.1\n-------------\n"
|
@echo "-------------\n Crow v1.3.1\n-------------\n"
|
||||||
@./server-crow & export PID=$$!; $(BENCH); kill $${PID}
|
@./server-crow & export PID=$$!; $(WAIT); $(BENCH); kill $${PID}
|
||||||
@echo ""
|
@echo ""
|
||||||
|
|
||||||
monitor-crow: server-crow
|
monitor-crow: server-crow
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ if(@HTTPLIB_IS_USING_ZSTD@)
|
|||||||
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED)
|
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED)
|
||||||
set(httplib_fd_zstd_required_arg REQUIRED)
|
set(httplib_fd_zstd_required_arg REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
find_package(zstd QUIET)
|
find_package(zstd 1.5.6 CONFIG QUIET)
|
||||||
if(NOT zstd_FOUND)
|
if(NOT zstd_FOUND)
|
||||||
find_package(PkgConfig ${httplib_fd_zstd_quiet_arg} ${httplib_fd_zstd_required_arg})
|
find_package(PkgConfig ${httplib_fd_zstd_quiet_arg} ${httplib_fd_zstd_required_arg})
|
||||||
if(PKG_CONFIG_FOUND)
|
if(PKG_CONFIG_FOUND)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ langs = ["en", "ja"]
|
|||||||
|
|
||||||
[site]
|
[site]
|
||||||
title = "cpp-httplib"
|
title = "cpp-httplib"
|
||||||
version = "0.43.1"
|
version = "0.50.0"
|
||||||
hostname = "https://yhirose.github.io"
|
hostname = "https://yhirose.github.io"
|
||||||
base_path = "/cpp-httplib"
|
base_path = "/cpp-httplib"
|
||||||
footer_message = "© 2026 Yuji Hirose. All rights reserved."
|
footer_message = "© 2026 Yuji Hirose. All rights reserved."
|
||||||
|
|||||||
@@ -57,4 +57,4 @@ Return `false` from the callback to abort the download. In the example above, if
|
|||||||
>
|
>
|
||||||
> The `ResponseHandler` is called after headers arrive but before the body. Return `false` to skip the download entirely.
|
> The `ResponseHandler` is called after headers arrive but before the body. Return `false` to skip the download entirely.
|
||||||
|
|
||||||
> To show download progress, see [C11. Use the progress callback](c11-progress-callback).
|
> To show download progress, see [C11. Use the progress callback](../c11-progress-callback).
|
||||||
|
|||||||
@@ -31,6 +31,6 @@ if (res && res->status == 200) {
|
|||||||
|
|
||||||
`res->body` is a `std::string`, so you can pass it straight to your JSON library.
|
`res->body` is a `std::string`, so you can pass it straight to your JSON library.
|
||||||
|
|
||||||
> **Note:** Servers sometimes return HTML on errors. Check the status code before parsing to be safe. Some APIs also require an `Accept: application/json` header. If you're calling a JSON API repeatedly, [C03. Set default headers](c03-default-headers) can save you some boilerplate.
|
> **Note:** Servers sometimes return HTML on errors. Check the status code before parsing to be safe. Some APIs also require an `Accept: application/json` header. If you're calling a JSON API repeatedly, [C03. Set default headers](../c03-default-headers) can save you some boilerplate.
|
||||||
|
|
||||||
> For how to receive and return JSON on the server side, see [S02. Receive JSON requests and return JSON responses](s02-json-api).
|
> For how to receive and return JSON on the server side, see [S02. Receive JSON requests and return JSON responses](../s02-json-api).
|
||||||
|
|||||||
@@ -52,4 +52,4 @@ auto res = cli.Get("/users", headers);
|
|||||||
|
|
||||||
Per-request headers are **added** on top of the defaults. Both are sent to the server.
|
Per-request headers are **added** on top of the defaults. Both are sent to the server.
|
||||||
|
|
||||||
> For details on Bearer token auth, see [C06. Call an API with a Bearer token](c06-bearer-token).
|
> For details on Bearer token auth, see [C06. Call an API with a Bearer token](../c06-bearer-token).
|
||||||
|
|||||||
@@ -35,4 +35,4 @@ Many sites redirect HTTP traffic to HTTPS. With `set_follow_location(true)` on,
|
|||||||
|
|
||||||
> **Warning:** To follow redirects to HTTPS, you need to build cpp-httplib with OpenSSL (or another TLS backend). Without TLS support, redirects to HTTPS will fail.
|
> **Warning:** To follow redirects to HTTPS, you need to build cpp-httplib with OpenSSL (or another TLS backend). Without TLS support, redirects to HTTPS will fail.
|
||||||
|
|
||||||
> **Note:** Following redirects adds to the total request time. See [C12. Set timeouts](c12-timeouts) for timeout configuration.
|
> **Note:** Following redirects adds to the total request time. See [C12. Set timeouts](../c12-timeouts) for timeout configuration.
|
||||||
|
|||||||
@@ -43,4 +43,4 @@ For the more secure Digest authentication scheme, use `set_digest_auth()`. This
|
|||||||
cli.set_digest_auth("alice", "s3cret");
|
cli.set_digest_auth("alice", "s3cret");
|
||||||
```
|
```
|
||||||
|
|
||||||
> To call an API with a Bearer token, see [C06. Call an API with a Bearer token](c06-bearer-token).
|
> To call an API with a Bearer token, see [C06. Call an API with a Bearer token](../c06-bearer-token).
|
||||||
|
|||||||
@@ -47,4 +47,4 @@ if (res && res->status == 401) {
|
|||||||
|
|
||||||
> **Warning:** A Bearer token is itself a credential. Always send it over HTTPS, and never hard-code it into source or config files.
|
> **Warning:** A Bearer token is itself a credential. Always send it over HTTPS, and never hard-code it into source or config files.
|
||||||
|
|
||||||
> To set multiple headers at once, see [C03. Set default headers](c03-default-headers).
|
> To set multiple headers at once, see [C03. Set default headers](../c03-default-headers).
|
||||||
|
|||||||
@@ -49,4 +49,4 @@ The arguments to `make_file_provider()` are `(form name, file path, file name, c
|
|||||||
|
|
||||||
> **Note:** You can mix `UploadFormDataItems` and `FormDataProviderItems` in the same request. A clean split is: text fields in `UploadFormDataItems`, files in `FormDataProviderItems`.
|
> **Note:** You can mix `UploadFormDataItems` and `FormDataProviderItems` in the same request. A clean split is: text fields in `UploadFormDataItems`, files in `FormDataProviderItems`.
|
||||||
|
|
||||||
> To show upload progress, see [C11. Use the progress callback](c11-progress-callback).
|
> To show upload progress, see [C11. Use the progress callback](../c11-progress-callback).
|
||||||
|
|||||||
@@ -31,4 +31,4 @@ If the file can't be opened, `make_file_body()` returns `size` as `0` and `provi
|
|||||||
|
|
||||||
> **Warning:** `make_file_body()` needs to fix the Content-Length up front, so it reads the file size ahead of time. If the file size might change mid-upload, this API isn't the right fit.
|
> **Warning:** `make_file_body()` needs to fix the Content-Length up front, so it reads the file size ahead of time. If the file size might change mid-upload, this API isn't the right fit.
|
||||||
|
|
||||||
> To send the file as multipart form data instead, see [C07. Upload a file as multipart form data](c07-multipart-upload).
|
> To send the file as multipart form data instead, see [C07. Upload a file as multipart form data](../c07-multipart-upload).
|
||||||
|
|||||||
@@ -44,4 +44,4 @@ With a known size, the request carries a Content-Length header — so the server
|
|||||||
|
|
||||||
> **Detail:** `sink.write()` returns a `bool` indicating whether the write succeeded. If it returns `false`, the connection is gone — return `false` from the lambda to stop.
|
> **Detail:** `sink.write()` returns a `bool` indicating whether the write succeeded. If it returns `false`, the connection is gone — return `false` from the lambda to stop.
|
||||||
|
|
||||||
> If you're just sending a file, `make_file_body()` is easier. See [C08. POST a file as raw binary](c08-post-file-body).
|
> If you're just sending a file, `make_file_body()` is easier. See [C08. POST a file as raw binary](../c08-post-file-body).
|
||||||
|
|||||||
@@ -48,5 +48,5 @@ Accumulate into a buffer, then pull out and parse one line each time you see a n
|
|||||||
|
|
||||||
> **Warning:** When you pass a `ContentReceiver`, `res->body` stays **empty**. Store or process the body inside the callback yourself.
|
> **Warning:** When you pass a `ContentReceiver`, `res->body` stays **empty**. Store or process the body inside the callback yourself.
|
||||||
|
|
||||||
> To track download progress, combine this with [C11. Use the progress callback](c11-progress-callback).
|
> To track download progress, combine this with [C11. Use the progress callback](../c11-progress-callback).
|
||||||
> For Server-Sent Events (SSE), see [E04. Receive SSE on the client](e04-sse-client).
|
> For Server-Sent Events (SSE), see [E04. Receive SSE on the client](../e04-sse-client).
|
||||||
|
|||||||
@@ -56,4 +56,4 @@ auto res = cli.Get("/large-file",
|
|||||||
|
|
||||||
> **Note:** `ContentReceiver` and the progress callback can be used together. When you want to stream to a file and show progress at the same time, pass both.
|
> **Note:** `ContentReceiver` and the progress callback can be used together. When you want to stream to a file and show progress at the same time, pass both.
|
||||||
|
|
||||||
> For a concrete example of saving to a file, see [C01. Get the response body / save to a file](c01-get-response-body).
|
> For a concrete example of saving to a file, see [C01. Get the response body / save to a file](../c01-get-response-body).
|
||||||
|
|||||||
@@ -47,4 +47,4 @@ cli.set_connection_timeout(3s);
|
|||||||
cli.set_read_timeout(10s);
|
cli.set_read_timeout(10s);
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Warning:** The read timeout covers a single receive call — not the whole request. If data keeps trickling in during a large download, the request can take half an hour without ever hitting the timeout. To cap the total request time, use [C13. Set an overall timeout](c13-max-timeout).
|
> **Warning:** The read timeout covers a single receive call — not the whole request. If data keeps trickling in during a large download, the request can take half an hour without ever hitting the timeout. To cap the total request time, use [C13. Set an overall timeout](../c13-max-timeout).
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ order: 13
|
|||||||
status: "draft"
|
status: "draft"
|
||||||
---
|
---
|
||||||
|
|
||||||
The three timeouts from [C12. Set timeouts](c12-timeouts) all apply to a single `send` or `recv` call. To cap the total time a request can take, use `set_max_timeout()`.
|
The three timeouts from [C12. Set timeouts](../c12-timeouts) all apply to a single `send` or `recv` call. To cap the total time a request can take, use `set_max_timeout()`.
|
||||||
|
|
||||||
## Basic usage
|
## Basic usage
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ cli.set_proxy_digest_auth("user", "password");
|
|||||||
|
|
||||||
## Combine with end-server authentication
|
## Combine with end-server authentication
|
||||||
|
|
||||||
Proxy authentication is separate from authenticating to the end server ([C05. Use Basic authentication](c05-basic-auth), [C06. Call an API with a Bearer token](c06-bearer-token)). When both are needed, set both.
|
Proxy authentication is separate from authenticating to the end server ([C05. Use Basic authentication](../c05-basic-auth), [C06. Call an API with a Bearer token](../c06-bearer-token)). When both are needed, set both.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
cli.set_proxy("proxy.internal", 8080);
|
cli.set_proxy("proxy.internal", 8080);
|
||||||
@@ -49,4 +49,39 @@ cli.set_bearer_token_auth("api-token"); // for the end server
|
|||||||
|
|
||||||
`Proxy-Authorization` is sent to the proxy, `Authorization` to the end server.
|
`Proxy-Authorization` is sent to the proxy, `Authorization` to the end server.
|
||||||
|
|
||||||
> **Note:** cpp-httplib does not read `HTTP_PROXY` or `HTTPS_PROXY` environment variables automatically. If you want to honor them, read them in your application and pass the values to `set_proxy()`.
|
## Bypass the proxy for specific hosts
|
||||||
|
|
||||||
|
You often want internal endpoints to skip the proxy. Configure a bypass list with `set_no_proxy()`.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
cli.set_proxy("proxy.internal", 8080);
|
||||||
|
cli.set_no_proxy({"internal.corp", "10.0.0.0/8", "*.dev.local"});
|
||||||
|
```
|
||||||
|
|
||||||
|
Each entry is one of:
|
||||||
|
|
||||||
|
- `*` — bypass the proxy for all hosts
|
||||||
|
- a hostname suffix (e.g. `example.com`) — matches `example.com` itself and any subdomain (`foo.example.com`). A leading dot is permitted but informational; both forms are equivalent.
|
||||||
|
- a single IP literal (e.g. `192.168.1.1`, `::1`)
|
||||||
|
- a CIDR block (e.g. `10.0.0.0/8`, `fe80::/10`)
|
||||||
|
|
||||||
|
Hostname matching is case-insensitive and uses a dot-boundary rule, so an entry of `example.com` does **not** match `evilexample.com`. IP comparisons are normalized through `inet_pton`, so `127.0.0.1` cannot be bypassed via alternate string forms (e.g. `127.000.000.001`). When an entry matches, the `Proxy-Authorization` header is suppressed as well.
|
||||||
|
|
||||||
|
Malformed entries are silently dropped. Port-specific entries such as `example.com:8080` are not supported (cpp-httplib's other host-keyed APIs are also keyed on hostname only).
|
||||||
|
|
||||||
|
## Read proxy settings from the environment
|
||||||
|
|
||||||
|
cpp-httplib doesn't touch `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` on its own — the config API is always explicit, the same way `set_ca_cert_path()` is. If you'd like that behavior, read the variables in your application and feed them to `set_proxy()` and `set_no_proxy()`.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if (const char *v = std::getenv("no_proxy")) {
|
||||||
|
std::vector<std::string> patterns;
|
||||||
|
std::stringstream ss(v);
|
||||||
|
for (std::string item; std::getline(ss, item, ',');) {
|
||||||
|
if (!item.empty()) { patterns.push_back(item); }
|
||||||
|
}
|
||||||
|
cli.set_no_proxy(patterns);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you also read `HTTP_PROXY` yourself, honor the lowercase `http_proxy` only. The uppercase form is poisoned in CGI/FastCGI environments by the `Proxy:` request header ([CVE-2016-5385 / "httpoxy"](https://httpoxy.org/)). `HTTPS_PROXY` and `NO_PROXY` are safe in either case because their names don't begin with `HTTP_`.
|
||||||
|
|||||||
@@ -60,4 +60,4 @@ std::cout << res->body << std::endl;
|
|||||||
|
|
||||||
Keep them separated in your head: network-layer errors go through `res.error()`, HTTP-level errors through `res->status`.
|
Keep them separated in your head: network-layer errors go through `res.error()`, HTTP-level errors through `res->status`.
|
||||||
|
|
||||||
> To dig deeper into SSL-related errors, see [C18. Handle SSL errors](c18-ssl-errors).
|
> To dig deeper into SSL-related errors, see [C18. Handle SSL errors](../c18-ssl-errors).
|
||||||
|
|||||||
@@ -48,4 +48,4 @@ if (res.ssl_backend_error() != 0) {
|
|||||||
| `SSLServerHostnameVerification` | The cert's CN/SAN doesn't match the host |
|
| `SSLServerHostnameVerification` | The cert's CN/SAN doesn't match the host |
|
||||||
| `SSLConnection` | TLS version mismatch, no shared cipher suite |
|
| `SSLConnection` | TLS version mismatch, no shared cipher suite |
|
||||||
|
|
||||||
> To change certificate verification settings, see [T02. Control SSL certificate verification](t02-cert-verification).
|
> To change certificate verification settings, see [T02. Control SSL certificate verification](../t02-cert-verification).
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ svr.Get("/time", [](const httplib::Request &req, httplib::Response &res) {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
When the client disconnects, call `sink.done()` to stop. Details in [S16. Detect client disconnection](s16-disconnect).
|
When the client disconnects, call `sink.done()` to stop. Details in [S16. Detect client disconnection](../s16-disconnect).
|
||||||
|
|
||||||
## Heartbeats via comment lines
|
## Heartbeats via comment lines
|
||||||
|
|
||||||
@@ -80,8 +80,8 @@ svr.new_task_queue = [] {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
See [S21. Configure the thread pool](s21-thread-pool).
|
See [S21. Configure the thread pool](../s21-thread-pool).
|
||||||
|
|
||||||
> **Note:** When `data:` contains newlines, split it into multiple `data:` lines — one per line. This is how the SSE spec requires multiline data to be transmitted.
|
> **Note:** When `data:` contains newlines, split it into multiple `data:` lines — one per line. This is how the SSE spec requires multiline data to be transmitted.
|
||||||
|
|
||||||
> For event names, see [E02. Use named events in SSE](e02-sse-event-names). For the client side, see [E04. Receive SSE on the client](e04-sse-client).
|
> For event names, see [E02. Use named events in SSE](../e02-sse-event-names). For the client side, see [E04. Receive SSE on the client](../e04-sse-client).
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ auto send_event = [](httplib::DataSink &sink,
|
|||||||
send_event(sink, "message", "Hello!", "42");
|
send_event(sink, "message", "Hello!", "42");
|
||||||
```
|
```
|
||||||
|
|
||||||
The ID format is up to you. Monotonic counters or UUIDs both work — just pick something unique and orderable on the server side. See [E03. Handle SSE reconnection](e03-sse-reconnect) for details.
|
The ID format is up to you. Monotonic counters or UUIDs both work — just pick something unique and orderable on the server side. See [E03. Handle SSE reconnection](../e03-sse-reconnect) for details.
|
||||||
|
|
||||||
## JSON payloads in data
|
## JSON payloads in data
|
||||||
|
|
||||||
|
|||||||
@@ -96,4 +96,4 @@ Use `last_event_id()` to read the current value.
|
|||||||
|
|
||||||
> **Note:** `SSEClient::start()` blocks, which is fine for a one-off command-line tool. For GUI apps or embedded in a server, the `start_async()` + `stop()` pair is the usual pattern.
|
> **Note:** `SSEClient::start()` blocks, which is fine for a one-off command-line tool. For GUI apps or embedded in a server, the `start_async()` + `stop()` pair is the usual pattern.
|
||||||
|
|
||||||
> For the server side, see [E01. Implement an SSE server](e01-sse-server).
|
> For the server side, see [E01. Implement an SSE server](../e01-sse-server).
|
||||||
|
|||||||
@@ -61,6 +61,6 @@ svr.Get("/me", [](const httplib::Request &req, httplib::Response &res) {
|
|||||||
|
|
||||||
To add a response header, use `res.set_header("Name", "Value")`.
|
To add a response header, use `res.set_header("Name", "Value")`.
|
||||||
|
|
||||||
> **Note:** `listen()` is a blocking call. To run it on a different thread, wrap it in `std::thread`. If you need non-blocking startup, see [S18. Control startup order with `listen_after_bind`](s18-listen-after-bind).
|
> **Note:** `listen()` is a blocking call. To run it on a different thread, wrap it in `std::thread`. If you need non-blocking startup, see [S18. Control startup order with `listen_after_bind`](../s18-listen-after-bind).
|
||||||
|
|
||||||
> To use path parameters like `/users/:id`, see [S03. Use path parameters](s03-path-params).
|
> To use path parameters like `/users/:id`, see [S03. Use path parameters](../s03-path-params).
|
||||||
|
|||||||
@@ -69,6 +69,6 @@ svr.Get("/api/health", [&](const auto &req, auto &res) {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note:** A large JSON body ends up entirely in `req.body`, which means it all sits in memory. For huge payloads, consider streaming reception — see [S07. Receive multipart data as a stream](s07-multipart-reader).
|
> **Note:** A large JSON body ends up entirely in `req.body`, which means it all sits in memory. For huge payloads, consider streaming reception — see [S07. Receive multipart data as a stream](../s07-multipart-reader).
|
||||||
|
|
||||||
> For the client side, see [C02. Send and receive JSON](c02-json).
|
> For the client side, see [C02. Send and receive JSON](../c02-json).
|
||||||
|
|||||||
@@ -52,4 +52,4 @@ svr.set_file_extension_and_mimetype_mapping("wasm", "application/wasm");
|
|||||||
|
|
||||||
> **Warning:** The static file server methods are **not thread-safe**. Don't call them after `listen()` — configure everything before starting the server.
|
> **Warning:** The static file server methods are **not thread-safe**. Don't call them after `listen()` — configure everything before starting the server.
|
||||||
|
|
||||||
> For download-style responses, see [S06. Return a file download response](s06-download-response).
|
> For download-style responses, see [S06. Return a file download response](../s06-download-response).
|
||||||
|
|||||||
@@ -60,4 +60,4 @@ Call `sink.done()` to signal the end.
|
|||||||
|
|
||||||
> **Note:** The provider lambda is called multiple times. Watch out for the lifetime of captured variables — wrap them in a `std::shared_ptr` if needed.
|
> **Note:** The provider lambda is called multiple times. Watch out for the lifetime of captured variables — wrap them in a `std::shared_ptr` if needed.
|
||||||
|
|
||||||
> To serve the file as a download, see [S06. Return a file download response](s06-download-response).
|
> To serve the file as a download, see [S06. Return a file download response](../s06-download-response).
|
||||||
|
|||||||
@@ -68,4 +68,4 @@ Only a small chunk sits in memory at any moment, so gigabyte-scale files are no
|
|||||||
|
|
||||||
> **Warning:** When you use `HandlerWithContentReader`, `req.body` stays **empty**. Handle the body yourself inside the callbacks.
|
> **Warning:** When you use `HandlerWithContentReader`, `req.body` stays **empty**. Handle the body yourself inside the callbacks.
|
||||||
|
|
||||||
> For the client side of multipart uploads, see [C07. Upload a file as multipart form data](c07-multipart-upload).
|
> For the client side of multipart uploads, see [C07. Upload a file as multipart form data](../c07-multipart-upload).
|
||||||
|
|||||||
@@ -50,4 +50,4 @@ svr.Get("/events", [](const httplib::Request &req, httplib::Response &res) {
|
|||||||
|
|
||||||
> **Note:** Tiny responses barely benefit from compression and just waste CPU time. cpp-httplib skips compression for bodies that are too small to bother with.
|
> **Note:** Tiny responses barely benefit from compression and just waste CPU time. cpp-httplib skips compression for bodies that are too small to bother with.
|
||||||
|
|
||||||
> For the client-side counterpart, see [C15. Enable compression](c15-compression).
|
> For the client-side counterpart, see [C15. Enable compression](../c15-compression).
|
||||||
|
|||||||
@@ -49,6 +49,6 @@ If auth fails, return `Handled` to respond with 401 immediately. If it passes, r
|
|||||||
|
|
||||||
## For per-route auth
|
## For per-route auth
|
||||||
|
|
||||||
If you want different auth rules per route rather than a single global check, `set_pre_request_handler()` is a better fit. See [S11. Authenticate per route with a pre-request handler](s11-pre-request).
|
If you want different auth rules per route rather than a single global check, `set_pre_request_handler()` is a better fit. See [S11. Authenticate per route with a pre-request handler](../s11-pre-request).
|
||||||
|
|
||||||
> **Note:** If all you want is to modify the response, `set_post_routing_handler()` is the right tool. See [S10. Add response headers with a post-routing handler](s10-post-routing).
|
> **Note:** If all you want is to modify the response, `set_post_routing_handler()` is the right tool. See [S10. Add response headers with a post-routing handler](../s10-post-routing).
|
||||||
|
|||||||
@@ -4,17 +4,19 @@ order: 30
|
|||||||
status: "draft"
|
status: "draft"
|
||||||
---
|
---
|
||||||
|
|
||||||
The `set_pre_routing_handler()` from [S09. Add pre-processing to all routes](s09-pre-routing) runs **before routing**, so it has no idea which route matched. When you want per-route behavior, `set_pre_request_handler()` is what you need.
|
The `set_pre_routing_handler()` from [S09. Add pre-processing to all routes](../s09-pre-routing) runs **before routing**, so it has no idea which route matched. When you want per-route behavior, `set_pre_request_handler()` is what you need.
|
||||||
|
|
||||||
## Pre-routing vs. pre-request
|
## Pre-routing vs. pre-request
|
||||||
|
|
||||||
| Hook | When it runs | Route info |
|
| Hook | When it runs | Route info | Request body |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `set_pre_routing_handler` | Before routing | Not available |
|
| `set_pre_routing_handler` | Before routing | Not available | Not read yet |
|
||||||
| `set_pre_request_handler` | After routing, right before the route handler | Available via `req.matched_route` |
|
| `set_pre_request_handler` | After routing, right before the route handler | Available via `req.matched_route` | Not read yet |
|
||||||
|
|
||||||
In a pre-request handler, `req.matched_route` holds the **pattern string** that matched. You can vary behavior based on the route definition itself.
|
In a pre-request handler, `req.matched_route` holds the **pattern string** that matched. You can vary behavior based on the route definition itself.
|
||||||
|
|
||||||
|
Because the body has not been read when the pre-request handler runs, you can reject a request — for example on a failed auth check — without consuming a (potentially large) request body. Note that this also means `req.body` and form fields parsed from the body are not available here; inspect headers, the path, query parameters, or `req.matched_route` instead.
|
||||||
|
|
||||||
## Switch auth per route
|
## Switch auth per route
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
@@ -44,4 +46,4 @@ Same as pre-routing — return `HandlerResponse`.
|
|||||||
|
|
||||||
## Passing auth info to the route handler
|
## Passing auth info to the route handler
|
||||||
|
|
||||||
To pass decoded user info into the route handler, use `res.user_data`. See [S12. Pass data between handlers with `res.user_data`](s12-user-data).
|
To pass decoded user info into the route handler, use `res.user_data`. See [S12. Pass data between handlers with `res.user_data`](../s12-user-data).
|
||||||
|
|||||||
@@ -48,4 +48,4 @@ svr.set_error_handler([](const httplib::Request &req, httplib::Response &res) {
|
|||||||
|
|
||||||
Now every error comes back in a consistent JSON shape.
|
Now every error comes back in a consistent JSON shape.
|
||||||
|
|
||||||
> **Note:** `set_error_handler()` also fires for 500 responses caused by exceptions thrown from a route handler. To get at the exception itself, combine it with `set_exception_handler()`. See [S14. Catch exceptions](s14-exception-handler).
|
> **Note:** `set_error_handler()` also fires for 500 responses caused by exceptions thrown from a route handler. To get at the exception itself, combine it with `set_exception_handler()`. See [S14. Catch exceptions](../s14-exception-handler).
|
||||||
|
|||||||
@@ -59,6 +59,6 @@ svr.set_logger([](const auto &req, const auto &res) {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
For more on `user_data`, see [S12. Pass data between handlers with `res.user_data`](s12-user-data).
|
For more on `user_data`, see [S12. Pass data between handlers with `res.user_data`](../s12-user-data).
|
||||||
|
|
||||||
> **Note:** The logger runs synchronously on the same thread as request processing. Heavy work inside it hurts throughput — push it to a queue and process asynchronously if you need anything expensive.
|
> **Note:** The logger runs synchronously on the same thread as request processing. Heavy work inside it hurts throughput — push it to a queue and process asynchronously if you need anything expensive.
|
||||||
|
|||||||
@@ -49,4 +49,4 @@ Because the port is assigned at runtime, parallel test runs don't collide.
|
|||||||
|
|
||||||
> **Note:** `bind_to_any_port()` returns `-1` on failure (permission errors, no available ports, etc.). Always check the return value.
|
> **Note:** `bind_to_any_port()` returns `-1` on failure (permission errors, no available ports, etc.). Always check the return value.
|
||||||
|
|
||||||
> To stop the server, see [S19. Shut down gracefully](s19-graceful-shutdown).
|
> To stop the server, see [S19. Shut down gracefully](../s19-graceful-shutdown).
|
||||||
|
|||||||
@@ -54,4 +54,4 @@ if (!svr.bind_to_port("0.0.0.0", 8080)) {
|
|||||||
|
|
||||||
`listen_after_bind()` blocks until the server stops and returns `true` on a clean shutdown.
|
`listen_after_bind()` blocks until the server stops and returns `true` on a clean shutdown.
|
||||||
|
|
||||||
> **Note:** To auto-pick a free port, see [S17. Bind to any available port](s17-bind-any-port). Under the hood, that's just `bind_to_any_port()` + `listen_after_bind()`.
|
> **Note:** To auto-pick a free port, see [S17. Bind to any available port](../s17-bind-any-port). Under the hood, that's just `bind_to_any_port()` + `listen_after_bind()`.
|
||||||
|
|||||||
@@ -52,6 +52,6 @@ Set `set_keep_alive_max_count(1)` and every request gets its own connection. Mos
|
|||||||
|
|
||||||
## Relationship with the thread pool
|
## Relationship with the thread pool
|
||||||
|
|
||||||
A Keep-Alive connection holds a worker thread for its entire lifetime. If `connections × concurrent requests` exceeds the thread pool size, new requests wait. For thread counts, see [S21. Configure the thread pool](s21-thread-pool).
|
A Keep-Alive connection holds a worker thread for its entire lifetime. If `connections × concurrent requests` exceeds the thread pool size, new requests wait. For thread counts, see [S21. Configure the thread pool](../s21-thread-pool).
|
||||||
|
|
||||||
> **Note:** For the client side, see [C14. Understand connection reuse and Keep-Alive behavior](c14-keep-alive). Even when the server closes the connection on timeout, the client reconnects automatically.
|
> **Note:** For the client side, see [C14. Understand connection reuse and Keep-Alive behavior](../c14-keep-alive). Even when the server closes the connection on timeout, the client reconnects automatically.
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ The usual approach is to treat each backend as a build variant and recompile the
|
|||||||
|
|
||||||
Certificate verification control, standing up an SSLServer, reading the peer certificate — these all share the same API across backends:
|
Certificate verification control, standing up an SSLServer, reading the peer certificate — these all share the same API across backends:
|
||||||
|
|
||||||
- [T02. Control SSL certificate verification](t02-cert-verification)
|
- [T02. Control SSL certificate verification](../t02-cert-verification)
|
||||||
- [T03. Start an SSL/TLS server](t03-ssl-server)
|
- [T03. Start an SSL/TLS server](../t03-ssl-server)
|
||||||
- [T05. Access the peer certificate on the server](t05-peer-cert)
|
- [T05. Access the peer certificate on the server](../t05-peer-cert)
|
||||||
|
|
||||||
> **Note:** On macOS with an OpenSSL-family backend, cpp-httplib automatically loads root certificates from the system keychain (via `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`, on by default). To disable this, define `CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES`.
|
> **Note:** On macOS with an OpenSSL-family backend, cpp-httplib automatically loads root certificates from the system keychain (via `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`, on by default). To disable this, define `CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES`.
|
||||||
|
|||||||
@@ -48,6 +48,6 @@ The certificate itself is still validated, so this is safer than fully disabling
|
|||||||
|
|
||||||
On most Linux distributions, root certificates live in a single file like `/etc/ssl/certs/ca-certificates.crt`. cpp-httplib reads the OS default store at startup, so for most servers you don't need to configure anything.
|
On most Linux distributions, root certificates live in a single file like `/etc/ssl/certs/ca-certificates.crt`. cpp-httplib reads the OS default store at startup, so for most servers you don't need to configure anything.
|
||||||
|
|
||||||
> The same APIs work on the mbedTLS and wolfSSL backends. For choosing between backends, see [T01. Choosing between OpenSSL, mbedTLS, and wolfSSL](t01-tls-backends).
|
> The same APIs work on the mbedTLS and wolfSSL backends. For choosing between backends, see [T01. Choosing between OpenSSL, mbedTLS, and wolfSSL](../t01-tls-backends).
|
||||||
|
|
||||||
> For details on diagnosing failures, see [C18. Handle SSL errors](c18-ssl-errors).
|
> For details on diagnosing failures, see [C18. Handle SSL errors](../c18-ssl-errors).
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ httplib::SSLServer svr("cert.pem", "key.pem",
|
|||||||
nullptr, nullptr, "password");
|
nullptr, nullptr, "password");
|
||||||
```
|
```
|
||||||
|
|
||||||
The third and fourth arguments are for client certificate verification (mTLS, see [T04. Configure mTLS](t04-mtls)). For now, pass `nullptr`.
|
The third and fourth arguments are for client certificate verification (mTLS, see [T04. Configure mTLS](../t04-mtls)). For now, pass `nullptr`.
|
||||||
|
|
||||||
## Load PEM data from memory
|
## Load PEM data from memory
|
||||||
|
|
||||||
@@ -73,6 +73,6 @@ openssl req -x509 -newkey rsa:2048 -days 365 -nodes \
|
|||||||
|
|
||||||
In production, use certificates from Let's Encrypt or your internal CA.
|
In production, use certificates from Let's Encrypt or your internal CA.
|
||||||
|
|
||||||
> **Warning:** Binding an HTTPS server to port 443 requires root. For a safe way to do that, see the privilege-drop pattern in [S18. Control startup order with `listen_after_bind`](s18-listen-after-bind).
|
> **Warning:** Binding an HTTPS server to port 443 requires root. For a safe way to do that, see the privilege-drop pattern in [S18. Control startup order with `listen_after_bind`](../s18-listen-after-bind).
|
||||||
|
|
||||||
> For mutual TLS (client certificates), see [T04. Configure mTLS](t04-mtls).
|
> For mutual TLS (client certificates), see [T04. Configure mTLS](../t04-mtls).
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ Note you're using `SSLClient` directly, not `Client`. If the private key has a p
|
|||||||
|
|
||||||
## Read client info from a handler
|
## Read client info from a handler
|
||||||
|
|
||||||
To see which client connected from inside a handler, use `req.peer_cert()`. Details in [T05. Access the peer certificate on the server](t05-peer-cert).
|
To see which client connected from inside a handler, use `req.peer_cert()`. Details in [T05. Access the peer certificate on the server](../t05-peer-cert).
|
||||||
|
|
||||||
## Use cases
|
## Use cases
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ svr.set_pre_request_handler(
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Combined with a pre-request handler, you can keep all authorization logic in one place. See [S11. Authenticate per route with a pre-request handler](s11-pre-request).
|
Combined with a pre-request handler, you can keep all authorization logic in one place. See [S11. Authenticate per route with a pre-request handler](../s11-pre-request).
|
||||||
|
|
||||||
## SNI (Server Name Indication)
|
## SNI (Server Name Indication)
|
||||||
|
|
||||||
@@ -85,4 +85,4 @@ cpp-httplib handles SNI automatically. If one server hosts multiple domains, SNI
|
|||||||
|
|
||||||
> **Warning:** `req.peer_cert()` only returns a meaningful value when mTLS is enabled and the client actually presented a certificate. For plain TLS, you get an empty `PeerCert`. Always do the `bool` check before using it.
|
> **Warning:** `req.peer_cert()` only returns a meaningful value when mTLS is enabled and the client actually presented a certificate. For plain TLS, you get an empty `PeerCert`. Always do the `bool` check before using it.
|
||||||
|
|
||||||
> To set up mTLS, see [T04. Configure mTLS](t04-mtls).
|
> To set up mTLS, see [T04. Configure mTLS](../t04-mtls).
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ ws.send("Hello"); // text frame
|
|||||||
ws.send(binary_data, binary_data_size); // binary frame
|
ws.send(binary_data, binary_data_size); // binary frame
|
||||||
```
|
```
|
||||||
|
|
||||||
The `std::string` overload sends as **text**; the `const char*` + size overload sends as **binary**. A bit subtle, but once you know it, it's intuitive. See [W04. Send and receive binary frames](w04-websocket-binary) for details.
|
The `std::string` overload sends as **text**; the `const char*` + size overload sends as **binary**. A bit subtle, but once you know it, it's intuitive. See [W04. Send and receive binary frames](../w04-websocket-binary) for details.
|
||||||
|
|
||||||
## Thread pool implications
|
## Thread pool implications
|
||||||
|
|
||||||
@@ -83,6 +83,6 @@ svr.new_task_queue = [] {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
See [S21. Configure the thread pool](s21-thread-pool).
|
See [S21. Configure the thread pool](../s21-thread-pool).
|
||||||
|
|
||||||
> **Note:** To run WebSocket over HTTPS, use `httplib::SSLServer` instead of `httplib::Server` — the same `WebSocket()` handler just works. On the client side, use a `wss://` URL.
|
> **Note:** To run WebSocket over HTTPS, use `httplib::SSLServer` instead of `httplib::Server` — the same `WebSocket()` handler just works. On the client side, use a `wss://` URL.
|
||||||
|
|||||||
@@ -77,4 +77,4 @@ The counter is reset whenever `read()` consumes an incoming Pong frame, so this
|
|||||||
|
|
||||||
Even with `0`, a dead connection won't linger forever: while your code is inside `read()`, `CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND` (default **300 seconds = 5 minutes**) acts as a backstop and `read()` fails if no frame arrives in time. Think of `max_missed_pongs` as the knob for detecting an unresponsive peer **faster** than that.
|
Even with `0`, a dead connection won't linger forever: while your code is inside `read()`, `CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND` (default **300 seconds = 5 minutes**) acts as a backstop and `read()` fails if no frame arrives in time. Think of `max_missed_pongs` as the knob for detecting an unresponsive peer **faster** than that.
|
||||||
|
|
||||||
> For handling a closed connection, see [W03. Handle connection close](w03-websocket-close).
|
> For handling a closed connection, see [W03. Handle connection close](../w03-websocket-close).
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ Binary frames still come back in a `std::string`, but treat its contents as raw
|
|||||||
|
|
||||||
## Ping is binary-ish, but hidden
|
## Ping is binary-ish, but hidden
|
||||||
|
|
||||||
WebSocket Ping/Pong frames are close cousins of binary frames at the opcode level, but cpp-httplib handles them automatically — you don't touch them. See [W02. Set a WebSocket heartbeat](w02-websocket-ping).
|
WebSocket Ping/Pong frames are close cousins of binary frames at the opcode level, but cpp-httplib handles them automatically — you don't touch them. See [W02. Set a WebSocket heartbeat](../w02-websocket-ping).
|
||||||
|
|
||||||
## Example: send an image
|
## Example: send an image
|
||||||
|
|
||||||
|
|||||||
@@ -57,4 +57,4 @@ auto res = cli.Get("/large-file",
|
|||||||
>
|
>
|
||||||
> `ResponseHandler`はヘッダー受信後、ボディ受信前に呼ばれます。`false`を返せばダウンロード自体をスキップできます。
|
> `ResponseHandler`はヘッダー受信後、ボディ受信前に呼ばれます。`false`を返せばダウンロード自体をスキップできます。
|
||||||
|
|
||||||
> ダウンロードの進捗を表示したい場合は[C11. 進捗コールバックを使う](c11-progress-callback)を参照してください。
|
> ダウンロードの進捗を表示したい場合は[C11. 進捗コールバックを使う](../c11-progress-callback)を参照してください。
|
||||||
|
|||||||
@@ -31,6 +31,6 @@ if (res && res->status == 200) {
|
|||||||
|
|
||||||
`res->body`は`std::string`なので、そのままJSONライブラリに渡せます。
|
`res->body`は`std::string`なので、そのままJSONライブラリに渡せます。
|
||||||
|
|
||||||
> **Note:** サーバーがエラー時にHTMLを返すことがあります。ステータスコードを確認してからパースすると安全です。また、APIによっては`Accept: application/json`ヘッダーが必要です。JSON APIを繰り返し呼ぶなら[C03. デフォルトヘッダーを設定する](c03-default-headers)が便利です。
|
> **Note:** サーバーがエラー時にHTMLを返すことがあります。ステータスコードを確認してからパースすると安全です。また、APIによっては`Accept: application/json`ヘッダーが必要です。JSON APIを繰り返し呼ぶなら[C03. デフォルトヘッダーを設定する](../c03-default-headers)が便利です。
|
||||||
|
|
||||||
> サーバー側でJSONを受け取って返す方法は[S02. JSONリクエストを受け取りJSONレスポンスを返す](s02-json-api)を参照してください。
|
> サーバー側でJSONを受け取って返す方法は[S02. JSONリクエストを受け取りJSONレスポンスを返す](../s02-json-api)を参照してください。
|
||||||
|
|||||||
@@ -52,4 +52,4 @@ auto res = cli.Get("/users", headers);
|
|||||||
|
|
||||||
リクエスト単位で渡したヘッダーはデフォルトヘッダーに**追加**されます。両方がサーバーに送られます。
|
リクエスト単位で渡したヘッダーはデフォルトヘッダーに**追加**されます。両方がサーバーに送られます。
|
||||||
|
|
||||||
> Bearerトークンを使った認証の詳細は[C06. BearerトークンでAPIを呼ぶ](c06-bearer-token)を参照してください。
|
> Bearerトークンを使った認証の詳細は[C06. BearerトークンでAPIを呼ぶ](../c06-bearer-token)を参照してください。
|
||||||
|
|||||||
@@ -35,4 +35,4 @@ auto res = cli.Get("/");
|
|||||||
|
|
||||||
> **Warning:** HTTPSへのリダイレクトを追従するには、cpp-httplibをOpenSSL(または他のTLSバックエンド)付きでビルドしておく必要があります。TLSサポートがないと、HTTPSへのリダイレクトは失敗します。
|
> **Warning:** HTTPSへのリダイレクトを追従するには、cpp-httplibをOpenSSL(または他のTLSバックエンド)付きでビルドしておく必要があります。TLSサポートがないと、HTTPSへのリダイレクトは失敗します。
|
||||||
|
|
||||||
> **Note:** リダイレクトを追従すると、リクエストの実行時間は伸びます。タイムアウトの設定は[C12. タイムアウトを設定する](c12-timeouts)を参照してください。
|
> **Note:** リダイレクトを追従すると、リクエストの実行時間は伸びます。タイムアウトの設定は[C12. タイムアウトを設定する](../c12-timeouts)を参照してください。
|
||||||
|
|||||||
@@ -43,4 +43,4 @@ auto res = cli.Get("/private", headers);
|
|||||||
cli.set_digest_auth("alice", "s3cret");
|
cli.set_digest_auth("alice", "s3cret");
|
||||||
```
|
```
|
||||||
|
|
||||||
> BearerトークンでAPIを呼びたい場合は[C06. BearerトークンでAPIを呼ぶ](c06-bearer-token)を参照してください。
|
> BearerトークンでAPIを呼びたい場合は[C06. BearerトークンでAPIを呼ぶ](../c06-bearer-token)を参照してください。
|
||||||
|
|||||||
@@ -47,4 +47,4 @@ if (res && res->status == 401) {
|
|||||||
|
|
||||||
> **Warning:** Bearerトークンはそれ自体が認証情報です。必ずHTTPS経由で送ってください。また、ソースコードや設定ファイルにトークンをハードコードしないようにしましょう。
|
> **Warning:** Bearerトークンはそれ自体が認証情報です。必ずHTTPS経由で送ってください。また、ソースコードや設定ファイルにトークンをハードコードしないようにしましょう。
|
||||||
|
|
||||||
> 複数のヘッダーをまとめて設定したいときは[C03. デフォルトヘッダーを設定する](c03-default-headers)も便利です。
|
> 複数のヘッダーをまとめて設定したいときは[C03. デフォルトヘッダーを設定する](../c03-default-headers)も便利です。
|
||||||
|
|||||||
@@ -49,4 +49,4 @@ auto res = cli.Post("/upload", httplib::Headers{}, items, provider_items);
|
|||||||
|
|
||||||
> **Note:** `UploadFormDataItems`と`FormDataProviderItems`は同じリクエスト内で併用できます。テキストフィールドは`UploadFormDataItems`、ファイルは`FormDataProviderItems`、という使い分けがきれいです。
|
> **Note:** `UploadFormDataItems`と`FormDataProviderItems`は同じリクエスト内で併用できます。テキストフィールドは`UploadFormDataItems`、ファイルは`FormDataProviderItems`、という使い分けがきれいです。
|
||||||
|
|
||||||
> アップロードの進捗を表示したい場合は[C11. 進捗コールバックを使う](c11-progress-callback)を参照してください。
|
> アップロードの進捗を表示したい場合は[C11. 進捗コールバックを使う](../c11-progress-callback)を参照してください。
|
||||||
|
|||||||
@@ -31,4 +31,4 @@ auto res = cli.Put("/bucket/backup.tar.gz", size,
|
|||||||
|
|
||||||
> **Warning:** `make_file_body()`はContent-Lengthを最初に確定させる必要があるため、ファイルサイズをあらかじめ取得します。送信中にファイルサイズが変わる可能性がある場合は、このAPIには向きません。
|
> **Warning:** `make_file_body()`はContent-Lengthを最初に確定させる必要があるため、ファイルサイズをあらかじめ取得します。送信中にファイルサイズが変わる可能性がある場合は、このAPIには向きません。
|
||||||
|
|
||||||
> マルチパート形式で送りたい場合は[C07. ファイルをマルチパートフォームとしてアップロードする](c07-multipart-upload)を参照してください。
|
> マルチパート形式で送りたい場合は[C07. ファイルをマルチパートフォームとしてアップロードする](../c07-multipart-upload)を参照してください。
|
||||||
|
|||||||
@@ -44,4 +44,4 @@ auto res = cli.Post("/upload", total_size,
|
|||||||
|
|
||||||
> **Detail:** `sink.write()`は書き込みが成功したかどうかを`bool`で返します。`false`が返ったら回線が切れています。ラムダはそのまま`false`を返して終了しましょう。
|
> **Detail:** `sink.write()`は書き込みが成功したかどうかを`bool`で返します。`false`が返ったら回線が切れています。ラムダはそのまま`false`を返して終了しましょう。
|
||||||
|
|
||||||
> ファイルをそのまま送るだけなら、`make_file_body()`が便利です。[C08. ファイルを生バイナリとしてPOSTする](c08-post-file-body)を参照してください。
|
> ファイルをそのまま送るだけなら、`make_file_body()`が便利です。[C08. ファイルを生バイナリとしてPOSTする](../c08-post-file-body)を参照してください。
|
||||||
|
|||||||
@@ -48,5 +48,5 @@ auto res = cli.Get("/events",
|
|||||||
|
|
||||||
> **Warning:** `ContentReceiver`を渡すと、`res->body`は**空のまま**になります。ボディは自分でコールバック内で保存するか処理するかしてください。
|
> **Warning:** `ContentReceiver`を渡すと、`res->body`は**空のまま**になります。ボディは自分でコールバック内で保存するか処理するかしてください。
|
||||||
|
|
||||||
> ダウンロードの進捗を知りたい場合は[C11. 進捗コールバックを使う](c11-progress-callback)と組み合わせましょう。
|
> ダウンロードの進捗を知りたい場合は[C11. 進捗コールバックを使う](../c11-progress-callback)と組み合わせましょう。
|
||||||
> Server-Sent Events(SSE)を扱うときは[E04. SSEをクライアントで受信する](e04-sse-client)も参考になります。
|
> Server-Sent Events(SSE)を扱うときは[E04. SSEをクライアントで受信する](../e04-sse-client)も参考になります。
|
||||||
|
|||||||
@@ -56,4 +56,4 @@ auto res = cli.Get("/large-file",
|
|||||||
|
|
||||||
> **Note:** `ContentReceiver`と進捗コールバックは同時に使えます。ファイルに書き出しながら進捗を表示したいときは、両方を渡しましょう。
|
> **Note:** `ContentReceiver`と進捗コールバックは同時に使えます。ファイルに書き出しながら進捗を表示したいときは、両方を渡しましょう。
|
||||||
|
|
||||||
> ファイル保存と組み合わせる具体例は[C01. レスポンスボディを取得する / ファイルに保存する](c01-get-response-body)も参照してください。
|
> ファイル保存と組み合わせる具体例は[C01. レスポンスボディを取得する / ファイルに保存する](../c01-get-response-body)も参照してください。
|
||||||
|
|||||||
@@ -47,4 +47,4 @@ cli.set_connection_timeout(3s);
|
|||||||
cli.set_read_timeout(10s);
|
cli.set_read_timeout(10s);
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Warning:** 読み取りタイムアウトは「1回の受信待ち」に対するタイムアウトです。大きなファイルのダウンロードで途中ずっとデータが流れている限り、リクエスト全体で30分かかっても発火しません。リクエスト全体の時間制限を設けたい場合は[C13. 全体タイムアウトを設定する](c13-max-timeout)を使ってください。
|
> **Warning:** 読み取りタイムアウトは「1回の受信待ち」に対するタイムアウトです。大きなファイルのダウンロードで途中ずっとデータが流れている限り、リクエスト全体で30分かかっても発火しません。リクエスト全体の時間制限を設けたい場合は[C13. 全体タイムアウトを設定する](../c13-max-timeout)を使ってください。
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ order: 13
|
|||||||
status: "draft"
|
status: "draft"
|
||||||
---
|
---
|
||||||
|
|
||||||
[C12. タイムアウトを設定する](c12-timeouts)で紹介した3種類のタイムアウトは、いずれも「1回の`send`や`recv`」に対するものです。リクエスト全体の所要時間に上限を設けたい場合は、`set_max_timeout()`を使います。
|
[C12. タイムアウトを設定する](../c12-timeouts)で紹介した3種類のタイムアウトは、いずれも「1回の`send`や`recv`」に対するものです。リクエスト全体の所要時間に上限を設けたい場合は、`set_max_timeout()`を使います。
|
||||||
|
|
||||||
## 基本の使い方
|
## 基本の使い方
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ cli.set_proxy_digest_auth("user", "password");
|
|||||||
|
|
||||||
## エンドのサーバー認証と組み合わせる
|
## エンドのサーバー認証と組み合わせる
|
||||||
|
|
||||||
プロキシ認証と、エンドサーバーへの認証([C05. Basic認証を使う](c05-basic-auth)や[C06. BearerトークンでAPIを呼ぶ](c06-bearer-token))は別物です。両方が必要なら、両方設定します。
|
プロキシ認証と、エンドサーバーへの認証([C05. Basic認証を使う](../c05-basic-auth)や[C06. BearerトークンでAPIを呼ぶ](../c06-bearer-token))は別物です。両方が必要なら、両方設定します。
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
cli.set_proxy("proxy.internal", 8080);
|
cli.set_proxy("proxy.internal", 8080);
|
||||||
@@ -49,4 +49,39 @@ cli.set_bearer_token_auth("api-token"); // エンドサーバー向け
|
|||||||
|
|
||||||
プロキシには`Proxy-Authorization`、エンドサーバーには`Authorization`ヘッダーが送られます。
|
プロキシには`Proxy-Authorization`、エンドサーバーには`Authorization`ヘッダーが送られます。
|
||||||
|
|
||||||
> **Note:** 環境変数の`HTTP_PROXY`や`HTTPS_PROXY`は自動的には読まれません。必要ならアプリケーション側で読み取って`set_proxy()`に渡してください。
|
## 特定のホストだけプロキシをバイパスする
|
||||||
|
|
||||||
|
社内エンドポイントなどはプロキシを経由させたくないことがあります。`set_no_proxy()`で除外リストを指定できます。
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
cli.set_proxy("proxy.internal", 8080);
|
||||||
|
cli.set_no_proxy({"internal.corp", "10.0.0.0/8", "*.dev.local"});
|
||||||
|
```
|
||||||
|
|
||||||
|
エントリは次のいずれかです。
|
||||||
|
|
||||||
|
- `*` — すべてのホストでバイパス
|
||||||
|
- ホスト名サフィックス(例: `example.com`)— `example.com`本体と任意のサブドメイン(`foo.example.com`)にマッチ。先頭にドットを付けても同じ意味です(`.example.com`)。
|
||||||
|
- 単一のIPリテラル(例: `192.168.1.1`、`::1`)
|
||||||
|
- CIDRブロック(例: `10.0.0.0/8`、`fe80::/10`)
|
||||||
|
|
||||||
|
ホスト名のマッチは大文字小文字を区別せず、ドット境界でしか一致しません。たとえば`example.com`というエントリは`evilexample.com`にはマッチしません。IPの比較は`inet_pton`で正規化されるので、`127.0.0.1`を`127.000.000.001`のような別表記でバイパスすることはできません。マッチした場合、`Proxy-Authorization`ヘッダーも自動的に外れます。
|
||||||
|
|
||||||
|
不正な書式のエントリは黙って捨てられます。`example.com:8080`のようなポート指定エントリはサポート外です(cpp-httplibの他のホストキーAPIもホスト名のみを扱う設計のため)。
|
||||||
|
|
||||||
|
## 環境変数からプロキシ設定を読み込む
|
||||||
|
|
||||||
|
cpp-httplib本体は`HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY`を読みません。`set_ca_cert_path()`と同じで、設定APIは常に明示的にしています。環境変数を反映させたい場合は、アプリ側で読んで`set_proxy()`や`set_no_proxy()`に渡してください。
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if (const char *v = std::getenv("no_proxy")) {
|
||||||
|
std::vector<std::string> patterns;
|
||||||
|
std::stringstream ss(v);
|
||||||
|
for (std::string item; std::getline(ss, item, ',');) {
|
||||||
|
if (!item.empty()) { patterns.push_back(item); }
|
||||||
|
}
|
||||||
|
cli.set_no_proxy(patterns);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`HTTP_PROXY`も自分で読むなら、小文字の`http_proxy`だけを採用してください。大文字の方はCGI/FastCGI環境で`Proxy:`リクエストヘッダーから汚染される可能性があります([CVE-2016-5385 / "httpoxy"](https://httpoxy.org/))。`HTTPS_PROXY`と`NO_PROXY`は名前が`HTTP_`で始まらないので、どちらの大文字小文字でも安全です。
|
||||||
|
|||||||
@@ -60,4 +60,4 @@ std::cout << res->body << std::endl;
|
|||||||
|
|
||||||
ネットワーク層のエラーは`res.error()`、HTTPのエラーは`res->status`、と頭の中で分けておきましょう。
|
ネットワーク層のエラーは`res.error()`、HTTPのエラーは`res->status`、と頭の中で分けておきましょう。
|
||||||
|
|
||||||
> SSL関連のエラーをさらに詳しく調べたい場合は[C18. SSLエラーをハンドリングする](c18-ssl-errors)を参照してください。
|
> SSL関連のエラーをさらに詳しく調べたい場合は[C18. SSLエラーをハンドリングする](../c18-ssl-errors)を参照してください。
|
||||||
|
|||||||
@@ -48,4 +48,4 @@ if (res.ssl_backend_error() != 0) {
|
|||||||
| `SSLServerHostnameVerification` | 証明書のCN/SANとホスト名が一致しない |
|
| `SSLServerHostnameVerification` | 証明書のCN/SANとホスト名が一致しない |
|
||||||
| `SSLConnection` | TLSバージョンの不一致、対応スイートが無い |
|
| `SSLConnection` | TLSバージョンの不一致、対応スイートが無い |
|
||||||
|
|
||||||
> 証明書の検証設定を変えたい場合は[T02. SSL証明書の検証を制御する](t02-cert-verification)を参照してください。
|
> 証明書の検証設定を変えたい場合は[T02. SSL証明書の検証を制御する](../t02-cert-verification)を参照してください。
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ svr.Get("/time", [](const httplib::Request &req, httplib::Response &res) {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
クライアントが切断したら`sink.done()`で終了します。詳しくは[S16. クライアントが切断したか検出する](s16-disconnect)を参照してください。
|
クライアントが切断したら`sink.done()`で終了します。詳しくは[S16. クライアントが切断したか検出する](../s16-disconnect)を参照してください。
|
||||||
|
|
||||||
## コメント行でハートビート
|
## コメント行でハートビート
|
||||||
|
|
||||||
@@ -80,8 +80,8 @@ svr.new_task_queue = [] {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
詳しくは[S21. マルチスレッド数を設定する](s21-thread-pool)を参照してください。
|
詳しくは[S21. マルチスレッド数を設定する](../s21-thread-pool)を参照してください。
|
||||||
|
|
||||||
> **Note:** `data:`の後ろに改行が含まれる場合、各行の先頭に`data: `を付けて複数の`data:`行として送ります。SSEの仕様で決まっているフォーマットです。
|
> **Note:** `data:`の後ろに改行が含まれる場合、各行の先頭に`data: `を付けて複数の`data:`行として送ります。SSEの仕様で決まっているフォーマットです。
|
||||||
|
|
||||||
> イベント名を使い分けたい場合は[E02. SSEでイベント名を使い分ける](e02-sse-event-names)を、クライアント側は[E04. SSEをクライアントで受信する](e04-sse-client)を参照してください。
|
> イベント名を使い分けたい場合は[E02. SSEでイベント名を使い分ける](../e02-sse-event-names)を、クライアント側は[E04. SSEをクライアントで受信する](../e04-sse-client)を参照してください。
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ auto send_event = [](httplib::DataSink &sink,
|
|||||||
send_event(sink, "message", "Hello!", "42");
|
send_event(sink, "message", "Hello!", "42");
|
||||||
```
|
```
|
||||||
|
|
||||||
IDの付け方は自由です。連番でもUUIDでも、サーバー側で重複せず順序が追えるものを選びましょう。再接続の詳細は[E03. SSEの再接続を処理する](e03-sse-reconnect)を参照してください。
|
IDの付け方は自由です。連番でもUUIDでも、サーバー側で重複せず順序が追えるものを選びましょう。再接続の詳細は[E03. SSEの再接続を処理する](../e03-sse-reconnect)を参照してください。
|
||||||
|
|
||||||
## JSONをdataに乗せる
|
## JSONをdataに乗せる
|
||||||
|
|
||||||
|
|||||||
@@ -96,4 +96,4 @@ std::cout << "last id: " << sse.last_event_id() << std::endl;
|
|||||||
|
|
||||||
> **Note:** SSEClientの`start()`はブロッキングなので、単発のツールならそのまま使えますが、GUIアプリやサーバーに組み込むときは`start_async()` + `stop()`の組み合わせが基本です。
|
> **Note:** SSEClientの`start()`はブロッキングなので、単発のツールならそのまま使えますが、GUIアプリやサーバーに組み込むときは`start_async()` + `stop()`の組み合わせが基本です。
|
||||||
|
|
||||||
> サーバー側の実装は[E01. SSEサーバーを実装する](e01-sse-server)を参照してください。
|
> サーバー側の実装は[E01. SSEサーバーを実装する](../e01-sse-server)を参照してください。
|
||||||
|
|||||||
@@ -61,6 +61,6 @@ svr.Get("/me", [](const httplib::Request &req, httplib::Response &res) {
|
|||||||
|
|
||||||
レスポンスヘッダーを追加したいときは`res.set_header("Name", "Value")`です。
|
レスポンスヘッダーを追加したいときは`res.set_header("Name", "Value")`です。
|
||||||
|
|
||||||
> **Note:** `listen()`はブロックする関数です。別スレッドで動かしたいときは`std::thread`で包むか、ノンブロッキング起動が必要なら[S18. `listen_after_bind`で起動順序を制御する](s18-listen-after-bind)を参照してください。
|
> **Note:** `listen()`はブロックする関数です。別スレッドで動かしたいときは`std::thread`で包むか、ノンブロッキング起動が必要なら[S18. `listen_after_bind`で起動順序を制御する](../s18-listen-after-bind)を参照してください。
|
||||||
|
|
||||||
> パスパラメーター(`/users/:id`)を使いたい場合は[S03. パスパラメーターを使う](s03-path-params)を参照してください。
|
> パスパラメーター(`/users/:id`)を使いたい場合は[S03. パスパラメーターを使う](../s03-path-params)を参照してください。
|
||||||
|
|||||||
@@ -69,6 +69,6 @@ svr.Get("/api/health", [&](const auto &req, auto &res) {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note:** 大きなJSONボディを受け取ると、`req.body`がまるごとメモリに載ります。巨大なペイロードを扱うときは[S07. マルチパートデータをストリーミングで受け取る](s07-multipart-reader)のように、ストリーミング受信も検討しましょう。
|
> **Note:** 大きなJSONボディを受け取ると、`req.body`がまるごとメモリに載ります。巨大なペイロードを扱うときは[S07. マルチパートデータをストリーミングで受け取る](../s07-multipart-reader)のように、ストリーミング受信も検討しましょう。
|
||||||
|
|
||||||
> クライアント側の書き方は[C02. JSONを送受信する](c02-json)を参照してください。
|
> クライアント側の書き方は[C02. JSONを送受信する](../c02-json)を参照してください。
|
||||||
|
|||||||
@@ -52,4 +52,4 @@ svr.set_file_extension_and_mimetype_mapping("wasm", "application/wasm");
|
|||||||
|
|
||||||
> **Warning:** 静的ファイル配信系のメソッドは**スレッドセーフではありません**。起動後(`listen()`以降)には呼ばないでください。起動前にまとめて設定しましょう。
|
> **Warning:** 静的ファイル配信系のメソッドは**スレッドセーフではありません**。起動後(`listen()`以降)には呼ばないでください。起動前にまとめて設定しましょう。
|
||||||
|
|
||||||
> ダウンロード用のレスポンスを返したい場合は[S06. ファイルダウンロードレスポンスを返す](s06-download-response)も参考になります。
|
> ダウンロード用のレスポンスを返したい場合は[S06. ファイルダウンロードレスポンスを返す](../s06-download-response)も参考になります。
|
||||||
|
|||||||
@@ -60,4 +60,4 @@ svr.Get("/events", [](const httplib::Request &req, httplib::Response &res) {
|
|||||||
|
|
||||||
> **Note:** プロバイダラムダは複数回呼ばれます。キャプチャする変数のライフタイムに気をつけてください。必要なら`std::shared_ptr`などで包みましょう。
|
> **Note:** プロバイダラムダは複数回呼ばれます。キャプチャする変数のライフタイムに気をつけてください。必要なら`std::shared_ptr`などで包みましょう。
|
||||||
|
|
||||||
> ファイルダウンロードとして扱いたい場合は[S06. ファイルダウンロードレスポンスを返す](s06-download-response)を参照してください。
|
> ファイルダウンロードとして扱いたい場合は[S06. ファイルダウンロードレスポンスを返す](../s06-download-response)を参照してください。
|
||||||
|
|||||||
@@ -68,4 +68,4 @@ svr.Post("/upload",
|
|||||||
|
|
||||||
> **Warning:** `HandlerWithContentReader`を使うと、`req.body`は**空のまま**です。ボディはコールバック内で自分で処理してください。
|
> **Warning:** `HandlerWithContentReader`を使うと、`req.body`は**空のまま**です。ボディはコールバック内で自分で処理してください。
|
||||||
|
|
||||||
> クライアント側でマルチパートを送る方法は[C07. ファイルをマルチパートフォームとしてアップロードする](c07-multipart-upload)を参照してください。
|
> クライアント側でマルチパートを送る方法は[C07. ファイルをマルチパートフォームとしてアップロードする](../c07-multipart-upload)を参照してください。
|
||||||
|
|||||||
@@ -50,4 +50,4 @@ svr.Get("/events", [](const httplib::Request &req, httplib::Response &res) {
|
|||||||
|
|
||||||
> **Note:** 小さなレスポンスは圧縮しても効果が薄く、むしろCPU時間を無駄にすることがあります。cpp-httplibは小さすぎるボディは圧縮をスキップします。
|
> **Note:** 小さなレスポンスは圧縮しても効果が薄く、むしろCPU時間を無駄にすることがあります。cpp-httplibは小さすぎるボディは圧縮をスキップします。
|
||||||
|
|
||||||
> クライアント側の挙動は[C15. 圧縮を有効にする](c15-compression)を参照してください。
|
> クライアント側の挙動は[C15. 圧縮を有効にする](../c15-compression)を参照してください。
|
||||||
|
|||||||
@@ -49,6 +49,6 @@ svr.set_pre_routing_handler(
|
|||||||
|
|
||||||
## 特定ルートだけに認証をかけたい場合
|
## 特定ルートだけに認証をかけたい場合
|
||||||
|
|
||||||
全ルート共通ではなく、ルート単位で認証を分けたいときは、[S11. Pre-request handlerでルート単位の認証を行う](s11-pre-request)のほうが適しています。
|
全ルート共通ではなく、ルート単位で認証を分けたいときは、[S11. Pre-request handlerでルート単位の認証を行う](../s11-pre-request)のほうが適しています。
|
||||||
|
|
||||||
> **Note:** レスポンスを加工したいだけなら、`set_post_routing_handler()`のほうが適切です。[S10. Post-routing handlerでレスポンスヘッダーを追加する](s10-post-routing)を参照してください。
|
> **Note:** レスポンスを加工したいだけなら、`set_post_routing_handler()`のほうが適切です。[S10. Post-routing handlerでレスポンスヘッダーを追加する](../s10-post-routing)を参照してください。
|
||||||
|
|||||||
@@ -4,17 +4,19 @@ order: 30
|
|||||||
status: "draft"
|
status: "draft"
|
||||||
---
|
---
|
||||||
|
|
||||||
[S09. 全ルートに共通の前処理をする](s09-pre-routing)で紹介した`set_pre_routing_handler()`はルーティングの**前**に呼ばれるので、「どのルートにマッチしたか」を知れません。ルートによって認証の有無を変えたい場合は、`set_pre_request_handler()`のほうが便利です。
|
[S09. 全ルートに共通の前処理をする](../s09-pre-routing)で紹介した`set_pre_routing_handler()`はルーティングの**前**に呼ばれるので、「どのルートにマッチしたか」を知れません。ルートによって認証の有無を変えたい場合は、`set_pre_request_handler()`のほうが便利です。
|
||||||
|
|
||||||
## Pre-routingとの違い
|
## Pre-routingとの違い
|
||||||
|
|
||||||
| フック | 呼ばれるタイミング | ルート情報 |
|
| フック | 呼ばれるタイミング | ルート情報 | リクエストボディ |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `set_pre_routing_handler` | ルーティングの前 | 取得できない |
|
| `set_pre_routing_handler` | ルーティングの前 | 取得できない | まだ読まれていない |
|
||||||
| `set_pre_request_handler` | ルーティング後、ルートハンドラの直前 | `req.matched_route`で取得可能 |
|
| `set_pre_request_handler` | ルーティング後、ルートハンドラの直前 | `req.matched_route`で取得可能 | まだ読まれていない |
|
||||||
|
|
||||||
Pre-requestハンドラなら、`req.matched_route`に「マッチしたパターン文字列」が入っているので、ルートに応じて処理を変えられます。
|
Pre-requestハンドラなら、`req.matched_route`に「マッチしたパターン文字列」が入っているので、ルートに応じて処理を変えられます。
|
||||||
|
|
||||||
|
Pre-requestハンドラが呼ばれる時点ではボディがまだ読まれていないので、認証に失敗したリクエストなどを、(巨大かもしれない)ボディを読み込む前に拒否できます。その代わり、`req.body`やボディから解析されるフォームフィールドはこの時点では参照できません。ヘッダ・パス・クエリパラメータ・`req.matched_route`を使って判断してください。
|
||||||
|
|
||||||
## ルートごとに認証を切り替える
|
## ルートごとに認証を切り替える
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
@@ -44,4 +46,4 @@ Pre-routingハンドラと同じく、`HandlerResponse`を返します。
|
|||||||
|
|
||||||
## 認証情報を後続のハンドラに渡す
|
## 認証情報を後続のハンドラに渡す
|
||||||
|
|
||||||
認証で取り出したユーザー情報などをルートハンドラに渡したいときは、`res.user_data`を使います。詳しくは[S12. `res.user_data`でハンドラ間データを渡す](s12-user-data)を参照してください。
|
認証で取り出したユーザー情報などをルートハンドラに渡したいときは、`res.user_data`を使います。詳しくは[S12. `res.user_data`でハンドラ間データを渡す](../s12-user-data)を参照してください。
|
||||||
|
|||||||
@@ -48,4 +48,4 @@ svr.set_error_handler([](const httplib::Request &req, httplib::Response &res) {
|
|||||||
|
|
||||||
これで全エラーが統一されたJSONで返ります。
|
これで全エラーが統一されたJSONで返ります。
|
||||||
|
|
||||||
> **Note:** `set_error_handler()`は、ルートハンドラが例外を投げた場合の500エラーにも呼ばれます。例外そのものの情報を取り出したい場合は`set_exception_handler()`を組み合わせましょう。[S14. 例外をキャッチする](s14-exception-handler)を参照してください。
|
> **Note:** `set_error_handler()`は、ルートハンドラが例外を投げた場合の500エラーにも呼ばれます。例外そのものの情報を取り出したい場合は`set_exception_handler()`を組み合わせましょう。[S14. 例外をキャッチする](../s14-exception-handler)を参照してください。
|
||||||
|
|||||||
@@ -59,6 +59,6 @@ svr.set_logger([](const auto &req, const auto &res) {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
`user_data`の使い方は[S12. `res.user_data`でハンドラ間データを渡す](s12-user-data)も参照してください。
|
`user_data`の使い方は[S12. `res.user_data`でハンドラ間データを渡す](../s12-user-data)も参照してください。
|
||||||
|
|
||||||
> **Note:** ロガーはリクエスト処理と同じスレッドで同期的に呼ばれます。重い処理を直接入れると全体のスループットが落ちるので、必要ならキューに流して非同期で処理しましょう。
|
> **Note:** ロガーはリクエスト処理と同じスレッドで同期的に呼ばれます。重い処理を直接入れると全体のスループットが落ちるので、必要ならキューに流して非同期で処理しましょう。
|
||||||
|
|||||||
@@ -49,4 +49,4 @@ t.join();
|
|||||||
|
|
||||||
> **Note:** `bind_to_any_port()`は失敗すると`-1`を返します。権限エラーや利用可能ポートが無いケースなので、返り値のチェックを忘れずに。
|
> **Note:** `bind_to_any_port()`は失敗すると`-1`を返します。権限エラーや利用可能ポートが無いケースなので、返り値のチェックを忘れずに。
|
||||||
|
|
||||||
> サーバーを止める方法は[S19. グレースフルシャットダウンする](s19-graceful-shutdown)を参照してください。
|
> サーバーを止める方法は[S19. グレースフルシャットダウンする](../s19-graceful-shutdown)を参照してください。
|
||||||
|
|||||||
@@ -54,4 +54,4 @@ if (!svr.bind_to_port("0.0.0.0", 8080)) {
|
|||||||
|
|
||||||
`listen_after_bind()`はサーバーが停止するまでブロックし、正常終了なら`true`を返します。
|
`listen_after_bind()`はサーバーが停止するまでブロックし、正常終了なら`true`を返します。
|
||||||
|
|
||||||
> **Note:** 空いているポートを自動で選びたいときは[S17. ポートを動的に割り当てる](s17-bind-any-port)を参照してください。こちらも内部では`bind_to_any_port()` + `listen_after_bind()`の組み合わせです。
|
> **Note:** 空いているポートを自動で選びたいときは[S17. ポートを動的に割り当てる](../s17-bind-any-port)を参照してください。こちらも内部では`bind_to_any_port()` + `listen_after_bind()`の組み合わせです。
|
||||||
|
|||||||
@@ -52,6 +52,6 @@ svr.set_keep_alive_max_count(1000);
|
|||||||
|
|
||||||
## スレッドプールとの関係
|
## スレッドプールとの関係
|
||||||
|
|
||||||
Keep-Aliveでつながりっぱなしの接続は、その間ずっとワーカースレッドを1つ占有します。接続数 × 同時リクエスト数がスレッドプールのサイズを超えると、新しいリクエストが待たされます。スレッド数の調整は[S21. マルチスレッド数を設定する](s21-thread-pool)を参照してください。
|
Keep-Aliveでつながりっぱなしの接続は、その間ずっとワーカースレッドを1つ占有します。接続数 × 同時リクエスト数がスレッドプールのサイズを超えると、新しいリクエストが待たされます。スレッド数の調整は[S21. マルチスレッド数を設定する](../s21-thread-pool)を参照してください。
|
||||||
|
|
||||||
> **Note:** クライアント側の挙動は[C14. 接続の再利用とKeep-Aliveの挙動を理解する](c14-keep-alive)を参照してください。サーバーがタイムアウトで接続を切っても、クライアントは自動で再接続します。
|
> **Note:** クライアント側の挙動は[C14. 接続の再利用とKeep-Aliveの挙動を理解する](../c14-keep-alive)を参照してください。サーバーがタイムアウトで接続を切っても、クライアントは自動で再接続します。
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ wolfSSLには商用ライセンスとサポートがあります。製品に組
|
|||||||
|
|
||||||
証明書の検証制御、SSLServerの立ち上げ、ピア証明書の取得などは、どのバックエンドでも同じAPIで呼べます。
|
証明書の検証制御、SSLServerの立ち上げ、ピア証明書の取得などは、どのバックエンドでも同じAPIで呼べます。
|
||||||
|
|
||||||
- [T02. SSL証明書の検証を制御する](t02-cert-verification)
|
- [T02. SSL証明書の検証を制御する](../t02-cert-verification)
|
||||||
- [T03. SSL/TLSサーバーを立ち上げる](t03-ssl-server)
|
- [T03. SSL/TLSサーバーを立ち上げる](../t03-ssl-server)
|
||||||
- [T05. サーバー側でピア証明書を参照する](t05-peer-cert)
|
- [T05. サーバー側でピア証明書を参照する](../t05-peer-cert)
|
||||||
|
|
||||||
> **Note:** macOSでは、OpenSSL系のバックエンドを使う場合、システムのキーチェーンからルート証明書を自動で読む設定(`CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`)がデフォルトで有効です。無効にしたい場合は`CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES`を定義してください。
|
> **Note:** macOSでは、OpenSSL系のバックエンドを使う場合、システムのキーチェーンからルート証明書を自動で読む設定(`CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`)がデフォルトで有効です。無効にしたい場合は`CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES`を定義してください。
|
||||||
|
|||||||
@@ -48,6 +48,6 @@ cli.enable_server_hostname_verification(false);
|
|||||||
|
|
||||||
多くのLinuxディストリビューションでは、`/etc/ssl/certs/ca-certificates.crt`などにルート証明書がまとまっています。cpp-httplibは起動時にOSのデフォルトストアを自動で読みにいくので、普通のサーバーならとくに設定不要です。
|
多くのLinuxディストリビューションでは、`/etc/ssl/certs/ca-certificates.crt`などにルート証明書がまとまっています。cpp-httplibは起動時にOSのデフォルトストアを自動で読みにいくので、普通のサーバーならとくに設定不要です。
|
||||||
|
|
||||||
> mbedTLSやwolfSSLバックエンドでも同じAPIが使えます。バックエンドの選び方は[T01. OpenSSL・mbedTLS・wolfSSLの選択指針](t01-tls-backends)を参照してください。
|
> mbedTLSやwolfSSLバックエンドでも同じAPIが使えます。バックエンドの選び方は[T01. OpenSSL・mbedTLS・wolfSSLの選択指針](../t01-tls-backends)を参照してください。
|
||||||
|
|
||||||
> 失敗したときの詳細を調べる方法は[C18. SSLエラーをハンドリングする](c18-ssl-errors)を参照してください。
|
> 失敗したときの詳細を調べる方法は[C18. SSLエラーをハンドリングする](../c18-ssl-errors)を参照してください。
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ httplib::SSLServer svr("cert.pem", "key.pem",
|
|||||||
nullptr, nullptr, "password");
|
nullptr, nullptr, "password");
|
||||||
```
|
```
|
||||||
|
|
||||||
第3、第4引数はクライアント証明書検証用(mTLS、[T04. mTLSを設定する](t04-mtls)参照)なので、今は`nullptr`を指定します。
|
第3、第4引数はクライアント証明書検証用(mTLS、[T04. mTLSを設定する](../t04-mtls)参照)なので、今は`nullptr`を指定します。
|
||||||
|
|
||||||
## メモリ上のPEMから立ち上げる
|
## メモリ上のPEMから立ち上げる
|
||||||
|
|
||||||
@@ -73,6 +73,6 @@ openssl req -x509 -newkey rsa:2048 -days 365 -nodes \
|
|||||||
|
|
||||||
本番では、Let's Encryptや社内CAから発行された証明書を使いましょう。
|
本番では、Let's Encryptや社内CAから発行された証明書を使いましょう。
|
||||||
|
|
||||||
> **Warning:** HTTPSサーバーを443番ポートで立ち上げるにはroot権限が必要です。安全に立ち上げる方法は[S18. `listen_after_bind`で起動順序を制御する](s18-listen-after-bind)の「特権降格」を参照してください。
|
> **Warning:** HTTPSサーバーを443番ポートで立ち上げるにはroot権限が必要です。安全に立ち上げる方法は[S18. `listen_after_bind`で起動順序を制御する](../s18-listen-after-bind)の「特権降格」を参照してください。
|
||||||
|
|
||||||
> クライアント証明書による相互認証(mTLS)は[T04. mTLSを設定する](t04-mtls)を参照してください。
|
> クライアント証明書による相互認証(mTLS)は[T04. mTLSを設定する](../t04-mtls)を参照してください。
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ auto res = cli.Get("/");
|
|||||||
|
|
||||||
## ハンドラからクライアント情報を取得する
|
## ハンドラからクライアント情報を取得する
|
||||||
|
|
||||||
ハンドラの中で、どのクライアントが接続してきたかを確認したいときは`req.peer_cert()`を使います。詳しくは[T05. サーバー側でピア証明書を参照する](t05-peer-cert)を参照してください。
|
ハンドラの中で、どのクライアントが接続してきたかを確認したいときは`req.peer_cert()`を使います。詳しくは[T05. サーバー側でピア証明書を参照する](../t05-peer-cert)を参照してください。
|
||||||
|
|
||||||
## 用途
|
## 用途
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ svr.set_pre_request_handler(
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Pre-requestハンドラと組み合わせれば、共通の認可ロジックを一箇所にまとめられます。詳しくは[S11. Pre-request handlerでルート単位の認証を行う](s11-pre-request)を参照してください。
|
Pre-requestハンドラと組み合わせれば、共通の認可ロジックを一箇所にまとめられます。詳しくは[S11. Pre-request handlerでルート単位の認証を行う](../s11-pre-request)を参照してください。
|
||||||
|
|
||||||
## SNI(Server Name Indication)
|
## SNI(Server Name Indication)
|
||||||
|
|
||||||
@@ -85,4 +85,4 @@ Pre-requestハンドラと組み合わせれば、共通の認可ロジックを
|
|||||||
|
|
||||||
> **Warning:** `req.peer_cert()`は、mTLSが有効で、かつクライアントが証明書を提示した場合のみ有効な値を返します。通常のTLS接続では空の`PeerCert`が返ります。使う前に必ず`bool`チェックしてください。
|
> **Warning:** `req.peer_cert()`は、mTLSが有効で、かつクライアントが証明書を提示した場合のみ有効な値を返します。通常のTLS接続では空の`PeerCert`が返ります。使う前に必ず`bool`チェックしてください。
|
||||||
|
|
||||||
> mTLSの設定方法は[T04. mTLSを設定する](t04-mtls)を参照してください。
|
> mTLSの設定方法は[T04. mTLSを設定する](../t04-mtls)を参照してください。
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ ws.send("Hello"); // テキストフレーム
|
|||||||
ws.send(binary_data, binary_data_size); // バイナリフレーム
|
ws.send(binary_data, binary_data_size); // バイナリフレーム
|
||||||
```
|
```
|
||||||
|
|
||||||
`std::string`を受け取るオーバーロードはテキスト、`const char*`とサイズを受け取るオーバーロードはバイナリとして送られます。詳しくは[W04. バイナリフレームを送受信する](w04-websocket-binary)を参照してください。
|
`std::string`を受け取るオーバーロードはテキスト、`const char*`とサイズを受け取るオーバーロードはバイナリとして送られます。詳しくは[W04. バイナリフレームを送受信する](../w04-websocket-binary)を参照してください。
|
||||||
|
|
||||||
## スレッドとの関係
|
## スレッドとの関係
|
||||||
|
|
||||||
@@ -83,6 +83,6 @@ svr.new_task_queue = [] {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
詳細は[S21. マルチスレッド数を設定する](s21-thread-pool)を参照してください。
|
詳細は[S21. マルチスレッド数を設定する](../s21-thread-pool)を参照してください。
|
||||||
|
|
||||||
> **Note:** HTTPSサーバーの上でWebSocketを動かしたいときは、`httplib::Server`の代わりに`httplib::SSLServer`を使えば、同じ`WebSocket()`ハンドラがそのまま動きます。クライアント側は`wss://`スキームを指定するだけです。
|
> **Note:** HTTPSサーバーの上でWebSocketを動かしたいときは、`httplib::Server`の代わりに`httplib::SSLServer`を使えば、同じ`WebSocket()`ハンドラがそのまま動きます。クライアント側は`wss://`スキームを指定するだけです。
|
||||||
|
|||||||
@@ -77,4 +77,4 @@ cli.set_websocket_max_missed_pongs(2); // 2回連続でPongが返ってこなけ
|
|||||||
|
|
||||||
ただし`0`のままでも最終的に接続が残り続けることはありません。`read()`を呼んでいる間は`CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND`(デフォルト**300秒 = 5分**)が保険として働き、フレームが一定時間来なければ`read()`が失敗します。つまり`max_missed_pongs`は「**もっと速く**無応答を検出したい」ときに使うオプションだと考えてください。
|
ただし`0`のままでも最終的に接続が残り続けることはありません。`read()`を呼んでいる間は`CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND`(デフォルト**300秒 = 5分**)が保険として働き、フレームが一定時間来なければ`read()`が失敗します。つまり`max_missed_pongs`は「**もっと速く**無応答を検出したい」ときに使うオプションだと考えてください。
|
||||||
|
|
||||||
> 接続が閉じたときの処理は[W03. 接続クローズをハンドリングする](w03-websocket-close)を参照してください。
|
> 接続が閉じたときの処理は[W03. 接続クローズをハンドリングする](../w03-websocket-close)を参照してください。
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ switch (result) {
|
|||||||
|
|
||||||
## Pingもバイナリフレームの一種
|
## Pingもバイナリフレームの一種
|
||||||
|
|
||||||
WebSocketのPing/PongフレームもOpcodeレベルではバイナリに近い扱いですが、cpp-httplibが自動で処理するので、アプリケーションコードで意識する必要はありません。[W02. ハートビートを設定する](w02-websocket-ping)を参照してください。
|
WebSocketのPing/PongフレームもOpcodeレベルではバイナリに近い扱いですが、cpp-httplibが自動で処理するので、アプリケーションコードで意識する必要はありません。[W02. ハートビートを設定する](../w02-websocket-ping)を参照してください。
|
||||||
|
|
||||||
## サンプル: 画像を送る
|
## サンプル: 画像を送る
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -107,7 +107,12 @@ if host_machine.system() == 'windows'
|
|||||||
elif host_machine.system() == 'darwin'
|
elif host_machine.system() == 'darwin'
|
||||||
async_ns_dep = dependency('appleframeworks', modules: ['CFNetwork', 'CoreFoundation'], required: async_ns_opt)
|
async_ns_dep = dependency('appleframeworks', modules: ['CFNetwork', 'CoreFoundation'], required: async_ns_opt)
|
||||||
else
|
else
|
||||||
async_ns_dep = cxx.find_library('anl', required: async_ns_opt)
|
has_gai_a = cxx.has_function('getaddrinfo_a', args: '-D_GNU_SOURCE')
|
||||||
|
if has_gai_a
|
||||||
|
async_ns_dep = declare_dependency()
|
||||||
|
else
|
||||||
|
async_ns_dep = cxx.find_library('anl', required: async_ns_opt)
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if async_ns_dep.found()
|
if async_ns_dep.found()
|
||||||
|
|||||||
+50
-16
@@ -2,10 +2,12 @@
|
|||||||
#
|
#
|
||||||
# Release a new version of cpp-httplib.
|
# Release a new version of cpp-httplib.
|
||||||
#
|
#
|
||||||
# Usage: ./release.sh [--run]
|
# Usage: ./release.sh [--run] [--minor]
|
||||||
#
|
#
|
||||||
# By default, runs in dry-run mode (no changes made).
|
# By default, runs in dry-run mode (no changes made).
|
||||||
# Pass --run to actually update files, commit, tag, and push.
|
# Pass --run to actually update files, commit, tag, and push.
|
||||||
|
# Pass --minor to force a minor bump even when ABI is unchanged
|
||||||
|
# (use this for behavioral breaking changes that don't break ABI).
|
||||||
#
|
#
|
||||||
# This script:
|
# This script:
|
||||||
# 1. Reads the current version from httplib.h
|
# 1. Reads the current version from httplib.h
|
||||||
@@ -14,21 +16,30 @@
|
|||||||
# 4. Determines the next version automatically:
|
# 4. Determines the next version automatically:
|
||||||
# - abidiff passed → patch bump (e.g., 0.38.0 → 0.38.1)
|
# - abidiff passed → patch bump (e.g., 0.38.0 → 0.38.1)
|
||||||
# - abidiff failed → minor bump (e.g., 0.38.1 → 0.39.0)
|
# - abidiff failed → minor bump (e.g., 0.38.1 → 0.39.0)
|
||||||
|
# - --minor passed → forces minor bump regardless of abidiff
|
||||||
# 5. Updates httplib.h and docs-src/config.toml
|
# 5. Updates httplib.h and docs-src/config.toml
|
||||||
# 6. Commits, tags (vX.Y.Z), and pushes
|
# 6. Commits, tags (vX.Y.Z), and pushes
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
DRY_RUN=1
|
DRY_RUN=1
|
||||||
if [ "${1:-}" = "--run" ]; then
|
FORCE_MINOR=0
|
||||||
DRY_RUN=0
|
while [ $# -gt 0 ]; do
|
||||||
shift
|
case "$1" in
|
||||||
fi
|
--run)
|
||||||
|
DRY_RUN=0
|
||||||
if [ $# -ne 0 ]; then
|
shift
|
||||||
echo "Usage: $0 [--run]"
|
;;
|
||||||
exit 1
|
--minor)
|
||||||
fi
|
FORCE_MINOR=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 [--run] [--minor]"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
# --- Step 1: Read current version from httplib.h ---
|
# --- Step 1: Read current version from httplib.h ---
|
||||||
CURRENT_VERSION=$(sed -n 's/^#define CPPHTTPLIB_VERSION "\([^"]*\)"/\1/p' httplib.h)
|
CURRENT_VERSION=$(sed -n 's/^#define CPPHTTPLIB_VERSION "\([^"]*\)"/\1/p' httplib.h)
|
||||||
@@ -51,8 +62,7 @@ HEAD_SHORT=$(git rev-parse --short HEAD)
|
|||||||
echo " Latest commit: $HEAD_SHORT"
|
echo " Latest commit: $HEAD_SHORT"
|
||||||
|
|
||||||
# Fetch all workflow runs for the HEAD commit
|
# Fetch all workflow runs for the HEAD commit
|
||||||
RUNS=$(gh run list --json name,conclusion,headSha \
|
RUNS=$(gh run list --commit "$HEAD_SHA" --json name,status,conclusion,headSha)
|
||||||
--jq "[.[] | select(.headSha == \"$HEAD_SHA\")]")
|
|
||||||
|
|
||||||
NUM_RUNS=$(echo "$RUNS" | jq 'length')
|
NUM_RUNS=$(echo "$RUNS" | jq 'length')
|
||||||
|
|
||||||
@@ -65,8 +75,17 @@ fi
|
|||||||
echo " Found $NUM_RUNS workflow run(s):"
|
echo " Found $NUM_RUNS workflow run(s):"
|
||||||
|
|
||||||
FAILED=0
|
FAILED=0
|
||||||
|
RUNNING=0
|
||||||
ABIDIFF_PASSED=0
|
ABIDIFF_PASSED=0
|
||||||
while IFS=$'\t' read -r name conclusion; do
|
while IFS=$'\t' read -r name status conclusion; do
|
||||||
|
# A run that hasn't completed yet has an empty conclusion; don't treat it
|
||||||
|
# as a failure — the release should wait until CI finishes.
|
||||||
|
if [ "$status" != "completed" ]; then
|
||||||
|
echo " [ .. ] $name (still running)"
|
||||||
|
RUNNING=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$name" == *abidiff* ]] || [[ "$name" == *abi* && "$name" != *stability* ]]; then
|
if [[ "$name" == *abidiff* ]] || [[ "$name" == *abi* && "$name" != *stability* ]]; then
|
||||||
if [ "$conclusion" = "success" ]; then
|
if [ "$conclusion" = "success" ]; then
|
||||||
echo " [ OK ] $name"
|
echo " [ OK ] $name"
|
||||||
@@ -84,7 +103,13 @@ while IFS=$'\t' read -r name conclusion; do
|
|||||||
echo " [FAIL] $name ($conclusion)"
|
echo " [FAIL] $name ($conclusion)"
|
||||||
FAILED=1
|
FAILED=1
|
||||||
fi
|
fi
|
||||||
done < <(echo "$RUNS" | jq -r '.[] | [.name, .conclusion] | @tsv')
|
done < <(echo "$RUNS" | jq -r '.[] | [.name, .status, .conclusion] | @tsv')
|
||||||
|
|
||||||
|
if [ "$RUNNING" -eq 1 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "Error: Some CI checks are still running. Wait for them to complete before releasing."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$FAILED" -eq 1 ]; then
|
if [ "$FAILED" -eq 1 ]; then
|
||||||
echo ""
|
echo ""
|
||||||
@@ -95,7 +120,12 @@ fi
|
|||||||
echo " All non-abidiff CI checks passed."
|
echo " All non-abidiff CI checks passed."
|
||||||
|
|
||||||
# --- Step 4: Determine new version ---
|
# --- Step 4: Determine new version ---
|
||||||
if [ "$ABIDIFF_PASSED" -eq 1 ]; then
|
if [ "$FORCE_MINOR" -eq 1 ] && [ "$ABIDIFF_PASSED" -eq 1 ]; then
|
||||||
|
NEW_MINOR=$((V_MINOR + 1))
|
||||||
|
NEW_VERSION="$V_MAJOR.$NEW_MINOR.0"
|
||||||
|
echo ""
|
||||||
|
echo "==> abidiff passed but --minor specified → forced minor bump"
|
||||||
|
elif [ "$ABIDIFF_PASSED" -eq 1 ]; then
|
||||||
NEW_PATCH=$((V_PATCH + 1))
|
NEW_PATCH=$((V_PATCH + 1))
|
||||||
NEW_VERSION="$V_MAJOR.$V_MINOR.$NEW_PATCH"
|
NEW_VERSION="$V_MAJOR.$V_MINOR.$NEW_PATCH"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -104,7 +134,11 @@ else
|
|||||||
NEW_MINOR=$((V_MINOR + 1))
|
NEW_MINOR=$((V_MINOR + 1))
|
||||||
NEW_VERSION="$V_MAJOR.$NEW_MINOR.0"
|
NEW_VERSION="$V_MAJOR.$NEW_MINOR.0"
|
||||||
echo ""
|
echo ""
|
||||||
echo "==> abidiff failed → minor bump"
|
if [ "$FORCE_MINOR" -eq 1 ]; then
|
||||||
|
echo "==> abidiff failed → minor bump (--minor also specified)"
|
||||||
|
else
|
||||||
|
echo "==> abidiff failed → minor bump"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
VERSION_HEX=$(printf "0x%02x%02x%02x" "${NEW_VERSION%%.*}" "$(echo "$NEW_VERSION" | cut -d. -f2)" "${NEW_VERSION##*.}")
|
VERSION_HEX=$(printf "0x%02x%02x%02x" "${NEW_VERSION%%.*}" "$(echo "$NEW_VERSION" | cut -d. -f2)" "${NEW_VERSION##*.}")
|
||||||
|
|||||||
@@ -116,6 +116,23 @@ if(HTTPLIB_IS_USING_OPENSSL)
|
|||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
COMMAND_ERROR_IS_FATAL ANY
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
)
|
)
|
||||||
|
# Certificates for IP-host hostname verification regression tests.
|
||||||
|
# cert_ip_cn.pem: CN is an IPv4 literal with NO subjectAltName. An IP host
|
||||||
|
# must NOT be authenticated via the CN, so verifying it
|
||||||
|
# against this cert must fail.
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${OPENSSL_COMMAND} req -x509 -key key.pem -sha256 -days 3650 -nodes -subj /CN=127.0.0.1 -out cert_ip_cn.pem
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
|
)
|
||||||
|
# cert_ipv6.pem: CN is an IPv6 literal plus an IPv6 iPAddress SAN for a
|
||||||
|
# different address. The SAN address must match; the CN
|
||||||
|
# address must be ignored.
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${OPENSSL_COMMAND} req -x509 -key key.pem -sha256 -days 3650 -nodes -subj /CN=::1 -addext subjectAltName=IP:2001:db8::1 -out cert_ipv6.pem
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory(fuzzing)
|
add_subdirectory(fuzzing)
|
||||||
|
|||||||
+65
-17
@@ -18,8 +18,10 @@ ifneq ($(OS), Windows_NT)
|
|||||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -lssl -lcrypto
|
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -lssl -lcrypto
|
||||||
MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -lmbedtls -lmbedx509 -lmbedcrypto
|
MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -lmbedtls -lmbedx509 -lmbedcrypto
|
||||||
WOLFSSL_SUPPORT = -DCPPHTTPLIB_WOLFSSL_SUPPORT -lwolfssl
|
WOLFSSL_SUPPORT = -DCPPHTTPLIB_WOLFSSL_SUPPORT -lwolfssl
|
||||||
# Disable ASLR for ASAN compatibility on WSL2 (high-entropy ASLR conflicts with ASAN shadow memory)
|
ifeq ($(UNAME_S), Linux)
|
||||||
SETARCH = setarch $(shell uname -m) -R
|
# Disable ASLR for ASAN compatibility on WSL2 (high-entropy ASLR conflicts with ASAN shadow memory)
|
||||||
|
SETARCH = setarch $(shell uname -m) -R
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -67,22 +69,29 @@ SHARDS ?= 4
|
|||||||
|
|
||||||
define run_parallel
|
define run_parallel
|
||||||
@echo "Running $(1) with $(SHARDS) shards in parallel..."
|
@echo "Running $(1) with $(SHARDS) shards in parallel..."
|
||||||
@fail=0; \
|
@fail=0; pids=""; \
|
||||||
for i in $$(seq 0 $$(($(SHARDS) - 1))); do \
|
for i in $$(seq 0 $$(($(SHARDS) - 1))); do \
|
||||||
GTEST_TOTAL_SHARDS=$(SHARDS) GTEST_SHARD_INDEX=$$i \
|
GTEST_TOTAL_SHARDS=$(SHARDS) GTEST_SHARD_INDEX=$$i \
|
||||||
LSAN_OPTIONS=suppressions=lsan_suppressions.txt \
|
LSAN_OPTIONS=suppressions=lsan_suppressions.txt \
|
||||||
$(SETARCH) ./$(1) --gtest_color=yes > $(1)_shard_$$i.log 2>&1 & \
|
$(SETARCH) ./$(1) --gtest_color=yes > $(1)_shard_$$i.log 2>&1 & \
|
||||||
|
pids="$$pids $$!"; \
|
||||||
done; \
|
done; \
|
||||||
wait; \
|
exits=""; \
|
||||||
for i in $$(seq 0 $$(($(SHARDS) - 1))); do \
|
for pid in $$pids; do \
|
||||||
if ! grep -q "\[ PASSED \]" $(1)_shard_$$i.log; then \
|
wait $$pid; exits="$$exits $$?"; \
|
||||||
echo "=== Shard $$i FAILED ==="; \
|
done; \
|
||||||
cat $(1)_shard_$$i.log; \
|
i=0; \
|
||||||
fail=1; \
|
for ec in $$exits; do \
|
||||||
else \
|
log=$(1)_shard_$$i.log; \
|
||||||
passed=$$(grep "\[ PASSED \]" $(1)_shard_$$i.log); \
|
if grep -q "\[ PASSED \]" $$log && ! grep -q "\[ FAILED \]" $$log && [ $$ec -eq 0 ]; then \
|
||||||
|
passed=$$(grep "\[ PASSED \]" $$log); \
|
||||||
echo "Shard $$i: $$passed"; \
|
echo "Shard $$i: $$passed"; \
|
||||||
|
else \
|
||||||
|
echo "=== Shard $$i FAILED (exit=$$ec) ==="; \
|
||||||
|
cat $$log; \
|
||||||
|
fail=1; \
|
||||||
fi; \
|
fi; \
|
||||||
|
i=$$((i+1)); \
|
||||||
done; \
|
done; \
|
||||||
if [ $$fail -ne 0 ]; then exit 1; fi; \
|
if [ $$fail -ne 0 ]; then exit 1; fi; \
|
||||||
echo "All shards passed."
|
echo "All shards passed."
|
||||||
@@ -193,8 +202,25 @@ test_split_no_tls : test.cc ../httplib.h httplib.cc Makefile
|
|||||||
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS_NO_TLS)
|
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS_NO_TLS)
|
||||||
|
|
||||||
# ThreadPool unit tests (no TLS, no compression needed)
|
# ThreadPool unit tests (no TLS, no compression needed)
|
||||||
|
#
|
||||||
|
# The constructor-exception-safety reproducer test interposes pthread_create
|
||||||
|
# at link time. The link flags below enable that interposition. ASAN is also
|
||||||
|
# stripped from this target because libasan installs its own pthread_create
|
||||||
|
# interceptor; layering our override on top corrupts ASAN's thread bookkeeping
|
||||||
|
# and trips "Joining already joined thread" on Linux. ThreadPool memory
|
||||||
|
# behavior is still covered by the ASAN-instrumented `test` binary.
|
||||||
|
ifneq ($(OS), Windows_NT)
|
||||||
|
ifeq ($(shell uname -s), Darwin)
|
||||||
|
THREAD_POOL_INTERPOSE_LDFLAGS := -Wl,-flat_namespace
|
||||||
|
else
|
||||||
|
THREAD_POOL_INTERPOSE_LDFLAGS := -Wl,--export-dynamic
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
THREAD_POOL_CXXFLAGS := $(filter-out -fsanitize=address,$(CXXFLAGS))
|
||||||
|
|
||||||
test_thread_pool : test_thread_pool.cc ../httplib.h Makefile
|
test_thread_pool : test_thread_pool.cc ../httplib.h Makefile
|
||||||
$(CXX) -o $@ -I.. $(CXXFLAGS) test_thread_pool.cc gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include -lpthread
|
$(CXX) -o $@ -I.. $(THREAD_POOL_CXXFLAGS) test_thread_pool.cc gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include -lpthread $(THREAD_POOL_INTERPOSE_LDFLAGS)
|
||||||
|
|
||||||
check_abi:
|
check_abi:
|
||||||
@./check-shared-library-abi-compatibility.sh
|
@./check-shared-library-abi-compatibility.sh
|
||||||
@@ -244,16 +270,38 @@ test_proxy_mbedtls : test_proxy.cc ../httplib.h Makefile cert.pem
|
|||||||
test_proxy_wolfssl : test_proxy.cc ../httplib.h Makefile cert.pem
|
test_proxy_wolfssl : test_proxy.cc ../httplib.h Makefile cert.pem
|
||||||
$(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS_WOLFSSL)
|
$(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS_WOLFSSL)
|
||||||
|
|
||||||
# Runs server_fuzzer.cc based on value of $(LIB_FUZZING_ENGINE).
|
# Runs all fuzz harnesses based on the value of $(LIB_FUZZING_ENGINE).
|
||||||
# Usage: make fuzz_test LIB_FUZZING_ENGINE=/path/to/libFuzzer
|
# By default LIB_FUZZING_ENGINE is standalone_fuzz_target_runner.o, so each
|
||||||
fuzz_test: server_fuzzer
|
# fuzzer is replayed over its regression corpus.
|
||||||
./server_fuzzer fuzzing/corpus/*
|
# Override for actual fuzzing:
|
||||||
|
# make fuzz_test LIB_FUZZING_ENGINE=/path/to/libFuzzer
|
||||||
|
fuzz_test: server_fuzzer client_fuzzer header_parser_fuzzer url_parser_fuzzer
|
||||||
|
@m=""; for f in fuzzing/corpus/[0-9]* fuzzing/corpus/issue1264 fuzzing/corpus/clusterfuzz-testcase-minimized-server_fuzzer-*; do if [ -f "$$f" ]; then m="$$m $$f"; fi; done; \
|
||||||
|
if [ -n "$$m" ]; then echo "./server_fuzzer$$m"; ./server_fuzzer $$m; else echo "(no server_fuzzer corpus)"; fi
|
||||||
|
@m=""; for f in fuzzing/corpus/clusterfuzz-testcase-minimized-client_fuzzer-*; do if [ -f "$$f" ]; then m="$$m $$f"; fi; done; \
|
||||||
|
if [ -n "$$m" ]; then echo "./client_fuzzer$$m"; ./client_fuzzer $$m; else echo "(no client_fuzzer corpus)"; fi
|
||||||
|
@m=""; for f in fuzzing/corpus/clusterfuzz-testcase-minimized-header_parser_fuzzer-*; do if [ -f "$$f" ]; then m="$$m $$f"; fi; done; \
|
||||||
|
if [ -n "$$m" ]; then echo "./header_parser_fuzzer$$m"; ./header_parser_fuzzer $$m; else echo "(no header_parser_fuzzer corpus)"; fi
|
||||||
|
@m=""; for f in fuzzing/corpus/clusterfuzz-testcase-minimized-url_parser_fuzzer-*; do if [ -f "$$f" ]; then m="$$m $$f"; fi; done; \
|
||||||
|
if [ -n "$$m" ]; then echo "./url_parser_fuzzer$$m"; ./url_parser_fuzzer $$m; else echo "(no url_parser_fuzzer corpus)"; fi
|
||||||
|
|
||||||
# Fuzz target, so that you can choose which $(LIB_FUZZING_ENGINE) to use.
|
# Fuzz target, so that you can choose which $(LIB_FUZZING_ENGINE) to use.
|
||||||
server_fuzzer : fuzzing/server_fuzzer.cc ../httplib.h standalone_fuzz_target_runner.o
|
server_fuzzer : fuzzing/server_fuzzer.cc ../httplib.h standalone_fuzz_target_runner.o
|
||||||
$(CXX) -o $@ -I.. $(CXXFLAGS) $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) $(ZSTD_SUPPORT) $(LIBS)
|
$(CXX) -o $@ -I.. $(CXXFLAGS) $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) $(ZSTD_SUPPORT) $(LIBS)
|
||||||
@file $@
|
@file $@
|
||||||
|
|
||||||
|
client_fuzzer : fuzzing/client_fuzzer.cc ../httplib.h standalone_fuzz_target_runner.o
|
||||||
|
$(CXX) -o $@ -I.. $(CXXFLAGS) $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) $(ZSTD_SUPPORT) $(LIBS)
|
||||||
|
@file $@
|
||||||
|
|
||||||
|
header_parser_fuzzer : fuzzing/header_parser_fuzzer.cc ../httplib.h standalone_fuzz_target_runner.o
|
||||||
|
$(CXX) -o $@ -I.. $(CXXFLAGS) $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) $(ZSTD_SUPPORT) $(LIBS)
|
||||||
|
@file $@
|
||||||
|
|
||||||
|
url_parser_fuzzer : fuzzing/url_parser_fuzzer.cc ../httplib.h standalone_fuzz_target_runner.o
|
||||||
|
$(CXX) -o $@ -I.. $(CXXFLAGS) $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) $(ZSTD_SUPPORT) $(LIBS)
|
||||||
|
@file $@
|
||||||
|
|
||||||
# Standalone fuzz runner, which just reads inputs from fuzzing/corpus/ dir and
|
# Standalone fuzz runner, which just reads inputs from fuzzing/corpus/ dir and
|
||||||
# feeds it to server_fuzzer.
|
# feeds it to server_fuzzer.
|
||||||
standalone_fuzz_target_runner.o : fuzzing/standalone_fuzz_target_runner.cpp
|
standalone_fuzz_target_runner.o : fuzzing/standalone_fuzz_target_runner.cpp
|
||||||
@@ -266,5 +314,5 @@ cert.pem:
|
|||||||
./gen-certs.sh
|
./gen-certs.sh
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf test test_split test_mbedtls test_split_mbedtls test_wolfssl test_split_wolfssl test_no_tls, test_split_no_tls test_proxy test_proxy_mbedtls test_proxy_wolfssl test_benchmark server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc _build* *.dSYM *_shard_*.log cpp-httplib
|
rm -rf test test_split test_mbedtls test_split_mbedtls test_wolfssl test_split_wolfssl test_no_tls, test_split_no_tls test_proxy test_proxy_mbedtls test_proxy_wolfssl test_benchmark server_fuzzer client_fuzzer header_parser_fuzzer url_parser_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc _build* *.dSYM *_shard_*.log cpp-httplib
|
||||||
|
|
||||||
|
|||||||
Executable
+87
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Delayed UDP responder used as a loopback test fixture.
|
||||||
|
|
||||||
|
This is a self-contained test fixture for the GetAddrInfoAsyncCancelTest
|
||||||
|
cases (reproducer for cpp-httplib issue #2431). It is NOT a general-purpose
|
||||||
|
nameserver and is only intended to run on 127.0.0.1 inside the test job's
|
||||||
|
own runner / container.
|
||||||
|
|
||||||
|
What it does
|
||||||
|
------------
|
||||||
|
Binds a UDP socket on 127.0.0.1:<port>, accepts well-formed DNS queries
|
||||||
|
from the test process, waits <delay_seconds>, then sends back a minimal
|
||||||
|
NXDOMAIN reply. The deliberate delay is what makes the bug reproducible:
|
||||||
|
|
||||||
|
* The test calls getaddrinfo_with_timeout() with timeout_sec=1.
|
||||||
|
* gai_suspend() returns EAI_AGAIN after 1s; the function returns and
|
||||||
|
its stack frame is destroyed.
|
||||||
|
* The fixture replies after <delay_seconds> (= 3s by default), so the
|
||||||
|
glibc resolver worker thread receives the response *after* the
|
||||||
|
caller's frame is gone and writes back into freed stack memory.
|
||||||
|
* AddressSanitizer (with detect_stack_use_after_return=1) catches the
|
||||||
|
write and aborts with a stack-use-after-return diagnostic.
|
||||||
|
|
||||||
|
Without this fixture the bug is hard to surface: dropping UDP/53 makes
|
||||||
|
the resolver hang forever, so the worker never receives anything and
|
||||||
|
never reaches the buggy write-back path.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
python3 test/dns_test_fixture.py <port> [<delay_seconds>]
|
||||||
|
|
||||||
|
Only standard library; no third-party dependencies.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def serve(port: int, delay_sec: float) -> None:
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
sock.bind(("127.0.0.1", port))
|
||||||
|
print(
|
||||||
|
f"[dns_test_fixture] listening on 127.0.0.1:{port}, "
|
||||||
|
f"reply delay={delay_sec}s",
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
data, addr = sock.recvfrom(2048)
|
||||||
|
except OSError:
|
||||||
|
return
|
||||||
|
threading.Thread(
|
||||||
|
target=_reply_after_delay,
|
||||||
|
args=(sock, data, addr, delay_sec),
|
||||||
|
daemon=True,
|
||||||
|
).start()
|
||||||
|
|
||||||
|
|
||||||
|
def _reply_after_delay(sock, query: bytes, addr, delay_sec: float) -> None:
|
||||||
|
time.sleep(delay_sec)
|
||||||
|
if len(query) < 12:
|
||||||
|
return
|
||||||
|
# Header: copy transaction id, set QR=1 RA=1 RCODE=3 (NXDOMAIN),
|
||||||
|
# preserve the requester's RD bit, then echo the question section so
|
||||||
|
# glibc's resolver accepts the reply as matching its outstanding query.
|
||||||
|
txid = query[:2]
|
||||||
|
rd_bit = query[2] & 0x01
|
||||||
|
flags = struct.pack(">H", 0x8003 | (rd_bit << 8))
|
||||||
|
counts = struct.pack(">HHHH", 1, 0, 0, 0)
|
||||||
|
question = query[12:]
|
||||||
|
reply = txid + flags + counts + question
|
||||||
|
try:
|
||||||
|
sock.sendto(reply, addr)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print(__doc__, file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
port_arg = int(sys.argv[1])
|
||||||
|
delay_arg = float(sys.argv[2]) if len(sys.argv) > 2 else 3.0
|
||||||
|
serve(port_arg, delay_arg)
|
||||||
@@ -13,7 +13,7 @@ ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
|||||||
BROTLI_DIR = /usr/local/opt/brotli
|
BROTLI_DIR = /usr/local/opt/brotli
|
||||||
# BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
|
# BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
|
||||||
|
|
||||||
FUZZERS = server_fuzzer url_parser_fuzzer header_parser_fuzzer
|
FUZZERS = server_fuzzer url_parser_fuzzer header_parser_fuzzer client_fuzzer multipart_parser_fuzzer
|
||||||
|
|
||||||
# Runs all the tests and also fuzz tests against seed corpus.
|
# Runs all the tests and also fuzz tests against seed corpus.
|
||||||
all : $(FUZZERS)
|
all : $(FUZZERS)
|
||||||
@@ -25,11 +25,18 @@ server_fuzzer : server_fuzzer.cc ../../httplib.h
|
|||||||
$(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread -lanl
|
$(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread -lanl
|
||||||
zip -q -r server_fuzzer_seed_corpus.zip corpus
|
zip -q -r server_fuzzer_seed_corpus.zip corpus
|
||||||
|
|
||||||
|
client_fuzzer : client_fuzzer.cc ../../httplib.h
|
||||||
|
$(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread -lanl
|
||||||
|
zip -q -r client_fuzzer_seed_corpus.zip corpus
|
||||||
|
|
||||||
header_parser_fuzzer : header_parser_fuzzer.cc ../../httplib.h
|
header_parser_fuzzer : header_parser_fuzzer.cc ../../httplib.h
|
||||||
$(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread -lanl
|
$(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread -lanl
|
||||||
|
|
||||||
url_parser_fuzzer : url_parser_fuzzer.cc ../../httplib.h
|
url_parser_fuzzer : url_parser_fuzzer.cc ../../httplib.h
|
||||||
$(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread -lanl
|
$(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread -lanl
|
||||||
|
|
||||||
|
multipart_parser_fuzzer : multipart_parser_fuzzer.cc ../../httplib.h
|
||||||
|
$(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread -lanl
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f server_fuzzer pem *.0 *.o *.1 *.srl *.zip
|
rm -f server_fuzzer pem *.0 *.o *.1 *.srl *.zip
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <httplib.h>
|
||||||
|
|
||||||
|
class FuzzedStream : public httplib::Stream {
|
||||||
|
public:
|
||||||
|
FuzzedStream(const uint8_t *data, size_t size)
|
||||||
|
: data_(data), size_(size), read_pos_(0) {}
|
||||||
|
|
||||||
|
ssize_t read(char *ptr, size_t size) override {
|
||||||
|
if (size + read_pos_ > size_) { size = size_ - read_pos_; }
|
||||||
|
memcpy(ptr, data_ + read_pos_, size);
|
||||||
|
read_pos_ += size;
|
||||||
|
return static_cast<ssize_t>(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t write(const char *ptr, size_t size) override {
|
||||||
|
request_.append(ptr, size);
|
||||||
|
return static_cast<ssize_t>(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t write(const char *ptr) { return write(ptr, strlen(ptr)); }
|
||||||
|
|
||||||
|
ssize_t write(const std::string &s) { return write(s.data(), s.size()); }
|
||||||
|
|
||||||
|
bool is_readable() const override { return true; }
|
||||||
|
|
||||||
|
bool wait_readable() const override { return true; }
|
||||||
|
|
||||||
|
bool wait_writable() const override { return true; }
|
||||||
|
|
||||||
|
void get_remote_ip_and_port(std::string &ip, int &port) const override {
|
||||||
|
ip = "127.0.0.1";
|
||||||
|
port = 8080;
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_local_ip_and_port(std::string &ip, int &port) const override {
|
||||||
|
ip = "127.0.0.1";
|
||||||
|
port = 8080;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket_t socket() const override { return 0; }
|
||||||
|
|
||||||
|
time_t duration() const override { return 0; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
const uint8_t *data_;
|
||||||
|
size_t size_;
|
||||||
|
size_t read_pos_;
|
||||||
|
std::string request_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FuzzableClient : public httplib::ClientImpl {
|
||||||
|
public:
|
||||||
|
FuzzableClient() : httplib::ClientImpl("localhost", 8080) {}
|
||||||
|
|
||||||
|
void ProcessFuzzedResponse(FuzzedStream &stream, const std::string &method) {
|
||||||
|
httplib::Request req;
|
||||||
|
req.method = method;
|
||||||
|
req.path = "/";
|
||||||
|
httplib::Response res;
|
||||||
|
bool close_connection = false;
|
||||||
|
httplib::Error error = httplib::Error::Success;
|
||||||
|
|
||||||
|
process_request(stream, req, res, close_connection, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||||
|
if (size < 1) return 0;
|
||||||
|
|
||||||
|
FuzzedStream stream{data + 1, size - 1};
|
||||||
|
FuzzableClient client;
|
||||||
|
|
||||||
|
// Use the first byte to select method
|
||||||
|
std::string method;
|
||||||
|
switch (data[0] % 6) {
|
||||||
|
case 0: method = "GET"; break;
|
||||||
|
case 1: method = "POST"; break;
|
||||||
|
case 2: method = "PUT"; break;
|
||||||
|
case 3: method = "PATCH"; break;
|
||||||
|
case 4: method = "DELETE"; break;
|
||||||
|
case 5: method = "OPTIONS"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.ProcessFuzzedResponse(stream, method);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
HTTP/1.1 777
|
||||||
|
Content-Length:20000000000
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
HTTP/1.1 777
|
||||||
|
Content-Length:446744071854775
|
||||||
|
|
||||||
BIN
Binary file not shown.
@@ -0,0 +1,37 @@
|
|||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <httplib.h>
|
||||||
|
|
||||||
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||||
|
if (size < 2 || size > 65536) return 0;
|
||||||
|
|
||||||
|
// First byte selects the boundary length, the rest is the boundary then body
|
||||||
|
size_t boundary_len = (static_cast<size_t>(data[0]) % 16) + 1;
|
||||||
|
if (boundary_len + 1 >= size) boundary_len = 0;
|
||||||
|
|
||||||
|
std::string boundary =
|
||||||
|
boundary_len > 0
|
||||||
|
? std::string(reinterpret_cast<const char *>(data + 1), boundary_len)
|
||||||
|
: "----fuzzboundary";
|
||||||
|
|
||||||
|
const uint8_t *body = data + 1 + boundary_len;
|
||||||
|
size_t body_size = size - 1 - boundary_len;
|
||||||
|
|
||||||
|
// FormDataParser::parse, fed in chunks to exercise the streaming paths
|
||||||
|
httplib::detail::FormDataParser parser;
|
||||||
|
parser.set_boundary(std::move(boundary));
|
||||||
|
|
||||||
|
auto header_cb = [](const httplib::FormData &) -> bool { return true; };
|
||||||
|
auto content_cb = [](const char *, size_t) -> bool { return true; };
|
||||||
|
|
||||||
|
size_t chunk = (static_cast<size_t>(data[1]) % 64) + 1;
|
||||||
|
for (size_t off = 0; off < body_size; off += chunk) {
|
||||||
|
size_t n = (off + chunk > body_size) ? body_size - off : chunk;
|
||||||
|
if (!parser.parse(reinterpret_cast<const char *>(body + off), n, header_cb,
|
||||||
|
content_cb))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user