mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-08-17 23:55:01 +00:00
Compare commits
118 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f00e476f1b | ||
|
|
8e702d3837 | ||
|
|
19333f80d4 | ||
|
|
86d0210391 | ||
|
|
6018c7feb3 | ||
|
|
8d5085df1b | ||
|
|
8f0ff32056 | ||
|
|
2dd44d0f52 | ||
|
|
86abc9a0ea | ||
|
|
bd02a50cbb | ||
|
|
1c2607cbb8 | ||
|
|
98be5fd3a6 | ||
|
|
d2ef193b9c | ||
|
|
a1aa2ad9cd | ||
|
|
b2b1d56d6d | ||
|
|
2b8658fa99 | ||
|
|
095a5c1caf | ||
|
|
6e8a7dcd3f | ||
|
|
148d61a6a3 | ||
|
|
c48ed1ed9a | ||
|
|
8d428361fb | ||
|
|
23f67f25c2 | ||
|
|
7963c382d6 | ||
|
|
486c81b275 | ||
|
|
be28cf9435 | ||
|
|
d860c842ea | ||
|
|
8e08c22783 | ||
|
|
f51df1473b | ||
|
|
15a23abd7e | ||
|
|
2e37c51921 | ||
|
|
571717adb8 | ||
|
|
5539a66c63 | ||
|
|
6b723fbf9d | ||
|
|
1562f0ec4f | ||
|
|
a691e531c3 | ||
|
|
ae8356d86e | ||
|
|
49b921b52d | ||
|
|
447b9c4a29 | ||
|
|
f406808497 | ||
|
|
2c28d2fa3e | ||
|
|
f24e79aab9 | ||
|
|
60f285a301 | ||
|
|
29ceccecd6 | ||
|
|
85ec3963bc | ||
|
|
f4fce42e77 | ||
|
|
23fef15e07 | ||
|
|
34b7c846d4 | ||
|
|
76b54e7de4 | ||
|
|
5b9d1495ff | ||
|
|
d66d9a9599 | ||
|
|
2fa0417754 | ||
|
|
82b1492c3d | ||
|
|
f7c8455a62 | ||
|
|
cc1be5ebb5 | ||
|
|
cda2bb5342 | ||
|
|
613a41b49d | ||
|
|
695961f8ae | ||
|
|
c64bf21a5e | ||
|
|
8bbfc90380 | ||
|
|
bf8baf0739 | ||
|
|
fd0c18b1b5 | ||
|
|
bbd56a7e2c | ||
|
|
54b3c1d072 | ||
|
|
6c4cbd49a2 | ||
|
|
f3e9a4d887 | ||
|
|
877a52f6b3 | ||
|
|
ef2beaea34 | ||
|
|
3adc525cec | ||
|
|
a415362dff | ||
|
|
982235c0a9 | ||
|
|
255c075b82 | ||
|
|
75938f08c7 | ||
|
|
0c1cc8c986 | ||
|
|
62d899feac | ||
|
|
2f986fd5e5 | ||
|
|
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 |
62
.github/workflows/benchmark_ab.yaml
vendored
Normal file
62
.github/workflows/benchmark_ab.yaml
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
name: benchmark-ab
|
||||||
|
|
||||||
|
# Manual A/B throughput comparison between two refs.
|
||||||
|
#
|
||||||
|
# This is a measurement, not a test: it never fails the build on a slow result.
|
||||||
|
# Absolute req/s from a shared runner is meaningless on its own, so both refs
|
||||||
|
# are built and measured alternately in the same job and only the ratio of the
|
||||||
|
# medians is reported, with a permutation test to say whether the difference
|
||||||
|
# stands out from the run-to-run noise.
|
||||||
|
#
|
||||||
|
# Non-SSL and Linux only for now.
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
base:
|
||||||
|
description: "Baseline ref"
|
||||||
|
required: false
|
||||||
|
default: "origin/master"
|
||||||
|
head:
|
||||||
|
description: "Ref to compare (defaults to the ref this run was started on)"
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
rounds:
|
||||||
|
description: "Measurement rounds per ref (9+ recommended; below 4 the test can never reach significance)"
|
||||||
|
required: false
|
||||||
|
default: "9"
|
||||||
|
duration:
|
||||||
|
description: "Load duration per measurement"
|
||||||
|
required: false
|
||||||
|
default: "5s"
|
||||||
|
connections:
|
||||||
|
description: "Concurrent connections"
|
||||||
|
required: false
|
||||||
|
default: "10"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ubuntu:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: install bombardier
|
||||||
|
run: go install github.com/codesenberg/bombardier@latest
|
||||||
|
|
||||||
|
- name: run A/B benchmark
|
||||||
|
run: |
|
||||||
|
export PATH="$(go env GOPATH)/bin:$PATH"
|
||||||
|
HEAD_REF="${{ inputs.head }}"
|
||||||
|
if [ -z "$HEAD_REF" ]; then HEAD_REF="${{ github.sha }}"; fi
|
||||||
|
./benchmark/ab.sh \
|
||||||
|
--base "${{ inputs.base }}" \
|
||||||
|
--head "$HEAD_REF" \
|
||||||
|
--rounds "${{ inputs.rounds }}" \
|
||||||
|
--duration "${{ inputs.duration }}" \
|
||||||
|
--connections "${{ inputs.connections }}"
|
||||||
106
.github/workflows/benchmark_run.yaml
vendored
Normal file
106
.github/workflows/benchmark_run.yaml
vendored
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
name: benchmark-run
|
||||||
|
|
||||||
|
# Runs the committed benchmark (`just bench`) and records the numbers.
|
||||||
|
#
|
||||||
|
# This is a measurement, not a test: nothing here fails the build. Unlike
|
||||||
|
# benchmark-ab, which compares two refs inside one job, this just reports the
|
||||||
|
# absolute throughput of the current ref alongside Crow for reference.
|
||||||
|
#
|
||||||
|
# Absolute req/s is only meaningful against other runs on the same runner type,
|
||||||
|
# so compare like with like when reading the history.
|
||||||
|
#
|
||||||
|
# Non-SSL only. Windows is excluded: benchmark/Makefile depends on `nc`, `&`
|
||||||
|
# and `kill`, so it would need a PowerShell rewrite first.
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
duration:
|
||||||
|
description: "Load duration per server"
|
||||||
|
required: false
|
||||||
|
default: "5s"
|
||||||
|
connections:
|
||||||
|
description: "Concurrent connections"
|
||||||
|
required: false
|
||||||
|
default: "10"
|
||||||
|
crow:
|
||||||
|
description: "Also benchmark Crow v1.3.1 for reference"
|
||||||
|
type: boolean
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bench:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macos-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# macos runners ship without Go.
|
||||||
|
- name: setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: stable
|
||||||
|
|
||||||
|
- name: install bombardier
|
||||||
|
run: go install github.com/codesenberg/bombardier@latest
|
||||||
|
|
||||||
|
# crow_all.h includes <asio.hpp>, which no runner has out of the box.
|
||||||
|
- name: install asio
|
||||||
|
if: ${{ inputs.crow }}
|
||||||
|
run: |
|
||||||
|
if [ "$RUNNER_OS" = "Linux" ]; then
|
||||||
|
sudo apt-get update && sudo apt-get install -y libasio-dev
|
||||||
|
else
|
||||||
|
brew install asio
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: run benchmark
|
||||||
|
run: |
|
||||||
|
# Without pipefail the `tee` below swallows a build failure and the
|
||||||
|
# job reports success having measured nothing.
|
||||||
|
set -o pipefail
|
||||||
|
export PATH="$(go env GOPATH)/bin:$PATH"
|
||||||
|
CROW_FLAGS="-std=c++17"
|
||||||
|
if [ "$RUNNER_OS" = "macOS" ]; then
|
||||||
|
CROW_FLAGS="$CROW_FLAGS -I$(brew --prefix asio)/include"
|
||||||
|
fi
|
||||||
|
if [ "${{ inputs.crow }}" = "true" ]; then TARGET=bench-all; else TARGET=bench; fi
|
||||||
|
make -C benchmark "$TARGET" \
|
||||||
|
CROW_CXXFLAGS="$CROW_FLAGS" \
|
||||||
|
BENCH="bombardier -c ${{ inputs.connections }} -d ${{ inputs.duration }} localhost:8080" \
|
||||||
|
2>&1 | tee /tmp/bench.txt
|
||||||
|
|
||||||
|
# pipefail only catches a failed build. Each Makefile recipe ends in
|
||||||
|
# `kill`, so a bombardier that never ran still leaves make happy — check
|
||||||
|
# that the measurements are actually there.
|
||||||
|
- name: check results were produced
|
||||||
|
run: |
|
||||||
|
expected=1
|
||||||
|
if [ "${{ inputs.crow }}" = "true" ]; then expected=2; fi
|
||||||
|
got=$(grep -c "Reqs/sec" /tmp/bench.txt || true)
|
||||||
|
if [ "$got" -lt "$expected" ]; then
|
||||||
|
echo "::error::expected $expected benchmark result(s), found $got"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: record results
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
{
|
||||||
|
echo "## Benchmark (${{ matrix.os }})"
|
||||||
|
echo ""
|
||||||
|
echo "- ref: \`${{ github.ref_name }}\` (${{ github.sha }})"
|
||||||
|
echo "- connections=${{ inputs.connections }} duration=${{ inputs.duration }}"
|
||||||
|
echo ""
|
||||||
|
echo '```'
|
||||||
|
cat /tmp/bench.txt
|
||||||
|
echo '```'
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
12
.github/workflows/cifuzz.yaml
vendored
12
.github/workflows/cifuzz.yaml
vendored
@@ -1,6 +1,16 @@
|
|||||||
name: CIFuzz
|
name: CIFuzz
|
||||||
|
|
||||||
on: [pull_request]
|
# The fuzzers only build httplib.h and the targets under test/fuzzing, so a
|
||||||
|
# pull request that touches neither has nothing for CIFuzz to exercise. Fuzzing
|
||||||
|
# is by far the longest job in CI (10 minutes of fuzzing on top of building the
|
||||||
|
# OSS-Fuzz image), and skipping it for documentation-only changes keeps the
|
||||||
|
# full 600 seconds for the pull requests that do reach the parsers.
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'httplib.h'
|
||||||
|
- 'test/fuzzing/**'
|
||||||
|
- '.github/workflows/cifuzz.yaml'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||||
|
|||||||
3
.github/workflows/docs.yml
vendored
3
.github/workflows/docs.yml
vendored
@@ -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
|
||||||
|
|||||||
63
.github/workflows/test.yaml
vendored
63
.github/workflows/test.yaml
vendored
@@ -104,10 +104,7 @@ 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 mbedTLS shards with reduced parallelism — under ASAN+mbedTLS the
|
run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
|
||||||
# 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
|
||||||
@@ -120,6 +117,30 @@ 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
|
||||||
|
|
||||||
|
# Ubuntu 26.04's apt ships Mbed TLS 3.6, giving 3.x coverage that
|
||||||
|
# ubuntu-latest (24.04 = 2.28) and macOS (Homebrew = 4.x) no longer provide.
|
||||||
|
# Uses the 26.04 public-preview image; fold into the main ubuntu matrix once
|
||||||
|
# ubuntu-latest moves to 26.04.
|
||||||
|
ubuntu-2604-mbedtls:
|
||||||
|
runs-on: ubuntu-26.04
|
||||||
|
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: ubuntu-26.04 (mbedtls 3.x)
|
||||||
|
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: install Mbed TLS
|
||||||
|
run: sudo apt-get install -y libmbedtls-dev
|
||||||
|
- name: build and run tests (Mbed TLS)
|
||||||
|
run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
|
||||||
|
|
||||||
# BoringSSL is Google's fork of OpenSSL. It has no API stability guarantee
|
# 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
|
# 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
|
# treats it as an OpenSSL backend variant via the OPENSSL_IS_BORINGSSL
|
||||||
@@ -372,7 +393,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: install Mbed TLS
|
- name: install Mbed TLS
|
||||||
if: matrix.tls_backend == 'mbedtls'
|
if: matrix.tls_backend == 'mbedtls'
|
||||||
run: brew install mbedtls@3
|
run: brew install mbedtls
|
||||||
- name: install wolfSSL
|
- name: install wolfSSL
|
||||||
if: matrix.tls_backend == 'wolfssl'
|
if: matrix.tls_backend == 'wolfssl'
|
||||||
run: brew install wolfssl
|
run: brew install wolfssl
|
||||||
@@ -383,10 +404,7 @@ 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'
|
||||||
# macOS runners under ASAN+mbedTLS still flake at SHARDS=2 (rapid
|
run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
|
||||||
# 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
|
||||||
@@ -449,6 +467,9 @@ jobs:
|
|||||||
|
|
||||||
windows:
|
windows:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
if: >
|
if: >
|
||||||
(github.event_name == 'push') ||
|
(github.event_name == 'push') ||
|
||||||
(github.event_name == 'pull_request' &&
|
(github.event_name == 'pull_request' &&
|
||||||
@@ -545,6 +566,30 @@ jobs:
|
|||||||
}
|
}
|
||||||
if ($failed) { exit 1 }
|
if ($failed) { exit 1 }
|
||||||
Write-Host "All shards passed."
|
Write-Host "All shards passed."
|
||||||
|
- name: Report flaky failure on issue #2533
|
||||||
|
if: failure() && matrix.config.name == 'without SSL' && github.event_name == 'push'
|
||||||
|
continue-on-error: true
|
||||||
|
shell: pwsh
|
||||||
|
working-directory: build/test
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
$summary = ""
|
||||||
|
for ($i = 0; $i -lt 4; $i++) {
|
||||||
|
$log = "shard_${i}.log"
|
||||||
|
if (Test-Path $log) {
|
||||||
|
$failedLines = Select-String -Path $log -Pattern "\[ FAILED \]"
|
||||||
|
if ($failedLines) {
|
||||||
|
$summary += "**Shard ${i}:**`n" + (($failedLines | ForEach-Object { $_.Line }) -join "`n") + "`n`n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not $summary) {
|
||||||
|
$summary = "_Could not extract failed test name from shard logs; see the run for details._`n`n"
|
||||||
|
}
|
||||||
|
$runUrl = "$($env:GITHUB_SERVER_URL)/$($env:GITHUB_REPOSITORY)/actions/runs/$($env:GITHUB_RUN_ID)"
|
||||||
|
$body = "Reoccurred on push: $runUrl`n`nCommit: $($env:GITHUB_SHA)`n`n$summary"
|
||||||
|
gh issue comment 2533 --repo $env:GITHUB_REPOSITORY --body $body
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VCPKG_ROOT: "C:/vcpkg"
|
VCPKG_ROOT: "C:/vcpkg"
|
||||||
|
|||||||
@@ -135,11 +135,31 @@ bool is_open() const;
|
|||||||
explicit WebSocketClient(const std::string &scheme_host_port_path,
|
explicit WebSocketClient(const std::string &scheme_host_port_path,
|
||||||
const Headers &headers = {});
|
const Headers &headers = {});
|
||||||
|
|
||||||
|
// Constructor with a client certificate for mutual TLS (wss:// only,
|
||||||
|
// requires CPPHTTPLIB_OPENSSL_SUPPORT). The certificate is ignored for
|
||||||
|
// ws:// URLs.
|
||||||
|
struct PemMemory {
|
||||||
|
const char *cert_pem;
|
||||||
|
size_t cert_pem_len;
|
||||||
|
const char *key_pem;
|
||||||
|
size_t key_pem_len;
|
||||||
|
const char *private_key_password;
|
||||||
|
};
|
||||||
|
explicit WebSocketClient(const std::string &scheme_host_port_path,
|
||||||
|
const PemMemory &pem, const Headers &headers = {});
|
||||||
|
|
||||||
// Check if the URL was parsed successfully
|
// Check if the URL was parsed successfully
|
||||||
bool is_valid() const;
|
bool is_valid() const;
|
||||||
|
|
||||||
// Connect (performs HTTP upgrade handshake)
|
// Connect (performs HTTP upgrade handshake). The returned Result is truthy
|
||||||
bool connect();
|
// only when the handshake fully succeeded; on failure it describes what went
|
||||||
|
// wrong:
|
||||||
|
// res.error() httplib::Error identifying the failing layer
|
||||||
|
// res.status() HTTP status of the upgrade response (-1 if none)
|
||||||
|
// res.headers() headers of the upgrade response
|
||||||
|
// res.ssl_error() TLS error detail (wss://, SSL builds only)
|
||||||
|
// res.ssl_backend_error() backend-specific TLS error code (SSL builds only)
|
||||||
|
Result connect();
|
||||||
|
|
||||||
// Get the subprotocol selected by the server (empty if none)
|
// Get the subprotocol selected by the server (empty if none)
|
||||||
const std::string &subprotocol() const;
|
const std::string &subprotocol() const;
|
||||||
@@ -155,11 +175,20 @@ bool is_open() const;
|
|||||||
// Timeouts
|
// Timeouts
|
||||||
void set_read_timeout(time_t sec, time_t usec = 0);
|
void set_read_timeout(time_t sec, time_t usec = 0);
|
||||||
void set_write_timeout(time_t sec, time_t usec = 0);
|
void set_write_timeout(time_t sec, time_t usec = 0);
|
||||||
|
void set_connection_timeout(time_t sec, time_t usec = 0);
|
||||||
|
template <class Rep, class Period>
|
||||||
|
void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||||
|
template <class Rep, class Period>
|
||||||
|
void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||||
|
template <class Rep, class Period>
|
||||||
|
void set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||||
|
|
||||||
// SSL configuration (wss:// only, requires CPPHTTPLIB_OPENSSL_SUPPORT)
|
// SSL configuration (wss:// only, requires CPPHTTPLIB_OPENSSL_SUPPORT)
|
||||||
void set_ca_cert_path(const std::string &path);
|
void set_ca_cert_path(const std::string &ca_cert_file_path,
|
||||||
|
const std::string &ca_cert_dir_path = std::string());
|
||||||
void set_ca_cert_store(tls::ca_store_t store);
|
void set_ca_cert_store(tls::ca_store_t store);
|
||||||
void enable_server_certificate_verification(bool enabled);
|
void enable_server_certificate_verification(bool enabled);
|
||||||
|
void enable_server_hostname_verification(bool enabled);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
@@ -200,6 +229,26 @@ if (ws.connect()) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Inspecting Connection Failures
|
||||||
|
|
||||||
|
`connect()` returns a `Result` that tells you why a connection attempt failed.
|
||||||
|
`error()` distinguishes network problems (`Connection`, `ConnectionTimeout`),
|
||||||
|
TLS problems (`SSLConnection`, `SSLServerVerification`,
|
||||||
|
`SSLServerHostnameVerification`), and upgrade rejections
|
||||||
|
(`WebSocketHandshake`). When the server answered with something other than
|
||||||
|
`101 Switching Protocols`, `status()` and `headers()` carry that response:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto res = ws.connect();
|
||||||
|
if (!res) {
|
||||||
|
std::cerr << "connect failed: " << httplib::to_string(res.error()) << std::endl;
|
||||||
|
if (res.status() != -1) {
|
||||||
|
// The server responded but refused the upgrade (e.g. 401, 404)
|
||||||
|
std::cerr << "HTTP status: " << res.status() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Text and Binary Messages
|
### Text and Binary Messages
|
||||||
|
|
||||||
Check the `ReadResult` return value to distinguish between text and binary:
|
Check the `ReadResult` return value to distinguish between text and binary:
|
||||||
@@ -286,8 +335,14 @@ httplib::Headers headers = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws", headers);
|
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws", headers);
|
||||||
ws.set_read_timeout(30, 0); // 30 seconds
|
ws.set_connection_timeout(5, 0); // 5 seconds
|
||||||
ws.set_write_timeout(10, 0); // 10 seconds
|
ws.set_read_timeout(30, 0); // 30 seconds
|
||||||
|
ws.set_write_timeout(10, 0); // 10 seconds
|
||||||
|
|
||||||
|
// std::chrono is also supported
|
||||||
|
ws.set_connection_timeout(std::chrono::seconds(5));
|
||||||
|
ws.set_read_timeout(std::chrono::seconds(30));
|
||||||
|
ws.set_write_timeout(std::chrono::seconds(10));
|
||||||
|
|
||||||
if (ws.connect()) {
|
if (ws.connect()) {
|
||||||
std::string msg;
|
std::string msg;
|
||||||
@@ -341,6 +396,7 @@ if (ws.connect()) {
|
|||||||
httplib::ws::WebSocketClient ws("wss://example.com/ws");
|
httplib::ws::WebSocketClient ws("wss://example.com/ws");
|
||||||
ws.set_ca_cert_path("/path/to/ca-bundle.crt");
|
ws.set_ca_cert_path("/path/to/ca-bundle.crt");
|
||||||
ws.enable_server_certificate_verification(true);
|
ws.enable_server_certificate_verification(true);
|
||||||
|
ws.enable_server_hostname_verification(true); // default; false skips the identity check
|
||||||
|
|
||||||
if (ws.connect()) {
|
if (ws.connect()) {
|
||||||
ws.send("secure message");
|
ws.send("secure message");
|
||||||
|
|||||||
148
README.md
148
README.md
@@ -67,7 +67,7 @@ cpp-httplib supports multiple TLS backends through an abstraction layer:
|
|||||||
| Backend | Define | Libraries | Notes |
|
| Backend | Define | Libraries | Notes |
|
||||||
| :------ | :----- | :-------- | :---- |
|
| :------ | :----- | :-------- | :---- |
|
||||||
| OpenSSL | `CPPHTTPLIB_OPENSSL_SUPPORT` | `libssl`, `libcrypto` | [3.0 or later](https://www.openssl.org/policies/releasestrat.html) required |
|
| OpenSSL | `CPPHTTPLIB_OPENSSL_SUPPORT` | `libssl`, `libcrypto` | [3.0 or later](https://www.openssl.org/policies/releasestrat.html) required |
|
||||||
| Mbed TLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | `libmbedtls`, `libmbedx509`, `libmbedcrypto` | 2.x and 3.x supported (auto-detected) |
|
| Mbed TLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | `libmbedtls`, `libmbedx509`, `libmbedcrypto` | 2.x, 3.x, and 4.x supported (auto-detected); 4.x renames `libmbedcrypto` to `libtfpsacrypto` |
|
||||||
| wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | `libwolfssl` | 5.x supported; must build with `--enable-opensslall` |
|
| wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | `libwolfssl` | 5.x supported; must build with `--enable-opensslall` |
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
@@ -168,6 +168,41 @@ cli.set_server_certificate_verifier(
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Mutual TLS (mTLS)
|
||||||
|
|
||||||
|
Regular TLS only verifies the server certificate. With mTLS, the client also presents a certificate that the server verifies.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// Server: pass a CA to verify client certificates against
|
||||||
|
httplib::SSLServer svr("./cert.pem", "./key.pem", "./client-ca-cert.pem");
|
||||||
|
|
||||||
|
// Client: present a certificate
|
||||||
|
httplib::SSLClient cli("api.example.com", 443,
|
||||||
|
"./client-cert.pem", "./client-key.pem");
|
||||||
|
```
|
||||||
|
|
||||||
|
Both `SSLServer` and `SSLClient` also accept an in-memory `PemMemory` struct instead of file paths — handy when certs come from an environment variable or a secrets manager:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
httplib::SSLServer::PemMemory server_pem{};
|
||||||
|
server_pem.cert_pem = server_cert.data();
|
||||||
|
server_pem.cert_pem_len = server_cert.size();
|
||||||
|
server_pem.key_pem = server_key.data();
|
||||||
|
server_pem.key_pem_len = server_key.size();
|
||||||
|
server_pem.client_ca_pem = client_ca.data();
|
||||||
|
server_pem.client_ca_pem_len = client_ca.size();
|
||||||
|
httplib::SSLServer svr(server_pem);
|
||||||
|
|
||||||
|
httplib::SSLClient::PemMemory client_pem{};
|
||||||
|
client_pem.cert_pem = client_cert.data();
|
||||||
|
client_pem.cert_pem_len = client_cert.size();
|
||||||
|
client_pem.key_pem = client_key.data();
|
||||||
|
client_pem.key_pem_len = client_key.size();
|
||||||
|
httplib::SSLClient cli("api.example.com", 443, client_pem);
|
||||||
|
```
|
||||||
|
|
||||||
|
`httplib::ws::WebSocketClient` has the same `PemMemory` constructor for `wss://` connections. See [README-websocket.md](README-websocket.md) for details.
|
||||||
|
|
||||||
### Peer Certificate Inspection
|
### Peer Certificate Inspection
|
||||||
|
|
||||||
On the server side, you can inspect the client's peer certificate from a request handler:
|
On the server side, you can inspect the client's peer certificate from a request handler:
|
||||||
@@ -450,6 +485,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") {
|
||||||
@@ -464,6 +501,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.
|
||||||
@@ -790,6 +859,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.
|
||||||
@@ -893,6 +971,7 @@ enum class Error {
|
|||||||
UnsupportedAddressFamily,
|
UnsupportedAddressFamily,
|
||||||
HTTPParsing,
|
HTTPParsing,
|
||||||
InvalidRangeHeader,
|
InvalidRangeHeader,
|
||||||
|
UnsupportedContentEncoding,
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -953,7 +1032,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
|
||||||
@@ -1197,7 +1276,7 @@ for details and for reading the variable from the environment.
|
|||||||
```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.
|
||||||
@@ -1246,6 +1325,36 @@ res->status; // 200
|
|||||||
cli.set_interface("eth0"); // Interface name, IP address or host name
|
cli.set_interface("eth0"); // Interface name, IP address or host name
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The same method is available on `httplib::ws::WebSocketClient`.
|
||||||
|
|
||||||
|
### Override the connection target for a hostname
|
||||||
|
|
||||||
|
`set_hostname_addr_map` redirects where the socket connects, without changing
|
||||||
|
the identity of the request. The hostname the client was constructed with keeps
|
||||||
|
supplying the `Host` header, the SNI, and the name that the server certificate
|
||||||
|
is verified against, so this is a connection-level override only, not a way to
|
||||||
|
talk to a different origin.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
httplib::Client cli("https://example.com");
|
||||||
|
|
||||||
|
// Connect to this IP address instead of resolving "example.com"
|
||||||
|
cli.set_hostname_addr_map({{"example.com", "192.168.1.10"}});
|
||||||
|
```
|
||||||
|
|
||||||
|
A mapped value may be an IP literal or another hostname. An IP literal is used
|
||||||
|
as-is; anything else is resolved as a name, so a host that is only reachable
|
||||||
|
under a different name works too:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
cli.set_hostname_addr_map({{"example.com", "internal.example.lan"}});
|
||||||
|
```
|
||||||
|
|
||||||
|
An empty value is ignored, leaving the original hostname as the connection
|
||||||
|
target.
|
||||||
|
|
||||||
|
The same method is available on `httplib::ws::WebSocketClient`.
|
||||||
|
|
||||||
### Automatic Path Encoding
|
### Automatic Path Encoding
|
||||||
|
|
||||||
The client automatically encodes special characters in URL paths by default:
|
The client automatically encodes special characters in URL paths by default:
|
||||||
@@ -1285,6 +1394,29 @@ httplib::Server svr;
|
|||||||
svr.listen("127.0.0.1", 8080);
|
svr.listen("127.0.0.1", 8080);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Ordered Headers, Query Parameters, and Form Data
|
||||||
|
|
||||||
|
`Headers`, `Params`, `FormFields`, and `FormFiles` preserve the order entries were received (for a parsed request) or inserted (for one you build yourself). Earlier versions stored these in `std::multimap` or `std::unordered_multimap`, which either sorted entries by key or gave no ordering guarantee at all for repeated keys. RFC 9110 §5.3 and RFC 7578 §5.2 both require the original order to be preserved, so this is now guaranteed rather than incidental.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// A request with two Accept-Encoding lines...
|
||||||
|
// Accept-Encoding: gzip
|
||||||
|
// Accept-Encoding: br
|
||||||
|
// ...visits "gzip" before "br", not the other way around.
|
||||||
|
for (auto it = req.headers.equal_range("Accept-Encoding").first;
|
||||||
|
it != req.headers.end(); ++it) {
|
||||||
|
std::cout << it->second << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_header_value(key, id) reaches a specific one directly.
|
||||||
|
auto second = req.get_header_value("Accept-Encoding", 1); // "br"
|
||||||
|
```
|
||||||
|
|
||||||
|
`Headers` matches field names case-insensitively, as before. `Params`, `FormFields`, and `FormFiles` are case-sensitive.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Iterators on these containers follow `std::vector` rules: inserting a new entry invalidates existing iterators. Code that keeps an iterator across a call to `insert()`/`emplace()` needs to re-fetch it afterward.
|
||||||
|
|
||||||
## Payload Limit
|
## Payload Limit
|
||||||
|
|
||||||
The maximum payload body size is limited to 100MB by default for both server and client. You can change it with `set_payload_max_length()` or by defining `CPPHTTPLIB_PAYLOAD_MAX_LENGTH` at compile time. Setting it to `0` disables the limit entirely.
|
The maximum payload body size is limited to 100MB by default for both server and client. You can change it with `set_payload_max_length()` or by defining `CPPHTTPLIB_PAYLOAD_MAX_LENGTH` at compile time. Setting it to `0` disables the limit entirely.
|
||||||
@@ -1321,13 +1453,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
|
||||||
@@ -1447,11 +1579,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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
227
benchmark/ab.sh
Executable file
227
benchmark/ab.sh
Executable file
@@ -0,0 +1,227 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# A/B throughput comparison between two git refs.
|
||||||
|
#
|
||||||
|
# Usage: ./ab.sh [--base REF] [--head REF] [--rounds N] [--duration S]
|
||||||
|
# [--connections N] [--threads N]
|
||||||
|
#
|
||||||
|
# Absolute numbers from a single run are meaningless: on a quiet 8-core laptop
|
||||||
|
# the same binary varies by +/-20% run to run, and shared CI runners are worse.
|
||||||
|
# So both refs are built and then measured alternately in the same session, and
|
||||||
|
# only the ratio of the medians is reported.
|
||||||
|
#
|
||||||
|
# Requires: bombardier, python3, g++ (or $CXX), git.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BASE_REF="master"
|
||||||
|
HEAD_REF="HEAD"
|
||||||
|
ROUNDS=5
|
||||||
|
DURATION="5s"
|
||||||
|
CONNECTIONS=10
|
||||||
|
THREADS=""
|
||||||
|
PORT=8080
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--base) BASE_REF="$2"; shift 2 ;;
|
||||||
|
--head) HEAD_REF="$2"; shift 2 ;;
|
||||||
|
--rounds) ROUNDS="$2"; shift 2 ;;
|
||||||
|
--duration) DURATION="$2"; shift 2 ;;
|
||||||
|
--connections) CONNECTIONS="$2"; shift 2 ;;
|
||||||
|
--threads) THREADS="$2"; shift 2 ;;
|
||||||
|
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
command -v bombardier >/dev/null || { echo "Error: bombardier not found" >&2; exit 1; }
|
||||||
|
command -v python3 >/dev/null || { echo "Error: python3 not found" >&2; exit 1; }
|
||||||
|
|
||||||
|
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||||
|
CXX=${CXX:-g++}
|
||||||
|
|
||||||
|
# Default the thread pool to the core count. The committed benchmark Makefile
|
||||||
|
# hardcodes 16, which heavily oversubscribes a 2-4 vCPU CI runner and inflates
|
||||||
|
# the variance we are trying to see through.
|
||||||
|
if [ -z "$THREADS" ]; then
|
||||||
|
THREADS=$(python3 -c 'import os; print(os.cpu_count() or 4)')
|
||||||
|
fi
|
||||||
|
|
||||||
|
WORKDIR=$(mktemp -d)
|
||||||
|
cleanup() {
|
||||||
|
pkill -f "$WORKDIR/.*/server-ab" 2>/dev/null || true
|
||||||
|
git -C "$REPO_ROOT" worktree remove --force "$WORKDIR/base" 2>/dev/null || true
|
||||||
|
git -C "$REPO_ROOT" worktree remove --force "$WORKDIR/head" 2>/dev/null || true
|
||||||
|
rm -rf "$WORKDIR"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
BASE_SHA=$(git -C "$REPO_ROOT" rev-parse --short "$BASE_REF")
|
||||||
|
HEAD_SHA=$(git -C "$REPO_ROOT" rev-parse --short "$HEAD_REF")
|
||||||
|
|
||||||
|
echo "==> base: $BASE_REF ($BASE_SHA)"
|
||||||
|
echo "==> head: $HEAD_REF ($HEAD_SHA)"
|
||||||
|
echo "==> rounds=$ROUNDS duration=$DURATION connections=$CONNECTIONS threads=$THREADS"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ "$BASE_SHA" = "$HEAD_SHA" ]; then
|
||||||
|
echo "Note: base and head are the same commit; this measures harness noise."
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Build both refs ---
|
||||||
|
build() {
|
||||||
|
local name=$1 ref=$2
|
||||||
|
git -C "$REPO_ROOT" worktree add --detach --quiet "$WORKDIR/$name" "$ref"
|
||||||
|
if [ ! -f "$WORKDIR/$name/benchmark/cpp-httplib/main.cpp" ]; then
|
||||||
|
echo "Error: benchmark/cpp-httplib/main.cpp missing in $ref" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
"$CXX" -o "$WORKDIR/$name/server-ab" -O2 -std=c++11 \
|
||||||
|
-I"$WORKDIR/$name" \
|
||||||
|
-DCPPHTTPLIB_THREAD_POOL_COUNT="$THREADS" \
|
||||||
|
"$WORKDIR/$name/benchmark/cpp-httplib/main.cpp" -lpthread
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "==> Building..."
|
||||||
|
build base "$BASE_REF"
|
||||||
|
build head "$HEAD_REF"
|
||||||
|
|
||||||
|
# --- Measure one ref once, echo rps ---
|
||||||
|
measure() {
|
||||||
|
local name=$1
|
||||||
|
local json rc
|
||||||
|
|
||||||
|
"$WORKDIR/$name/server-ab" >/dev/null 2>&1 &
|
||||||
|
local pid=$!
|
||||||
|
|
||||||
|
# Wait for the listener (no dependency on nc)
|
||||||
|
local i
|
||||||
|
for i in $(seq 1 200); do
|
||||||
|
if (exec 3<>/dev/tcp/127.0.0.1/$PORT) 2>/dev/null; then exec 3>&- 3<&-; break; fi
|
||||||
|
sleep 0.05
|
||||||
|
done
|
||||||
|
|
||||||
|
set +e
|
||||||
|
json=$(bombardier -c "$CONNECTIONS" -d "$DURATION" -o json -p r \
|
||||||
|
"http://127.0.0.1:$PORT/" 2>/dev/null)
|
||||||
|
rc=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
kill "$pid" 2>/dev/null || true
|
||||||
|
wait "$pid" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Wait for the port to be released before the next run
|
||||||
|
for i in $(seq 1 200); do
|
||||||
|
if ! (exec 3<>/dev/tcp/127.0.0.1/$PORT) 2>/dev/null; then break; fi
|
||||||
|
exec 3>&- 3<&-
|
||||||
|
sleep 0.05
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $rc -ne 0 ] || [ -z "$json" ]; then
|
||||||
|
echo "Error: bombardier failed for $name" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 -c '
|
||||||
|
import json, sys
|
||||||
|
r = json.load(sys.stdin)["result"]
|
||||||
|
total = sum(r[k] for k in ("req1xx","req2xx","req3xx","req4xx","req5xx","others"))
|
||||||
|
bad = total - r["req2xx"]
|
||||||
|
if bad:
|
||||||
|
sys.stderr.write("Error: %d non-2xx/error responses\n" % bad)
|
||||||
|
sys.exit(1)
|
||||||
|
print("%.1f" % (total / r["timeTakenSeconds"]))
|
||||||
|
' <<<"$json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Alternate, flipping the order each round to cancel ordering bias ---
|
||||||
|
BASE_RESULTS=()
|
||||||
|
HEAD_RESULTS=()
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==> Measuring..."
|
||||||
|
for ((r = 1; r <= ROUNDS; r++)); do
|
||||||
|
if (( r % 2 == 1 )); then order=("base" "head"); else order=("head" "base"); fi
|
||||||
|
line=" round $r:"
|
||||||
|
for name in "${order[@]}"; do
|
||||||
|
rps=$(measure "$name")
|
||||||
|
if [ "$name" = "base" ]; then BASE_RESULTS+=("$rps"); else HEAD_RESULTS+=("$rps"); fi
|
||||||
|
line="$line $name=$rps"
|
||||||
|
done
|
||||||
|
echo "$line"
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Report ---
|
||||||
|
SUMMARY=$(python3 -c '
|
||||||
|
import statistics, sys
|
||||||
|
from itertools import combinations
|
||||||
|
|
||||||
|
base = [float(x) for x in sys.argv[1].split()]
|
||||||
|
head = [float(x) for x in sys.argv[2].split()]
|
||||||
|
bm, hm = statistics.median(base), statistics.median(head)
|
||||||
|
|
||||||
|
def spread(v):
|
||||||
|
return (max(v) - min(v)) / statistics.median(v) * 100
|
||||||
|
|
||||||
|
def u_stat(a, b):
|
||||||
|
"""Mann-Whitney U: number of (a, b) pairs where a > b, ties count a half."""
|
||||||
|
return sum((x > y) + 0.5 * (x == y) for x in a for y in b)
|
||||||
|
|
||||||
|
def exact_p(a, b):
|
||||||
|
"""Two-sided permutation p-value. A single slow round cannot swing this
|
||||||
|
the way a min/max spread check can."""
|
||||||
|
n1, n2 = len(a), len(b)
|
||||||
|
pooled = a + b
|
||||||
|
observed = abs(u_stat(a, b) - n1 * n2 / 2)
|
||||||
|
total = extreme = 0
|
||||||
|
for idx in combinations(range(n1 + n2), n1):
|
||||||
|
s = set(idx)
|
||||||
|
ga = [pooled[i] for i in idx]
|
||||||
|
gb = [pooled[i] for i in range(n1 + n2) if i not in s]
|
||||||
|
total += 1
|
||||||
|
if abs(u_stat(ga, gb) - n1 * n2 / 2) >= observed:
|
||||||
|
extreme += 1
|
||||||
|
return extreme / total
|
||||||
|
|
||||||
|
print("| | median req/s | min | max | spread |")
|
||||||
|
print("|---|---|---|---|---|")
|
||||||
|
print("| base | %.0f | %.0f | %.0f | %.1f%% |" % (bm, min(base), max(base), spread(base)))
|
||||||
|
print("| head | %.0f | %.0f | %.0f | %.1f%% |" % (hm, min(head), max(head), spread(head)))
|
||||||
|
print("")
|
||||||
|
print("**ratio: %.3fx** (%+.1f%%)" % (hm / bm, (hm / bm - 1) * 100))
|
||||||
|
print("")
|
||||||
|
|
||||||
|
if len(base) + len(head) > 20:
|
||||||
|
print("> %d rounds: skipping the permutation test (too many combinations)."
|
||||||
|
% len(base))
|
||||||
|
else:
|
||||||
|
p = exact_p(base, head)
|
||||||
|
if p <= 0.05:
|
||||||
|
print("> Separation is consistent across rounds (permutation p = %.3f)." % p)
|
||||||
|
else:
|
||||||
|
print("> Not separated from noise (permutation p = %.3f). Inconclusive;" % p)
|
||||||
|
print("> raise --rounds or --duration, or run on a quieter machine.")
|
||||||
|
min_p = exact_p(list(range(len(base))),
|
||||||
|
list(range(len(base), len(base) + len(head))))
|
||||||
|
if min_p > 0.05:
|
||||||
|
print(">")
|
||||||
|
print("> With %d rounds even perfect separation only reaches p = %.3f,"
|
||||||
|
% (len(base), min_p))
|
||||||
|
print("> so this test can never call a win. Use --rounds 4 or more.")
|
||||||
|
' "${BASE_RESULTS[*]}" "${HEAD_RESULTS[*]}")
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "$SUMMARY"
|
||||||
|
|
||||||
|
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
|
||||||
|
{
|
||||||
|
echo "## Benchmark A/B"
|
||||||
|
echo ""
|
||||||
|
echo "- base: \`$BASE_REF\` ($BASE_SHA)"
|
||||||
|
echo "- head: \`$HEAD_REF\` ($HEAD_SHA)"
|
||||||
|
echo "- rounds=$ROUNDS duration=$DURATION connections=$CONNECTIONS threads=$THREADS"
|
||||||
|
echo ""
|
||||||
|
echo "$SUMMARY"
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
fi
|
||||||
@@ -4,7 +4,7 @@ langs = ["en", "ja"]
|
|||||||
|
|
||||||
[site]
|
[site]
|
||||||
title = "cpp-httplib"
|
title = "cpp-httplib"
|
||||||
version = "0.46.0"
|
version = "0.53.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,6 @@ 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).
|
||||||
|
|
||||||
|
> For WebSocket client timeouts, see [W06. Set Timeouts](../w06-websocket-timeouts).
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -94,3 +94,5 @@ A collection of recipes that answer "How do I...?" questions. Each recipe is sel
|
|||||||
- [W02. Set a WebSocket heartbeat](w02-websocket-ping)
|
- [W02. Set a WebSocket heartbeat](w02-websocket-ping)
|
||||||
- [W03. Handle connection close](w03-websocket-close)
|
- [W03. Handle connection close](w03-websocket-close)
|
||||||
- [W04. Send and receive binary frames](w04-websocket-binary)
|
- [W04. Send and receive binary frames](w04-websocket-binary)
|
||||||
|
- [W05. Configure TLS for wss:// connections](w05-websocket-tls)
|
||||||
|
- [W06. Set timeouts](w06-websocket-timeouts)
|
||||||
|
|||||||
@@ -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,8 @@ 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).
|
||||||
|
|
||||||
|
> For TLS configuration on a WebSocket client (`wss://`), see [W05. Configure TLS for wss:// Connections](../w05-websocket-tls).
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -57,9 +57,25 @@ auto res = cli.Get("/");
|
|||||||
|
|
||||||
Note you're using `SSLClient` directly, not `Client`. If the private key has a password, pass it as the fifth argument.
|
Note you're using `SSLClient` directly, not `Client`. If the private key has a password, pass it as the fifth argument.
|
||||||
|
|
||||||
|
The client side has the same `PemMemory` struct too, letting you set the client certificate from PEM in memory.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
httplib::SSLClient::PemMemory pem{};
|
||||||
|
pem.cert_pem = client_cert.data();
|
||||||
|
pem.cert_pem_len = client_cert.size();
|
||||||
|
pem.key_pem = client_key.data();
|
||||||
|
pem.key_pem_len = client_key.size();
|
||||||
|
|
||||||
|
httplib::SSLClient cli("api.example.com", 443, pem);
|
||||||
|
|
||||||
|
auto res = cli.Get("/");
|
||||||
|
```
|
||||||
|
|
||||||
|
> For mTLS with a WebSocket client (`wss://`), see [W05. Configure TLS for wss:// Connections](../w05-websocket-tls).
|
||||||
|
|
||||||
## 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. For CA and client certificate configuration, see [W05. Configure TLS for wss:// Connections](../w05-websocket-tls).
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
49
docs-src/pages/en/cookbook/w05-websocket-tls.md
Normal file
49
docs-src/pages/en/cookbook/w05-websocket-tls.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
title: "W05. Configure TLS for wss:// Connections"
|
||||||
|
order: 55
|
||||||
|
status: "draft"
|
||||||
|
---
|
||||||
|
|
||||||
|
Client-side TLS configuration for `wss://` (WebSocket over TLS) connections uses almost the same API as `SSLClient`. `ws::WebSocketClient` handles both `ws://` and `wss://` through the same class, so there's no separate class to switch to the way `SSLClient` requires.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
httplib::ws::WebSocketClient ws1("ws://localhost:8080/ws"); // plaintext
|
||||||
|
httplib::ws::WebSocketClient ws2("wss://localhost:8443/ws"); // TLS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verifying the server certificate
|
||||||
|
|
||||||
|
Use `set_ca_cert_path()` to point at your own CA certificate. The signature matches `SSLClient`: the first argument is the CA certificate file, the second is an optional CA directory.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
httplib::ws::WebSocketClient ws("wss://internal.example.com/ws");
|
||||||
|
ws.set_ca_cert_path("/etc/ssl/certs/internal-ca.pem");
|
||||||
|
|
||||||
|
if (ws.connect()) {
|
||||||
|
ws.send("hello");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To disable certificate verification entirely, use `enable_server_certificate_verification(false)`. For details on that behavior, see [T02. Control SSL Certificate Verification](../t02-cert-verification).
|
||||||
|
|
||||||
|
## Presenting a client certificate (mTLS)
|
||||||
|
|
||||||
|
`ws::WebSocketClient` has a constructor overload that takes a `PemMemory` struct, letting `wss://` connections present a client certificate.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
httplib::ws::WebSocketClient::PemMemory pem{};
|
||||||
|
pem.cert_pem = client_cert.data();
|
||||||
|
pem.cert_pem_len = client_cert.size();
|
||||||
|
pem.key_pem = client_key.data();
|
||||||
|
pem.key_pem_len = client_key.size();
|
||||||
|
|
||||||
|
httplib::ws::WebSocketClient ws("wss://api.example.com/ws", pem);
|
||||||
|
|
||||||
|
if (ws.connect()) {
|
||||||
|
ws.send("hello");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Passing `PemMemory` to a `ws://` (non-TLS) URL is silently ignored. There's no constructor that reads the cert files directly, so unlike `SSLClient` you always load the PEM into memory yourself before passing it in.
|
||||||
|
|
||||||
|
For the full mTLS picture, including server-side setup and use cases, see [T04. Configure mTLS](../t04-mtls).
|
||||||
51
docs-src/pages/en/cookbook/w06-websocket-timeouts.md
Normal file
51
docs-src/pages/en/cookbook/w06-websocket-timeouts.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
title: "W06. Set Timeouts"
|
||||||
|
order: 56
|
||||||
|
status: "draft"
|
||||||
|
---
|
||||||
|
|
||||||
|
`ws::WebSocketClient` has the same three kinds of timeouts as `Client`, with the same meaning.
|
||||||
|
|
||||||
|
| Kind | API | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Connection | `set_connection_timeout` | 300s |
|
||||||
|
| Read | `set_read_timeout` | 300s (`CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND`) |
|
||||||
|
| Write | `set_write_timeout` | 5s |
|
||||||
|
|
||||||
|
## Basic usage
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws");
|
||||||
|
|
||||||
|
ws.set_connection_timeout(5, 0); // 5 seconds
|
||||||
|
ws.set_read_timeout(30, 0); // 30 seconds
|
||||||
|
ws.set_write_timeout(10, 0); // 10 seconds
|
||||||
|
|
||||||
|
if (ws.connect()) {
|
||||||
|
ws.send("hello");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Set these before calling `connect()`.
|
||||||
|
|
||||||
|
## Use `std::chrono`
|
||||||
|
|
||||||
|
Just like `Client`, there's an overload that takes a `std::chrono` duration directly.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
ws.set_connection_timeout(5s);
|
||||||
|
ws.set_read_timeout(30s);
|
||||||
|
ws.set_write_timeout(10s);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Watch out for what the read timeout means
|
||||||
|
|
||||||
|
`set_read_timeout()` applies to a single `read()` call. If no message arrives within that time, `read()` returns `ReadResult::Fail`. For connections where long idle periods are normal — waiting on notifications, for example — set a longer timeout, or reconnect from your application code when the read fails.
|
||||||
|
|
||||||
|
> Unresponsive-peer detection via Ping/Pong is a separate mechanism. See [W02. Set a WebSocket Heartbeat](../w02-websocket-ping) for details.
|
||||||
|
|
||||||
|
## How this differs from `Client`
|
||||||
|
|
||||||
|
For `Client`'s timeout configuration, see [C12. Set Timeouts](../c12-timeouts). The behavior and API are nearly identical, but `WebSocketClient` has no equivalent to `set_max_timeout()` for capping the whole request — once connected, the connection stays open for as long as you keep calling `read()`.
|
||||||
@@ -79,6 +79,8 @@ cpp-httplib also supports Mbed TLS and wolfSSL in addition to OpenSSL. You can s
|
|||||||
| Mbed TLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | `libmbedtls`, `libmbedx509`, `libmbedcrypto` |
|
| Mbed TLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | `libmbedtls`, `libmbedx509`, `libmbedcrypto` |
|
||||||
| wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | `libwolfssl` |
|
| wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | `libwolfssl` |
|
||||||
|
|
||||||
|
Mbed TLS 2.x, 3.x, and 4.x are all supported and auto-detected. Note that Mbed TLS 4.x renames `libmbedcrypto` to `libtfpsacrypto`, so link against that instead.
|
||||||
|
|
||||||
This tour assumes OpenSSL, but the API is the same regardless of which backend you choose.
|
This tour assumes OpenSSL, but the API is the same regardless of which backend you choose.
|
||||||
|
|
||||||
## Next Step
|
## Next Step
|
||||||
|
|||||||
@@ -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,6 @@ 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)を使ってください。
|
||||||
|
|
||||||
|
> WebSocketクライアントのタイムアウト設定は[W06. タイムアウトを設定する](../w06-websocket-timeouts)を参照してください。
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)を参照してください。
|
||||||
|
|||||||
@@ -94,3 +94,5 @@ status: "draft"
|
|||||||
- [W02. ハートビートを設定する](w02-websocket-ping)
|
- [W02. ハートビートを設定する](w02-websocket-ping)
|
||||||
- [W03. 接続クローズをハンドリングする](w03-websocket-close)
|
- [W03. 接続クローズをハンドリングする](w03-websocket-close)
|
||||||
- [W04. バイナリフレームを送受信する](w04-websocket-binary)
|
- [W04. バイナリフレームを送受信する](w04-websocket-binary)
|
||||||
|
- [W05. wss接続でTLSを設定する](w05-websocket-tls)
|
||||||
|
- [W06. タイムアウトを設定する](w06-websocket-timeouts)
|
||||||
|
|||||||
@@ -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,8 @@ 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)を参照してください。
|
||||||
|
|
||||||
|
> WebSocketクライアント(`wss://`)のTLS設定は[W05. wss接続でTLSを設定する](../w05-websocket-tls)を参照してください。
|
||||||
|
|||||||
@@ -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)を参照してください。
|
||||||
|
|||||||
@@ -57,9 +57,25 @@ auto res = cli.Get("/");
|
|||||||
|
|
||||||
`Client`ではなく`SSLClient`を直接使う点に注意してください。秘密鍵にパスワードがある場合は第5引数で渡せます。
|
`Client`ではなく`SSLClient`を直接使う点に注意してください。秘密鍵にパスワードがある場合は第5引数で渡せます。
|
||||||
|
|
||||||
|
クライアント側にも同じ`PemMemory`構造体があり、メモリ上のPEMからクライアント証明書を設定できます。
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
httplib::SSLClient::PemMemory pem{};
|
||||||
|
pem.cert_pem = client_cert.data();
|
||||||
|
pem.cert_pem_len = client_cert.size();
|
||||||
|
pem.key_pem = client_key.data();
|
||||||
|
pem.key_pem_len = client_key.size();
|
||||||
|
|
||||||
|
httplib::SSLClient cli("api.example.com", 443, pem);
|
||||||
|
|
||||||
|
auto res = cli.Get("/");
|
||||||
|
```
|
||||||
|
|
||||||
|
> WebSocketクライアント(`wss://`)でmTLSを使う場合は[W05. wss接続でTLSを設定する](../w05-websocket-tls)を参照してください。
|
||||||
|
|
||||||
## ハンドラからクライアント情報を取得する
|
## ハンドラからクライアント情報を取得する
|
||||||
|
|
||||||
ハンドラの中で、どのクライアントが接続してきたかを確認したいときは`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://`スキームを指定するだけです。CA証明書やクライアント証明書の設定は[W05. wss接続でTLSを設定する](../w05-websocket-tls)を参照してください。
|
||||||
|
|||||||
@@ -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)を参照してください。
|
||||||
|
|
||||||
## サンプル: 画像を送る
|
## サンプル: 画像を送る
|
||||||
|
|
||||||
|
|||||||
49
docs-src/pages/ja/cookbook/w05-websocket-tls.md
Normal file
49
docs-src/pages/ja/cookbook/w05-websocket-tls.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
title: "W05. wss接続でTLSを設定する"
|
||||||
|
order: 55
|
||||||
|
status: "draft"
|
||||||
|
---
|
||||||
|
|
||||||
|
`wss://`(WebSocket over TLS)接続のクライアント側TLS設定は、`SSLClient`とほぼ同じAPIです。`ws::WebSocketClient`は`ws://`と`wss://`を同じクラスで扱うので、`SSLClient`のような別クラスへの切り替えは不要です。
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
httplib::ws::WebSocketClient ws1("ws://localhost:8080/ws"); // 平文
|
||||||
|
httplib::ws::WebSocketClient ws2("wss://localhost:8443/ws"); // TLS
|
||||||
|
```
|
||||||
|
|
||||||
|
## サーバー証明書の検証
|
||||||
|
|
||||||
|
`set_ca_cert_path()`で独自のCA証明書を指定できます。シグネチャは`SSLClient`と同じで、第1引数がCA証明書ファイル、第2引数がCA証明書ディレクトリ(省略可)です。
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
httplib::ws::WebSocketClient ws("wss://internal.example.com/ws");
|
||||||
|
ws.set_ca_cert_path("/etc/ssl/certs/internal-ca.pem");
|
||||||
|
|
||||||
|
if (ws.connect()) {
|
||||||
|
ws.send("hello");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
証明書検証そのものを無効にしたい場合は`enable_server_certificate_verification(false)`が使えます。挙動の詳細は[T02. SSL証明書の検証を制御する](../t02-cert-verification)を参照してください。
|
||||||
|
|
||||||
|
## クライアント証明書を使う(mTLS)
|
||||||
|
|
||||||
|
`ws::WebSocketClient`には`PemMemory`構造体を受け取るコンストラクタがあり、`wss://`接続でクライアント証明書を提示できます。
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
httplib::ws::WebSocketClient::PemMemory pem{};
|
||||||
|
pem.cert_pem = client_cert.data();
|
||||||
|
pem.cert_pem_len = client_cert.size();
|
||||||
|
pem.key_pem = client_key.data();
|
||||||
|
pem.key_pem_len = client_key.size();
|
||||||
|
|
||||||
|
httplib::ws::WebSocketClient ws("wss://api.example.com/ws", pem);
|
||||||
|
|
||||||
|
if (ws.connect()) {
|
||||||
|
ws.send("hello");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`ws://`(非TLS)のURLに`PemMemory`を渡した場合は黙って無視されます。`SSLClient`と違い、ファイルパスから直接読み込むコンストラクタは用意されていないので、PEMをメモリ上に読み込んでから渡す必要があります。
|
||||||
|
|
||||||
|
mTLSの全体像(サーバー側の設定や用途の解説を含む)は[T04. mTLSを設定する](../t04-mtls)を参照してください。
|
||||||
51
docs-src/pages/ja/cookbook/w06-websocket-timeouts.md
Normal file
51
docs-src/pages/ja/cookbook/w06-websocket-timeouts.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
title: "W06. タイムアウトを設定する"
|
||||||
|
order: 56
|
||||||
|
status: "draft"
|
||||||
|
---
|
||||||
|
|
||||||
|
`ws::WebSocketClient`には`Client`と同じ3種類のタイムアウトがあり、意味も同じです。
|
||||||
|
|
||||||
|
| 種類 | API | デフォルト |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 接続タイムアウト | `set_connection_timeout` | 300秒 |
|
||||||
|
| 読み取りタイムアウト | `set_read_timeout` | 300秒(`CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND`) |
|
||||||
|
| 書き込みタイムアウト | `set_write_timeout` | 5秒 |
|
||||||
|
|
||||||
|
## 基本の使い方
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws");
|
||||||
|
|
||||||
|
ws.set_connection_timeout(5, 0); // 5秒
|
||||||
|
ws.set_read_timeout(30, 0); // 30秒
|
||||||
|
ws.set_write_timeout(10, 0); // 10秒
|
||||||
|
|
||||||
|
if (ws.connect()) {
|
||||||
|
ws.send("hello");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`connect()`を呼ぶ前に設定してください。
|
||||||
|
|
||||||
|
## `std::chrono`で指定する
|
||||||
|
|
||||||
|
`Client`と同じく、`std::chrono`の期間を直接渡すオーバーロードもあります。
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
ws.set_connection_timeout(5s);
|
||||||
|
ws.set_read_timeout(30s);
|
||||||
|
ws.set_write_timeout(10s);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 読み取りタイムアウトの意味に注意
|
||||||
|
|
||||||
|
`set_read_timeout()`は「1回の`read()`呼び出し」に対するタイムアウトです。メッセージが届かないまま指定時間が経過すると`read()`が`ReadResult::Fail`を返します。通知の待受のように長時間メッセージが来ないことが正常な接続では、意図せず切断されないよう長めに設定するか、切断されたらアプリケーション側で再接続してください。
|
||||||
|
|
||||||
|
> Ping/Pongによる無応答ピア検出は別の仕組みです。詳しくは[W02. ハートビートを設定する](../w02-websocket-ping)を参照してください。
|
||||||
|
|
||||||
|
## `Client`との違い
|
||||||
|
|
||||||
|
`Client`のタイムアウト設定については[C12. タイムアウトを設定する](../c12-timeouts)を参照してください。挙動とAPIはほぼ同じですが、`WebSocketClient`には`set_max_timeout()`に相当するリクエスト全体のタイムアウトはありません。接続を確立したあとは、`read()`のループを回し続ける限り接続が維持されます。
|
||||||
@@ -79,6 +79,8 @@ cpp-httplibはOpenSSL以外にも、Mbed TLSとwolfSSLに対応しています
|
|||||||
| Mbed TLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | `libmbedtls`, `libmbedx509`, `libmbedcrypto` |
|
| Mbed TLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | `libmbedtls`, `libmbedx509`, `libmbedcrypto` |
|
||||||
| wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | `libwolfssl` |
|
| wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | `libwolfssl` |
|
||||||
|
|
||||||
|
Mbed TLSは2.x、3.x、4.xいずれも対応していて、自動判定されます。4.xでは`libmbedcrypto`が`libtfpsacrypto`という名前に変わっているので、リンクするライブラリはそちらに読み替えてください。
|
||||||
|
|
||||||
このTourではOpenSSLを前提に進めますが、APIはどのバックエンドでも共通です。
|
このTourではOpenSSLを前提に進めますが、APIはどのバックエンドでも共通です。
|
||||||
|
|
||||||
## 次のステップ
|
## 次のステップ
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// sample.cc
|
// server.cc
|
||||||
//
|
//
|
||||||
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
|
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <httplib.h>
|
#include <httplib.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
using namespace httplib;
|
using namespace httplib;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
@@ -45,12 +46,41 @@ int main(void) {
|
|||||||
<< "text file length: " << text_file.content.length() << endl
|
<< "text file length: " << text_file.content.length() << endl
|
||||||
<< "text file name: " << text_file.filename << endl;
|
<< "text file name: " << text_file.filename << endl;
|
||||||
|
|
||||||
|
// Reduce a client-supplied filename to a safe base name, or return an
|
||||||
|
// empty string if it cannot be trusted (empty, ".", "..", or contains a
|
||||||
|
// path separator).
|
||||||
|
auto sanitize = [](const string &filename) -> string {
|
||||||
|
auto name = filename.substr(filename.find_last_of("/\\") + 1);
|
||||||
|
if (name.empty() || name == "." || name == ".." ||
|
||||||
|
name.find(':') != string::npos) {
|
||||||
|
return string();
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto image_name = sanitize(image_file.filename);
|
||||||
|
const auto text_name = sanitize(text_file.filename);
|
||||||
|
if (image_name.empty() || text_name.empty()) {
|
||||||
|
res.status = StatusCode::BadRequest_400;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ofstream ofs(image_file.filename, ios::binary);
|
ofstream ofs(image_name, ios::binary);
|
||||||
|
if (!ofs) {
|
||||||
|
res.status = StatusCode::InternalServerError_500;
|
||||||
|
res.set_content("Failed to write image file", "text/plain");
|
||||||
|
return;
|
||||||
|
}
|
||||||
ofs << image_file.content;
|
ofs << image_file.content;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
ofstream ofs(text_file.filename);
|
ofstream ofs(text_name);
|
||||||
|
if (!ofs) {
|
||||||
|
res.status = StatusCode::InternalServerError_500;
|
||||||
|
res.set_content("Failed to write text file", "text/plain");
|
||||||
|
return;
|
||||||
|
}
|
||||||
ofs << text_file.content;
|
ofs << text_file.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user