Repository: yhirose/cpp-httplib Branch: master Commit: 45820de3327c Files: 178 Total size: 5.1 MB Directory structure: gitextract_7tv2227u/ ├── .gitattributes ├── .github/ │ └── workflows/ │ ├── abidiff.yaml │ ├── cifuzz.yaml │ ├── docs.yml │ ├── release-docker.yml │ ├── test-32bit.yml │ ├── test.yaml │ ├── test_benchmark.yaml │ ├── test_no_exceptions.yaml │ ├── test_offline.yaml │ └── test_proxy.yaml ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── README-sse.md ├── README-stream.md ├── README-websocket.md ├── README.md ├── benchmark/ │ ├── Makefile │ ├── cpp-httplib/ │ │ └── main.cpp │ └── crow/ │ ├── crow_all.h │ └── main.cpp ├── cmake/ │ ├── FindBrotli.cmake │ ├── httplibConfig.cmake.in │ └── modules.cmake ├── docker/ │ ├── html/ │ │ └── index.html │ └── main.cc ├── docker-compose.yml ├── docs-src/ │ ├── config.toml │ └── pages/ │ ├── en/ │ │ ├── cookbook/ │ │ │ └── index.md │ │ ├── index.md │ │ ├── llm-app/ │ │ │ └── index.md │ │ └── tour/ │ │ ├── 01-getting-started.md │ │ ├── 02-basic-client.md │ │ ├── 03-basic-server.md │ │ ├── 04-static-file-server.md │ │ ├── 05-tls-setup.md │ │ ├── 06-https-client.md │ │ ├── 07-https-server.md │ │ ├── 08-websocket.md │ │ ├── 09-whats-next.md │ │ └── index.md │ └── ja/ │ ├── cookbook/ │ │ └── index.md │ ├── index.md │ ├── llm-app/ │ │ └── index.md │ └── tour/ │ ├── 01-getting-started.md │ ├── 02-basic-client.md │ ├── 03-basic-server.md │ ├── 04-static-file-server.md │ ├── 05-tls-setup.md │ ├── 06-https-client.md │ ├── 07-https-server.md │ ├── 08-websocket.md │ ├── 09-whats-next.md │ └── index.md ├── example/ │ ├── Dockerfile.hello │ ├── Makefile │ ├── accept_header.cc │ ├── benchmark.cc │ ├── ca-bundle.crt │ ├── client.cc │ ├── client.vcxproj │ ├── example.sln │ ├── hello.cc │ ├── one_time_request.cc │ ├── redirect.cc │ ├── server.cc │ ├── server.vcxproj │ ├── server_and_client.cc │ ├── simplecli.cc │ ├── simplesvr.cc │ ├── ssecli-stream.cc │ ├── ssecli.cc │ ├── ssesvr.cc │ ├── upload.cc │ ├── uploader.sh │ └── wsecho.cc ├── generate_module.py ├── httplib.h ├── justfile ├── meson.build ├── meson_options.txt ├── split.py └── test/ ├── CMakeLists.txt ├── Makefile ├── ca-bundle.crt ├── fuzzing/ │ ├── CMakeLists.txt │ ├── Makefile │ ├── corpus/ │ │ ├── 1 │ │ ├── 2 │ │ ├── 3 │ │ ├── clusterfuzz-testcase-minimized-server_fuzzer-5042094968537088 │ │ ├── clusterfuzz-testcase-minimized-server_fuzzer-5372331946541056 │ │ ├── clusterfuzz-testcase-minimized-server_fuzzer-5386708825800704 │ │ ├── clusterfuzz-testcase-minimized-server_fuzzer-5667822731132928 │ │ ├── clusterfuzz-testcase-minimized-server_fuzzer-5886572146327552 │ │ ├── clusterfuzz-testcase-minimized-server_fuzzer-5942767436562432 │ │ ├── clusterfuzz-testcase-minimized-server_fuzzer-6007379124158464 │ │ ├── clusterfuzz-testcase-minimized-server_fuzzer-6508706672541696 │ │ └── issue1264 │ ├── server_fuzzer.cc │ ├── server_fuzzer.dict │ └── standalone_fuzz_target_runner.cpp ├── gen-certs.sh ├── gtest/ │ ├── include/ │ │ └── gtest/ │ │ ├── gtest-assertion-result.h │ │ ├── gtest-death-test.h │ │ ├── gtest-matchers.h │ │ ├── gtest-message.h │ │ ├── gtest-param-test.h │ │ ├── gtest-printers.h │ │ ├── gtest-spi.h │ │ ├── gtest-test-part.h │ │ ├── gtest-typed-test.h │ │ ├── gtest.h │ │ ├── gtest_pred_impl.h │ │ ├── gtest_prod.h │ │ └── internal/ │ │ ├── custom/ │ │ │ ├── README.md │ │ │ ├── gtest-port.h │ │ │ ├── gtest-printers.h │ │ │ └── gtest.h │ │ ├── gtest-death-test-internal.h │ │ ├── gtest-filepath.h │ │ ├── gtest-internal.h │ │ ├── gtest-param-util.h │ │ ├── gtest-port-arch.h │ │ ├── gtest-port.h │ │ ├── gtest-string.h │ │ └── gtest-type-util.h │ └── src/ │ ├── gtest-all.cc │ ├── gtest-assertion-result.cc │ ├── gtest-death-test.cc │ ├── gtest-filepath.cc │ ├── gtest-internal-inl.h │ ├── gtest-matchers.cc │ ├── gtest-port.cc │ ├── gtest-printers.cc │ ├── gtest-test-part.cc │ ├── gtest-typed-test.cc │ ├── gtest.cc │ └── gtest_main.cc ├── include_httplib.cc ├── include_windows_h.cc ├── lsan_suppressions.txt ├── make-shared-library.sh ├── meson.build ├── proxy/ │ ├── Dockerfile │ ├── basic_passwd │ ├── basic_squid.conf │ ├── digest_passwd │ ├── digest_squid.conf │ ├── docker-compose.ci.yml │ └── docker-compose.yml ├── test.cc ├── test.conf ├── test.rootCA.conf ├── test.sln ├── test.vcxproj ├── test_32bit_build.cpp ├── test_benchmark.cc ├── test_proxy.cc ├── test_thread_pool.cc ├── test_websocket_heartbeat.cc ├── www/ │ ├── dir/ │ │ ├── 1MB.txt │ │ ├── index.html │ │ ├── meson.build │ │ ├── test.abcde │ │ └── test.html │ ├── empty_file │ ├── file │ ├── meson.build │ └── 日本語Dir/ │ ├── meson.build │ └── 日本語File.txt ├── www2/ │ └── dir/ │ ├── index.html │ ├── meson.build │ └── test.html └── www3/ └── dir/ ├── index.html ├── meson.build └── test.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ /test/www*/dir/*.html text eol=lf /test/www*/dir/*.txt text eol=lf ================================================ FILE: .github/workflows/abidiff.yaml ================================================ # SPDX-FileCopyrightText: 2025 Andrea Pappacoda # SPDX-License-Identifier: MIT name: abidiff on: [push, pull_request] concurrency: group: ${{ github.workflow }}-${{ github.ref || github.run_id }} cancel-in-progress: true defaults: run: shell: sh jobs: abi: runs-on: ubuntu-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name container: image: debian:testing steps: - name: Install dependencies run: apt -y --update install --no-install-recommends abigail-tools ca-certificates g++ git libbrotli-dev libssl-dev libzstd-dev meson pkg-config python3 zlib1g-dev - uses: actions/checkout@v4 with: path: current - uses: actions/checkout@v4 with: path: previous fetch-depth: 0 - name: Checkout previous working-directory: previous run: | git switch master git describe --tags --abbrev=0 master | xargs git checkout - name: Build current working-directory: current run: | meson setup --buildtype=debug -Dcpp-httplib_compile=true build ninja -C build - name: Build previous working-directory: previous run: | meson setup --buildtype=debug -Dcpp-httplib_compile=true build ninja -C build - name: Run abidiff run: abidiff --headers-dir1 previous/build --headers-dir2 current/build previous/build/libcpp-httplib.so current/build/libcpp-httplib.so ================================================ FILE: .github/workflows/cifuzz.yaml ================================================ name: CIFuzz on: [pull_request] concurrency: group: ${{ github.workflow }}-${{ github.ref || github.run_id }} cancel-in-progress: true jobs: Fuzzing: runs-on: ubuntu-latest steps: - name: Build Fuzzers id: build uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master with: oss-fuzz-project-name: 'cpp-httplib' dry-run: false language: c++ - name: Run Fuzzers uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master with: oss-fuzz-project-name: 'cpp-httplib' fuzz-seconds: 600 dry-run: false language: c++ - name: Upload Crash uses: actions/upload-artifact@v4 if: failure() && steps.build.outcome == 'success' with: name: artifacts path: ./out/artifacts ================================================ FILE: .github/workflows/docs.yml ================================================ name: docs on: push: branches: [master] paths: - 'docs-src/**' permissions: contents: read pages: write id-token: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: deploy: runs-on: ubuntu-latest environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: Install docs-gen run: cargo install docs-gen - name: Build run: docs-gen build docs-src docs - uses: actions/configure-pages@v5 - uses: actions/upload-pages-artifact@v3 with: path: docs - id: deployment uses: actions/deploy-pages@v4 ================================================ FILE: .github/workflows/release-docker.yml ================================================ name: Release Docker Image on: release: types: [published] workflow_dispatch: jobs: build-and-push: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 # Fetch all history and tags - name: Extract tag (manual) if: github.event_name == 'workflow_dispatch' id: set_tag_manual run: | # Checkout the latest tag and set output git fetch --tags LATEST_TAG=$(git describe --tags --abbrev=0) git checkout $LATEST_TAG echo "tag=${LATEST_TAG#v}" >> $GITHUB_OUTPUT - name: Extract tag (release) if: github.event_name == 'release' id: set_tag_release run: echo "tag=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile push: true platforms: linux/amd64,linux/arm64 # Build for both amd64 and arm64 # Use extracted tag without leading 'v' tags: | yhirose4dockerhub/cpp-httplib-server:latest yhirose4dockerhub/cpp-httplib-server:${{ steps.set_tag_manual.outputs.tag || steps.set_tag_release.outputs.tag }} ================================================ FILE: .github/workflows/test-32bit.yml ================================================ name: 32-bit Build Test on: push: branches: [master] pull_request: branches: [master] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref || github.run_id }} cancel-in-progress: true jobs: test-win32: name: Windows 32-bit (MSVC x86) runs-on: windows-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 - name: Build (Win32) shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86 cl /std:c++14 /EHsc /W4 /WX /c /Fo:NUL test\test_32bit_build.cpp test-arm32: name: ARM 32-bit (cross-compile) runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 - name: Install cross compiler run: sudo apt-get update && sudo apt-get install -y g++-arm-linux-gnueabihf - name: Build (ARM 32-bit) run: arm-linux-gnueabihf-g++ -std=c++11 -Wall -Wextra -Wno-psabi -Werror -c -o /dev/null test/test_32bit_build.cpp ================================================ FILE: .github/workflows/test.yaml ================================================ name: test on: push: pull_request: workflow_dispatch: inputs: gtest_filter: description: 'Google Test filter' test_linux: description: 'Test on Linux' type: boolean default: true test_macos: description: 'Test on MacOS' type: boolean default: true test_windows: description: 'Test on Windows' type: boolean default: true concurrency: group: ${{ github.workflow }}-${{ github.ref || github.run_id }} cancel-in-progress: true env: GTEST_FILTER: ${{ github.event.inputs.gtest_filter || '*' }} jobs: style-check: runs-on: ubuntu-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name continue-on-error: true steps: - name: checkout uses: actions/checkout@v4 - name: run style check run: | clang-format --version cd test && make style_check build-and-test-on-32bit: runs-on: ubuntu-latest if: > (github.event_name == 'push') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) || (github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true') strategy: matrix: config: - arch_flags: -m32 arch_suffix: :i386 name: (32-bit) steps: - name: checkout uses: actions/checkout@v4 - name: install libraries run: | sudo dpkg --add-architecture i386 sudo apt-get update sudo apt-get install -y libc6-dev${{ matrix.config.arch_suffix }} libstdc++-13-dev${{ matrix.config.arch_suffix }} \ libssl-dev${{ matrix.config.arch_suffix }} libcurl4-openssl-dev${{ matrix.config.arch_suffix }} \ zlib1g-dev${{ matrix.config.arch_suffix }} libbrotli-dev${{ matrix.config.arch_suffix }} \ libzstd-dev${{ matrix.config.arch_suffix }} - name: build and run tests run: cd test && make test EXTRA_CXXFLAGS="${{ matrix.config.arch_flags }}" ubuntu: runs-on: ubuntu-latest if: > (github.event_name == 'push') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) || (github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true') strategy: matrix: tls_backend: [openssl, mbedtls, wolfssl] name: ubuntu (${{ matrix.tls_backend }}) 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 OpenSSL if: matrix.tls_backend == 'openssl' run: sudo apt-get install -y libssl-dev - name: install Mbed TLS if: matrix.tls_backend == 'mbedtls' run: sudo apt-get install -y libmbedtls-dev - name: install wolfSSL if: matrix.tls_backend == 'wolfssl' run: sudo apt-get install -y libwolfssl-dev - name: build and run tests (OpenSSL) if: matrix.tls_backend == 'openssl' run: cd test && make test_split && make test_openssl_parallel env: LSAN_OPTIONS: suppressions=lsan_suppressions.txt - name: build and run tests (Mbed TLS) if: matrix.tls_backend == 'mbedtls' run: cd test && make test_split_mbedtls && make test_mbedtls_parallel - name: build and run tests (wolfSSL) if: matrix.tls_backend == 'wolfssl' run: cd test && make test_split_wolfssl && make test_wolfssl_parallel - name: run fuzz test target if: matrix.tls_backend == 'openssl' run: cd test && make fuzz_test - name: build and run WebSocket heartbeat test if: matrix.tls_backend == 'openssl' run: cd test && make test_websocket_heartbeat && ./test_websocket_heartbeat - name: build and run ThreadPool test run: cd test && make test_thread_pool && ./test_thread_pool macos: runs-on: macos-latest if: > (github.event_name == 'push') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) || (github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true') strategy: matrix: tls_backend: [openssl, mbedtls, wolfssl] name: macos (${{ matrix.tls_backend }}) steps: - name: checkout uses: actions/checkout@v4 - name: install Mbed TLS if: matrix.tls_backend == 'mbedtls' run: brew install mbedtls@3 - name: install wolfSSL if: matrix.tls_backend == 'wolfssl' run: brew install wolfssl - name: build and run tests (OpenSSL) if: matrix.tls_backend == 'openssl' run: cd test && make test_split && make test_openssl_parallel env: LSAN_OPTIONS: suppressions=lsan_suppressions.txt - name: build and run tests (Mbed TLS) if: matrix.tls_backend == 'mbedtls' run: cd test && make test_split_mbedtls && make test_mbedtls_parallel - name: build and run tests (wolfSSL) if: matrix.tls_backend == 'wolfssl' run: cd test && make test_split_wolfssl && make test_wolfssl_parallel - name: run fuzz test target if: matrix.tls_backend == 'openssl' run: cd test && make fuzz_test - name: build and run WebSocket heartbeat test if: matrix.tls_backend == 'openssl' run: cd test && make test_websocket_heartbeat && ./test_websocket_heartbeat - name: build and run ThreadPool test run: cd test && make test_thread_pool && ./test_thread_pool windows: runs-on: windows-latest if: > (github.event_name == 'push') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) || (github.event_name == 'workflow_dispatch' && github.event.inputs.test_windows == 'true') strategy: matrix: config: - with_ssl: false compiled: false run_tests: true name: without SSL - with_ssl: true compiled: false run_tests: true name: with SSL - with_ssl: false compiled: true run_tests: false name: compiled name: windows ${{ matrix.config.name }} steps: - name: Prepare Git for Checkout on Windows run: | git config --global core.autocrlf false git config --global core.eol lf - name: Checkout uses: actions/checkout@v4 - name: Export GitHub Actions cache environment variables uses: actions/github-script@v7 with: script: | core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - name: Setup msbuild on windows uses: microsoft/setup-msbuild@v2 - name: Cache vcpkg packages id: vcpkg-cache uses: actions/cache@v4 with: path: C:/vcpkg/installed key: vcpkg-installed-windows-gtest-curl-zlib-brotli-zstd - name: Install vcpkg dependencies if: steps.vcpkg-cache.outputs.cache-hit != 'true' run: vcpkg install gtest curl zlib brotli zstd - name: Install OpenSSL if: ${{ matrix.config.with_ssl }} run: choco install openssl - name: Configure CMake ${{ matrix.config.name }} run: > cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake -DHTTPLIB_TEST=ON -DHTTPLIB_COMPILE=${{ matrix.config.compiled && 'ON' || 'OFF' }} -DHTTPLIB_USE_OPENSSL_IF_AVAILABLE=${{ matrix.config.with_ssl && 'ON' || 'OFF' }} -DHTTPLIB_REQUIRE_ZLIB=ON -DHTTPLIB_REQUIRE_BROTLI=ON -DHTTPLIB_REQUIRE_ZSTD=ON -DHTTPLIB_REQUIRE_OPENSSL=${{ matrix.config.with_ssl && 'ON' || 'OFF' }} - name: Build ${{ matrix.config.name }} run: cmake --build build --config Release -- /v:m /clp:ShowCommandLine - name: Run tests ${{ matrix.config.name }} if: ${{ matrix.config.run_tests }} shell: pwsh working-directory: build/test run: | $shards = 4 $procs = @() for ($i = 0; $i -lt $shards; $i++) { $log = "shard_${i}.log" $procs += Start-Process -FilePath ./Release/httplib-test.exe ` -ArgumentList "--gtest_color=yes","--gtest_filter=${{ github.event.inputs.gtest_filter || '*' }}" ` -NoNewWindow -PassThru -RedirectStandardOutput $log -RedirectStandardError "${log}.err" ` -Environment @{ GTEST_TOTAL_SHARDS="$shards"; GTEST_SHARD_INDEX="$i" } } $procs | Wait-Process $failed = $false for ($i = 0; $i -lt $shards; $i++) { $log = "shard_${i}.log" if (Select-String -Path $log -Pattern "\[ PASSED \]" -Quiet) { $passed = (Select-String -Path $log -Pattern "\[ PASSED \]").Line Write-Host "Shard ${i}: $passed" } else { Write-Host "=== Shard $i FAILED ===" Get-Content $log if (Test-Path "${log}.err") { Get-Content "${log}.err" } $failed = $true } } if ($failed) { exit 1 } Write-Host "All shards passed." env: VCPKG_ROOT: "C:/vcpkg" VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" ================================================ FILE: .github/workflows/test_benchmark.yaml ================================================ name: benchmark on: push: pull_request: workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref || github.run_id }} cancel-in-progress: true jobs: ubuntu: runs-on: ubuntu-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - name: checkout uses: actions/checkout@v4 - name: build and run run: cd test && make test_benchmark && ./test_benchmark macos: runs-on: macos-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - name: checkout uses: actions/checkout@v4 - name: build and run run: cd test && make test_benchmark && ./test_benchmark windows: runs-on: windows-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - name: Prepare Git for Checkout on Windows run: | git config --global core.autocrlf false git config --global core.eol lf - name: checkout uses: actions/checkout@v4 - name: Export GitHub Actions cache environment variables uses: actions/github-script@v7 with: script: | core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - name: Cache vcpkg packages id: vcpkg-cache uses: actions/cache@v4 with: path: C:/vcpkg/installed key: vcpkg-installed-windows-gtest - name: Install vcpkg dependencies if: steps.vcpkg-cache.outputs.cache-hit != 'true' run: vcpkg install gtest - name: Configure and build shell: pwsh run: | $cmake_content = @" cmake_minimum_required(VERSION 3.14) project(httplib-benchmark CXX) find_package(GTest REQUIRED) add_executable(httplib-benchmark test/test_benchmark.cc) target_include_directories(httplib-benchmark PRIVATE .) target_link_libraries(httplib-benchmark PRIVATE GTest::gtest_main) target_compile_options(httplib-benchmark PRIVATE "$<$:/utf-8>") "@ New-Item -ItemType Directory -Force -Path build_bench/test | Out-Null Set-Content -Path build_bench/CMakeLists.txt -Value $cmake_content Copy-Item -Path httplib.h -Destination build_bench/ Copy-Item -Path test/test_benchmark.cc -Destination build_bench/test/ cmake -B build_bench/build -S build_bench ` -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" cmake --build build_bench/build --config Release - name: Run with retry run: ctest --output-on-failure --test-dir build_bench/build -C Release --repeat until-pass:5 env: VCPKG_ROOT: "C:/vcpkg" VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" ================================================ FILE: .github/workflows/test_no_exceptions.yaml ================================================ name: No Exceptions Test on: [push, pull_request] jobs: test-no-exceptions: runs-on: ubuntu-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - uses: actions/checkout@v3 - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y build-essential libssl-dev zlib1g-dev libcurl4-openssl-dev libbrotli-dev libzstd-dev - name: Run tests with CPPHTTPLIB_NO_EXCEPTIONS run: | cd test && make test_split EXTRA_CXXFLAGS="-fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS" && make test_openssl_parallel EXTRA_CXXFLAGS="-fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS" ================================================ FILE: .github/workflows/test_offline.yaml ================================================ name: test_offline on: push: pull_request: workflow_dispatch: inputs: test_linux: description: 'Test on Linux' type: boolean default: true concurrency: group: ${{ github.workflow }}-${{ github.ref || github.run_id }} cancel-in-progress: true env: GTEST_FILTER: "-*.*_Online" jobs: ubuntu: runs-on: ubuntu-latest if: > (github.event_name == 'push') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) || (github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true') strategy: matrix: tls_backend: [openssl, no-tls] name: ubuntu (${{ matrix.tls_backend }}) 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 OpenSSL if: matrix.tls_backend == 'openssl' run: sudo apt-get install -y libssl-dev - name: disable network run: | sudo iptables -A OUTPUT -o lo -j ACCEPT sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT sudo iptables -A OUTPUT -j REJECT sudo ip6tables -A OUTPUT -o lo -j ACCEPT sudo ip6tables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT sudo ip6tables -A OUTPUT -j REJECT - name: build and run tests (OpenSSL) if: matrix.tls_backend == 'openssl' run: cd test && make test_split && make test_openssl_parallel env: LSAN_OPTIONS: suppressions=lsan_suppressions.txt - name: build and run tests (No TLS) if: matrix.tls_backend == 'no-tls' run: cd test && make test_no_tls_parallel - name: restore network if: always() run: | sudo iptables -F OUTPUT sudo ip6tables -F OUTPUT ================================================ FILE: .github/workflows/test_proxy.yaml ================================================ name: Proxy Test on: [push, pull_request] jobs: test-proxy: runs-on: ubuntu-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name strategy: matrix: tls_backend: [openssl, mbedtls] name: proxy (${{ matrix.tls_backend }}) steps: - uses: actions/checkout@v4 - name: Install common dependencies run: | sudo apt-get update sudo apt-get install -y build-essential zlib1g-dev libcurl4-openssl-dev libbrotli-dev libzstd-dev netcat-openbsd - name: Install OpenSSL if: matrix.tls_backend == 'openssl' run: sudo apt-get install -y libssl-dev - name: Install Mbed TLS if: matrix.tls_backend == 'mbedtls' run: sudo apt-get install -y libmbedtls-dev - name: Run proxy tests (OpenSSL) if: matrix.tls_backend == 'openssl' run: cd test && make proxy env: COMPOSE_FILE: docker-compose.yml:docker-compose.ci.yml - name: Run proxy tests (Mbed TLS) if: matrix.tls_backend == 'mbedtls' run: cd test && make proxy_mbedtls env: COMPOSE_FILE: docker-compose.yml:docker-compose.ci.yml ================================================ FILE: CMakeLists.txt ================================================ #[[ Build options: * Standard BUILD_SHARED_LIBS is supported and sets HTTPLIB_SHARED default value. * HTTPLIB_USE_OPENSSL_IF_AVAILABLE (default on) * HTTPLIB_USE_WOLFSSL_IF_AVAILABLE (default off) * HTTPLIB_USE_MBEDTLS_IF_AVAILABLE (default off) * HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on) * HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on) * HTTPLIB_USE_ZSTD_IF_AVAILABLE (default on) * HTTPLIB_BUILD_MODULES (default off) * HTTPLIB_REQUIRE_OPENSSL (default off) * HTTPLIB_REQUIRE_WOLFSSL (default off) * HTTPLIB_REQUIRE_MBEDTLS (default off) * HTTPLIB_REQUIRE_ZLIB (default off) * HTTPLIB_REQUIRE_BROTLI (default off) * HTTPLIB_REQUIRE_ZSTD (default off) * HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES (default off) * HTTPLIB_USE_NON_BLOCKING_GETADDRINFO (default on) * HTTPLIB_COMPILE (default off) * HTTPLIB_INSTALL (default on) * HTTPLIB_SHARED (default off) builds as a shared library (if HTTPLIB_COMPILE is ON) * HTTPLIB_TEST (default off) * BROTLI_USE_STATIC_LIBS - tells Cmake to use the static Brotli libs (only works if you have them installed). * OPENSSL_USE_STATIC_LIBS - tells Cmake to use the static OpenSSL libs (only works if you have them installed). ------------------------------------------------------------------------------- After installation with Cmake, a find_package(httplib COMPONENTS OpenSSL wolfssl MbedTLS ZLIB Brotli zstd) is available. This creates a httplib::httplib target (if found and if listed components are supported). It can be linked like so: target_link_libraries(your_exe httplib::httplib) The following will build & install for later use. Linux/macOS: mkdir -p build cd build cmake -DCMAKE_BUILD_TYPE=Release .. sudo cmake --build . --target install Windows: mkdir build cd build cmake .. runas /user:Administrator "cmake --build . --config Release --target install" ------------------------------------------------------------------------------- These variables are available after you run find_package(httplib) * HTTPLIB_HEADER_PATH - this is the full path to the installed header (e.g. /usr/include/httplib.h). * HTTPLIB_IS_USING_OPENSSL - a bool for if OpenSSL support is enabled. * HTTPLIB_IS_USING_WOLFSSL - a bool for if wolfSSL support is enabled. * HTTPLIB_IS_USING_MBEDTLS - a bool for if MbedTLS support is enabled. * HTTPLIB_IS_USING_ZLIB - a bool for if ZLIB support is enabled. * HTTPLIB_IS_USING_BROTLI - a bool for if Brotli support is enabled. * HTTPLIB_IS_USING_ZSTD - a bool for if ZSTD support is enabled. * HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES - a bool for if support of loading system certs from the Apple Keychain is enabled. * HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO - a bool for if nonblocking getaddrinfo is enabled. * HTTPLIB_IS_COMPILED - a bool for if the library is compiled, or otherwise header-only. * HTTPLIB_INCLUDE_DIR - the root path to httplib's header (e.g. /usr/include). * HTTPLIB_LIBRARY - the full path to the library if compiled (e.g. /usr/lib/libhttplib.so). * httplib_VERSION or HTTPLIB_VERSION - the project's version string. * HTTPLIB_FOUND - a bool for if the target was found. Want to use precompiled headers (Cmake feature since v3.16)? It's as simple as doing the following (before linking): target_precompile_headers(httplib::httplib INTERFACE "${HTTPLIB_HEADER_PATH}") ------------------------------------------------------------------------------- ARCH_INDEPENDENT option of write_basic_package_version_file() requires Cmake v3.14 ]] cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR) # Get the CPPHTTPLIB_VERSION value and use it as a version # This gets the string with the CPPHTTPLIB_VERSION value from the header. # This is so the maintainer doesn't actually need to update this manually. file(STRINGS httplib.h _raw_version_string REGEX "CPPHTTPLIB_VERSION \"([0-9]+\\.[0-9]+\\.[0-9]+)\"") # Extracts just the version string itself from the whole string contained in _raw_version_string # since _raw_version_string would contain the entire line of code where it found the version string string(REGEX MATCH "([0-9]+\\.?)+" _httplib_version "${_raw_version_string}") project(httplib VERSION ${_httplib_version} LANGUAGES CXX DESCRIPTION "A C++ header-only HTTP/HTTPS server and client library." HOMEPAGE_URL "https://github.com/yhirose/cpp-httplib" ) # Change as needed to set an OpenSSL minimum version. # This is used in the installed Cmake config file. set(_HTTPLIB_OPENSSL_MIN_VER "3.0.0") # Lets you disable C++ exception during CMake configure time. # The value is used in the install CMake config file. option(HTTPLIB_NO_EXCEPTIONS "Disable the use of C++ exceptions" OFF) # Allow for a build to require OpenSSL to pass, instead of just being optional option(HTTPLIB_REQUIRE_OPENSSL "Requires OpenSSL to be found & linked, or fails build." OFF) option(HTTPLIB_REQUIRE_WOLFSSL "Requires wolfSSL to be found & linked, or fails build." OFF) option(HTTPLIB_REQUIRE_MBEDTLS "Requires MbedTLS to be found & linked, or fails build." OFF) option(HTTPLIB_REQUIRE_ZLIB "Requires ZLIB to be found & linked, or fails build." OFF) # Allow for a build to casually enable OpenSSL/ZLIB support, but silently continue if not found. # Make these options so their automatic use can be specifically disabled (as needed) option(HTTPLIB_USE_OPENSSL_IF_AVAILABLE "Uses OpenSSL (if available) to enable HTTPS support." ON) option(HTTPLIB_USE_WOLFSSL_IF_AVAILABLE "Uses wolfSSL (if available) to enable HTTPS support." OFF) option(HTTPLIB_USE_MBEDTLS_IF_AVAILABLE "Uses MbedTLS (if available) to enable HTTPS support." OFF) option(HTTPLIB_USE_ZLIB_IF_AVAILABLE "Uses ZLIB (if available) to enable Zlib compression support." ON) # Lets you compile the program as a regular library instead of header-only option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a compilable header & source file (requires Python v3)." OFF) # Lets you disable the installation (useful when fetched from another CMake project) option(HTTPLIB_INSTALL "Enables the installation target" ON) option(HTTPLIB_TEST "Enables testing and builds tests" OFF) option(HTTPLIB_REQUIRE_BROTLI "Requires Brotli to be found & linked, or fails build." OFF) option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON) option(HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES "Disable loading system certs from the Apple Keychain on macOS." OFF) option(HTTPLIB_USE_NON_BLOCKING_GETADDRINFO "Enables the non-blocking alternatives for getaddrinfo." ON) option(HTTPLIB_REQUIRE_ZSTD "Requires ZSTD to be found & linked, or fails build." OFF) option(HTTPLIB_USE_ZSTD_IF_AVAILABLE "Uses ZSTD (if available) to enable zstd support." ON) # C++20 modules support requires CMake 3.28 or later if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.28") option(HTTPLIB_BUILD_MODULES "Build httplib modules (requires HTTPLIB_COMPILE to be ON)." OFF) else() set(HTTPLIB_BUILD_MODULES OFF CACHE INTERNAL "Build httplib modules disabled (requires CMake 3.28+)" FORCE) if(DEFINED CACHE{HTTPLIB_BUILD_MODULES} AND HTTPLIB_BUILD_MODULES) message(WARNING "HTTPLIB_BUILD_MODULES requires CMake 3.28 or later. Current version is ${CMAKE_VERSION}. Modules support has been disabled.") endif() endif() # Incompatibility between TLS libraries set(TLS_LIBRARIES_USED_TMP 0) foreach(tls_library OPENSSL WOLFSSL MBEDTLS) set(TLS_REQUIRED ${HTTPLIB_REQUIRE_${tls_library}}) set(TLS_IF_AVAILABLE ${HTTPLIB_USE_${tls_library}_IF_AVAILABLE}) if(TLS_REQUIRED OR TLS_IF_AVAILABLE) math(EXPR TLS_LIBRARIES_USED_TMP "${TLS_LIBRARIES_USED_TMP} + 1") endif() endforeach() if(TLS_LIBRARIES_USED_TMP GREATER 1) message(FATAL_ERROR "TLS libraries are mutually exclusive.") endif() # Defaults to static library but respects standard BUILD_SHARED_LIBS if set include(CMakeDependentOption) cmake_dependent_option(HTTPLIB_SHARED "Build the library as a shared library instead of static. Has no effect if using header-only." "${BUILD_SHARED_LIBS}" HTTPLIB_COMPILE OFF ) if(HTTPLIB_SHARED) set(HTTPLIB_LIB_TYPE SHARED) if(WIN32) # Necessary for Windows if building shared libs # See https://stackoverflow.com/a/40743080 set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) endif() else() set(HTTPLIB_LIB_TYPE STATIC) endif() if(CMAKE_SYSTEM_NAME MATCHES "Windows") if(CMAKE_SYSTEM_VERSION) if(${CMAKE_SYSTEM_VERSION} VERSION_LESS "10.0.0") message(WARNING "Windows ${CMAKE_SYSTEM_VERSION} or lower is not supported. Please use Windows 10 or later.") endif() else() set(CMAKE_SYSTEM_VERSION "10.0.19041.0") message(WARNING "The target is Windows but CMAKE_SYSTEM_VERSION is not set, the default system version is set to Windows 10.") endif() endif() # Set some variables that are used in-tree and while building based on our options set(HTTPLIB_IS_COMPILED ${HTTPLIB_COMPILE}) set(HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES TRUE) if(HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES) set(HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES FALSE) endif() set(HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO ${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO}) # Threads needed for on some systems, and for on Linux set(THREADS_PREFER_PTHREAD_FLAG TRUE) find_package(Threads REQUIRED) # Since Cmake v3.11, Crypto & SSL became optional when not specified as COMPONENTS. if(HTTPLIB_REQUIRE_OPENSSL) find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL REQUIRED) set(HTTPLIB_IS_USING_OPENSSL TRUE) elseif(HTTPLIB_USE_OPENSSL_IF_AVAILABLE) find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL QUIET) # Avoid a rare circumstance of not finding all components but the end-user did their # own call for OpenSSL, which might trick us into thinking we'd otherwise have what we wanted if (TARGET OpenSSL::SSL AND TARGET OpenSSL::Crypto) set(HTTPLIB_IS_USING_OPENSSL ${OPENSSL_FOUND}) else() set(HTTPLIB_IS_USING_OPENSSL FALSE) endif() endif() if(HTTPLIB_REQUIRE_WOLFSSL) find_package(wolfssl REQUIRED) set(HTTPLIB_IS_USING_WOLFSSL TRUE) elseif(HTTPLIB_USE_WOLFSSL_IF_AVAILABLE) find_package(wolfssl QUIET) set(HTTPLIB_IS_USING_WOLFSSL ${wolfssl_FOUND}) endif() if(HTTPLIB_REQUIRE_MBEDTLS) find_package(MbedTLS REQUIRED) set(HTTPLIB_IS_USING_MBEDTLS TRUE) elseif(HTTPLIB_USE_MBEDTLS_IF_AVAILABLE) find_package(MbedTLS QUIET) set(HTTPLIB_IS_USING_MBEDTLS ${MbedTLS_FOUND}) endif() if(HTTPLIB_REQUIRE_ZLIB) find_package(ZLIB REQUIRED) set(HTTPLIB_IS_USING_ZLIB TRUE) elseif(HTTPLIB_USE_ZLIB_IF_AVAILABLE) find_package(ZLIB QUIET) # FindZLIB doesn't have a ZLIB_FOUND variable, so check the target. if(TARGET ZLIB::ZLIB) set(HTTPLIB_IS_USING_ZLIB TRUE) endif() endif() # Adds our cmake folder to the search path for find_package # This is so we can use our custom FindBrotli.cmake list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") if(HTTPLIB_REQUIRE_BROTLI) find_package(Brotli COMPONENTS encoder decoder common REQUIRED) set(HTTPLIB_IS_USING_BROTLI TRUE) elseif(HTTPLIB_USE_BROTLI_IF_AVAILABLE) find_package(Brotli COMPONENTS encoder decoder common QUIET) set(HTTPLIB_IS_USING_BROTLI ${Brotli_FOUND}) endif() # NOTE: # zstd < 1.5.6 does not provide the CMake imported target `zstd::libzstd`. # Older versions must be consumed via their pkg-config file. if(HTTPLIB_REQUIRE_ZSTD) find_package(zstd 1.5.6 CONFIG) if(NOT zstd_FOUND) find_package(PkgConfig REQUIRED) pkg_check_modules(zstd REQUIRED IMPORTED_TARGET libzstd) add_library(zstd::libzstd ALIAS PkgConfig::zstd) endif() set(HTTPLIB_IS_USING_ZSTD TRUE) elseif(HTTPLIB_USE_ZSTD_IF_AVAILABLE) find_package(zstd 1.5.6 CONFIG QUIET) if(NOT zstd_FOUND) find_package(PkgConfig QUIET) if(PKG_CONFIG_FOUND) pkg_check_modules(zstd QUIET IMPORTED_TARGET libzstd) if(TARGET PkgConfig::zstd) add_library(zstd::libzstd ALIAS PkgConfig::zstd) endif() endif() endif() # Both find_package and PkgConf set a XXX_FOUND var set(HTTPLIB_IS_USING_ZSTD ${zstd_FOUND}) endif() # Used for default, common dirs that the end-user can change (if needed) # like CMAKE_INSTALL_INCLUDEDIR or CMAKE_INSTALL_DATADIR include(GNUInstallDirs) if(HTTPLIB_COMPILE) # Put the split script into the build dir configure_file(split.py "${CMAKE_CURRENT_BINARY_DIR}/split.py" COPYONLY ) # Needs to be in the same dir as the python script configure_file(httplib.h "${CMAKE_CURRENT_BINARY_DIR}/httplib.h" COPYONLY ) # Used outside of this if-else set(_INTERFACE_OR_PUBLIC PUBLIC) # Brings in the Python3_EXECUTABLE path we can use. find_package(Python3 REQUIRED) # Actually split the file # Keeps the output in the build dir to not pollute the main dir execute_process(COMMAND ${Python3_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/split.py" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ERROR_VARIABLE _httplib_split_error ) if(_httplib_split_error) message(FATAL_ERROR "Failed when trying to split cpp-httplib with the Python script.\n${_httplib_split_error}") endif() # If building modules, also generate the module file if(HTTPLIB_BUILD_MODULES) # Put the generate_module script into the build dir configure_file(generate_module.py "${CMAKE_CURRENT_BINARY_DIR}/generate_module.py" COPYONLY ) # Generate the module file execute_process(COMMAND ${Python3_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/generate_module.py" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ERROR_VARIABLE _httplib_module_error ) if(_httplib_module_error) message(FATAL_ERROR "Failed when trying to generate cpp-httplib module with the Python script.\n${_httplib_module_error}") endif() endif() # split.py puts output in "out" set(_httplib_build_includedir "${CMAKE_CURRENT_BINARY_DIR}/out") add_library(${PROJECT_NAME} ${HTTPLIB_LIB_TYPE} "${_httplib_build_includedir}/httplib.cc") target_sources(${PROJECT_NAME} PUBLIC $ $ ) # Add C++20 module support if requested # Include from separate file to prevent parse errors on older CMake versions if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.28") include(cmake/modules.cmake) endif() set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${${PROJECT_NAME}_VERSION} SOVERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}" OUTPUT_NAME cpp-httplib ) else() # This is for header-only. set(_INTERFACE_OR_PUBLIC INTERFACE) add_library(${PROJECT_NAME} INTERFACE) set(_httplib_build_includedir "${CMAKE_CURRENT_SOURCE_DIR}") endif() # Lets you address the target with httplib::httplib # Only useful if building in-tree, versus using it from an installation. add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) # Require C++11, or C++20 if modules are enabled if(HTTPLIB_BUILD_MODULES) target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_20) else() target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_11) endif() target_include_directories(${PROJECT_NAME} SYSTEM ${_INTERFACE_OR_PUBLIC} $ $ ) target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} # Always require threads Threads::Threads # Needed for Windows libs on Mingw, as the pragma comment(lib, "xyz") aren't triggered. $<$:ws2_32> $<$:crypt32> # Needed for API from MacOS Security framework "$<$,$,$>:-framework CFNetwork -framework CoreFoundation -framework Security>" # Needed for non-blocking getaddrinfo on MacOS "$<$,$>:-framework CFNetwork -framework CoreFoundation>" # Can't put multiple targets in a single generator expression or it bugs out. $<$:Brotli::common> $<$:Brotli::encoder> $<$:Brotli::decoder> $<$:ZLIB::ZLIB> $<$:zstd::libzstd> $<$:OpenSSL::SSL> $<$:OpenSSL::Crypto> $<$:wolfssl::wolfssl> $<$:MbedTLS::mbedtls> ) # Set the definitions to enable optional features target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} $<$:CPPHTTPLIB_NO_EXCEPTIONS> $<$:CPPHTTPLIB_BROTLI_SUPPORT> $<$:CPPHTTPLIB_ZLIB_SUPPORT> $<$:CPPHTTPLIB_ZSTD_SUPPORT> $<$:CPPHTTPLIB_OPENSSL_SUPPORT> $<$:CPPHTTPLIB_WOLFSSL_SUPPORT> $<$:CPPHTTPLIB_MBEDTLS_SUPPORT> $<$,$>:CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES> $<$:CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO> ) # CMake configuration files installation directory set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") include(CMakePackageConfigHelpers) # Configures the meta-file httplibConfig.cmake.in to replace variables with paths/values/etc. configure_package_config_file("cmake/${PROJECT_NAME}Config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" INSTALL_DESTINATION "${_TARGET_INSTALL_CMAKEDIR}" # Passes the includedir install path PATH_VARS CMAKE_INSTALL_FULL_INCLUDEDIR ) if(HTTPLIB_COMPILE) write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake" # Example: if you find_package(httplib 0.5.4) # then anything >= 0.5.4 and < 0.6 is accepted COMPATIBILITY SameMinorVersion ) else() write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake" # Example: if you find_package(httplib 0.5.4) # then anything >= 0.5.4 and < 0.6 is accepted COMPATIBILITY SameMinorVersion # Tells Cmake that it's a header-only lib # Mildly useful for end-users :) ARCH_INDEPENDENT ) endif() if(HTTPLIB_INSTALL) # Creates the export httplibTargets.cmake # This is strictly what holds compilation requirements # and linkage information (doesn't find deps though). if(HTTPLIB_BUILD_MODULES) install(TARGETS ${PROJECT_NAME} EXPORT httplibTargets FILE_SET CXX_MODULES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/httplib/modules CXX_MODULES_BMI DESTINATION ${CMAKE_INSTALL_LIBDIR}/httplib/modules) else() install(TARGETS ${PROJECT_NAME} EXPORT httplibTargets) endif() install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" # Install it so it can be used later by the httplibConfig.cmake file. # Put it in the same dir as our config file instead of a global path so we don't potentially stomp on other packages. "${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindBrotli.cmake" DESTINATION ${_TARGET_INSTALL_CMAKEDIR} ) # NOTE: This path changes depending on if it's on Windows or Linux install(EXPORT httplibTargets # Puts the targets into the httplib namespace # So this makes httplib::httplib linkable after doing find_package(httplib) NAMESPACE ${PROJECT_NAME}:: DESTINATION ${_TARGET_INSTALL_CMAKEDIR} ) # Install documentation & license # ex: /usr/share/doc/httplib/README.md and /usr/share/licenses/httplib/LICENSE install(FILES "README.md" DESTINATION "${CMAKE_INSTALL_DOCDIR}") install(FILES "LICENSE" DESTINATION "${CMAKE_INSTALL_DATADIR}/licenses/${PROJECT_NAME}") include(CPack) endif() if(HTTPLIB_BUILD_MODULES AND NOT HTTPLIB_COMPILE) message(FATAL_ERROR "HTTPLIB_BUILD_MODULES requires HTTPLIB_COMPILE to be ON.") endif() if(HTTPLIB_TEST) include(CTest) add_subdirectory(test) endif() ================================================ FILE: Dockerfile ================================================ FROM yhirose4dockerhub/ubuntu-builder AS builder WORKDIR /build COPY httplib.h . COPY docker/main.cc . RUN g++ -std=c++23 -static -o server -O2 -I. main.cc && strip server FROM scratch COPY --from=builder /build/server /server COPY docker/html/index.html /html/index.html EXPOSE 80 ENTRYPOINT ["/server"] CMD ["--host", "0.0.0.0", "--port", "80", "--mount", "/:./html"] ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2017 yhirose Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README-sse.md ================================================ # SSEClient - Server-Sent Events Client A simple, EventSource-like SSE client for C++11. ## Features - **Auto-reconnect**: Automatically reconnects on connection loss - **Last-Event-ID**: Sends last received ID on reconnect for resumption - **retry field**: Respects server's reconnect interval - **Event types**: Supports custom event types via `on_event()` - **Async support**: Run in background thread with `start_async()` - **C++11 compatible**: No C++14/17/20 features required ## Quick Start ```cpp httplib::Client cli("http://localhost:8080"); httplib::sse::SSEClient sse(cli, "/events"); sse.on_message([](const httplib::sse::SSEMessage &msg) { std::cout << "Event: " << msg.event << std::endl; std::cout << "Data: " << msg.data << std::endl; }); sse.start(); // Blocking, with auto-reconnect ``` ## API Reference ### SSEMessage ```cpp struct SSEMessage { std::string event; // Event type (default: "message") std::string data; // Event payload std::string id; // Event ID }; ``` ### SSEClient #### Constructor ```cpp // Basic SSEClient(Client &client, const std::string &path); // With custom headers SSEClient(Client &client, const std::string &path, const Headers &headers); ``` #### Event Handlers ```cpp // Called for all events (or events without a specific handler) sse.on_message([](const SSEMessage &msg) { }); // Called for specific event types sse.on_event("update", [](const SSEMessage &msg) { }); sse.on_event("delete", [](const SSEMessage &msg) { }); // Called when connection is established sse.on_open([]() { }); // Called on connection errors sse.on_error([](httplib::Error err) { }); ``` #### Configuration ```cpp // Set reconnect interval (default: 3000ms) sse.set_reconnect_interval(5000); // Set max reconnect attempts (default: 0 = unlimited) sse.set_max_reconnect_attempts(10); ``` #### Control ```cpp // Blocking start with auto-reconnect sse.start(); // Non-blocking start (runs in background thread) sse.start_async(); // Stop the client (thread-safe) sse.stop(); ``` #### State ```cpp bool connected = sse.is_connected(); const std::string &id = sse.last_event_id(); ``` ## Examples ### Basic Usage ```cpp httplib::Client cli("http://localhost:8080"); httplib::sse::SSEClient sse(cli, "/events"); sse.on_message([](const httplib::sse::SSEMessage &msg) { std::cout << msg.data << std::endl; }); sse.start(); ``` ### With Custom Event Types ```cpp httplib::sse::SSEClient sse(cli, "/events"); sse.on_event("notification", [](const httplib::sse::SSEMessage &msg) { std::cout << "Notification: " << msg.data << std::endl; }); sse.on_event("update", [](const httplib::sse::SSEMessage &msg) { std::cout << "Update: " << msg.data << std::endl; }); sse.start(); ``` ### Async with Stop ```cpp httplib::sse::SSEClient sse(cli, "/events"); sse.on_message([](const httplib::sse::SSEMessage &msg) { std::cout << msg.data << std::endl; }); sse.start_async(); // Returns immediately // ... do other work ... sse.stop(); // Stop when done ``` ### With Custom Headers (e.g., Authentication) ```cpp httplib::Headers headers = { {"Authorization", "Bearer token123"} }; httplib::sse::SSEClient sse(cli, "/events", headers); sse.start(); ``` ### Error Handling ```cpp sse.on_error([](httplib::Error err) { std::cerr << "Error: " << httplib::to_string(err) << std::endl; }); sse.set_reconnect_interval(1000); sse.set_max_reconnect_attempts(5); sse.start(); ``` ## SSE Protocol The client parses SSE format according to the [W3C specification](https://html.spec.whatwg.org/multipage/server-sent-events.html): ``` event: custom-type id: 123 data: {"message": "hello"} data: simple message : this is a comment (ignored) ``` ================================================ FILE: README-stream.md ================================================ # cpp-httplib Streaming API This document describes the streaming extensions for cpp-httplib, providing an iterator-style API for handling HTTP responses incrementally with **true socket-level streaming**. > **Important Notes**: > > - **No Keep-Alive**: Each `stream::Get()` call uses a dedicated connection that is closed after the response is fully read. For connection reuse, use `Client::Get()`. > - **Single iteration only**: The `next()` method can only iterate through the body once. > - **Result is not thread-safe**: While `stream::Get()` can be called from multiple threads simultaneously, the returned `stream::Result` must be used from a single thread only. ## Overview The streaming API allows you to process HTTP response bodies chunk by chunk using an iterator-style pattern. Data is read directly from the network socket, enabling low-memory processing of large responses. This is particularly useful for: - **LLM/AI streaming responses** (e.g., ChatGPT, Claude, Ollama) - **Server-Sent Events (SSE)** - **Large file downloads** with progress tracking - **Reverse proxy implementations** ## Quick Start ```cpp #include "httplib.h" int main() { httplib::Client cli("http://localhost:8080"); // Get streaming response auto result = httplib::stream::Get(cli, "/stream"); if (result) { // Process response body in chunks while (result.next()) { std::cout.write(result.data(), result.size()); } } return 0; } ``` ## API Layers cpp-httplib provides multiple API layers for different use cases: ```text ┌─────────────────────────────────────────────┐ │ SSEClient │ ← SSE-specific, parsed events │ - on_message(), on_event() │ │ - Auto-reconnect, Last-Event-ID │ ├─────────────────────────────────────────────┤ │ stream::Get() / stream::Result │ ← Iterator-based streaming │ - while (result.next()) { ... } │ ├─────────────────────────────────────────────┤ │ open_stream() / StreamHandle │ ← General-purpose streaming │ - handle.read(buf, len) │ ├─────────────────────────────────────────────┤ │ Client::Get() │ ← Traditional, full buffering └─────────────────────────────────────────────┘ ``` | Use Case | Recommended API | |----------|----------------| | SSE with auto-reconnect | `SSEClient` (see [README-sse.md](README-sse.md)) | | LLM streaming (JSON Lines) | `stream::Get()` | | Large file download | `stream::Get()` or `open_stream()` | | Reverse proxy | `open_stream()` | | Small responses with Keep-Alive | `Client::Get()` | ## API Reference ### Low-Level API: `StreamHandle` The `StreamHandle` struct provides direct control over streaming responses. It takes ownership of the socket connection and reads data directly from the network. > **Note:** When using `open_stream()`, the connection is dedicated to streaming and **Keep-Alive is not supported**. For Keep-Alive connections, use `client.Get()` instead. ```cpp // Open a stream (takes ownership of socket) httplib::Client cli("http://localhost:8080"); auto handle = cli.open_stream("GET", "/path"); // Check validity if (handle.is_valid()) { // Access response headers immediately int status = handle.response->status; auto content_type = handle.response->get_header_value("Content-Type"); // Read body incrementally char buf[4096]; ssize_t n; while ((n = handle.read(buf, sizeof(buf))) > 0) { process(buf, n); } } ``` #### StreamHandle Members | Member | Type | Description | |--------|------|-------------| | `response` | `std::unique_ptr` | HTTP response with headers | | `error` | `Error` | Error code if request failed | | `is_valid()` | `bool` | Returns true if response is valid | | `read(buf, len)` | `ssize_t` | Read up to `len` bytes directly from socket | | `get_read_error()` | `Error` | Get the last read error | | `has_read_error()` | `bool` | Check if a read error occurred | ### High-Level API: `stream::Get()` and `stream::Result` The `httplib.h` header provides a more ergonomic iterator-style API. ```cpp #include "httplib.h" httplib::Client cli("http://localhost:8080"); cli.set_follow_location(true); ... // Simple GET auto result = httplib::stream::Get(cli, "/path"); // GET with custom headers httplib::Headers headers = {{"Authorization", "Bearer token"}}; auto result = httplib::stream::Get(cli, "/path", headers); // Process the response if (result) { while (result.next()) { process(result.data(), result.size()); } } // Or read entire body at once auto result2 = httplib::stream::Get(cli, "/path"); if (result2) { std::string body = result2.read_all(); } ``` #### stream::Result Members | Member | Type | Description | |--------|------|-------------| | `operator bool()` | `bool` | Returns true if response is valid | | `is_valid()` | `bool` | Same as `operator bool()` | | `status()` | `int` | HTTP status code | | `headers()` | `const Headers&` | Response headers | | `get_header_value(key, def)` | `std::string` | Get header value (with optional default) | | `has_header(key)` | `bool` | Check if header exists | | `next()` | `bool` | Read next chunk, returns false when done | | `data()` | `const char*` | Pointer to current chunk data | | `size()` | `size_t` | Size of current chunk | | `read_all()` | `std::string` | Read entire remaining body into string | | `error()` | `Error` | Get the connection/request error | | `read_error()` | `Error` | Get the last read error | | `has_read_error()` | `bool` | Check if a read error occurred | ## Usage Examples ### Example 1: SSE (Server-Sent Events) Client ```cpp #include "httplib.h" #include int main() { httplib::Client cli("http://localhost:1234"); auto result = httplib::stream::Get(cli, "/events"); if (!result) { return 1; } while (result.next()) { std::cout.write(result.data(), result.size()); std::cout.flush(); } return 0; } ``` For a complete SSE client with auto-reconnection and event parsing, see `example/ssecli-stream.cc`. ### Example 2: LLM Streaming Response ```cpp #include "httplib.h" #include int main() { httplib::Client cli("http://localhost:11434"); // Ollama auto result = httplib::stream::Get(cli, "/api/generate"); if (result && result.status() == 200) { while (result.next()) { std::cout.write(result.data(), result.size()); std::cout.flush(); } } // Check for connection errors if (result.read_error() != httplib::Error::Success) { std::cerr << "Connection lost\n"; } return 0; } ``` ### Example 3: Large File Download with Progress ```cpp #include "httplib.h" #include #include int main() { httplib::Client cli("http://example.com"); auto result = httplib::stream::Get(cli, "/large-file.zip"); if (!result || result.status() != 200) { std::cerr << "Download failed\n"; return 1; } std::ofstream file("download.zip", std::ios::binary); size_t total = 0; while (result.next()) { file.write(result.data(), result.size()); total += result.size(); std::cout << "\rDownloaded: " << (total / 1024) << " KB" << std::flush; } std::cout << "\nComplete!\n"; return 0; } ``` ### Example 4: Reverse Proxy Streaming ```cpp #include "httplib.h" httplib::Server svr; svr.Get("/proxy/(.*)", [](const httplib::Request& req, httplib::Response& res) { httplib::Client upstream("http://backend:8080"); auto handle = upstream.open_stream("/" + req.matches[1].str()); if (!handle.is_valid()) { res.status = 502; return; } res.status = handle.response->status; res.set_chunked_content_provider( handle.response->get_header_value("Content-Type"), [handle = std::move(handle)](size_t, httplib::DataSink& sink) mutable { char buf[8192]; auto n = handle.read(buf, sizeof(buf)); if (n > 0) { sink.write(buf, static_cast(n)); return true; } sink.done(); return true; } ); }); svr.listen("0.0.0.0", 3000); ``` ## Comparison with Existing APIs | Feature | `Client::Get()` | `open_stream()` | `stream::Get()` | |---------|----------------|-----------------|----------------| | Headers available | After complete | Immediately | Immediately | | Body reading | All at once | Direct from socket | Iterator-based | | Memory usage | Full body in RAM | Minimal (controlled) | Minimal (controlled) | | Keep-Alive support | ✅ Yes | ❌ No | ❌ No | | Compression | Auto-handled | Auto-handled | Auto-handled | | Best for | Small responses, Keep-Alive | Low-level streaming | Easy streaming | ## Features - **True socket-level streaming**: Data is read directly from the network socket - **Low memory footprint**: Only the current chunk is held in memory - **Compression support**: Automatic decompression for gzip, brotli, and zstd - **Chunked transfer**: Full support for chunked transfer encoding - **SSL/TLS support**: Works with HTTPS connections ## Important Notes ### Keep-Alive Behavior The streaming API (`stream::Get()` / `open_stream()`) takes ownership of the socket connection for the duration of the stream. This means: - **Keep-Alive is not supported** for streaming connections - The socket is closed when `StreamHandle` is destroyed - For Keep-Alive scenarios, use the standard `client.Get()` API instead ```cpp // Use for streaming (no Keep-Alive) auto result = httplib::stream::Get(cli, "/large-stream"); while (result.next()) { /* ... */ } // Use for Keep-Alive connections auto res = cli.Get("/api/data"); // Connection can be reused ``` ## Related - [Issue #2269](https://github.com/yhirose/cpp-httplib/issues/2269) - Original feature request - [example/ssecli-stream.cc](./example/ssecli-stream.cc) - SSE client with auto-reconnection ================================================ FILE: README-websocket.md ================================================ # WebSocket - RFC 6455 WebSocket Support A simple, blocking WebSocket implementation for C++11. > [!IMPORTANT] > This is a blocking I/O WebSocket implementation using a thread-per-connection model. If you need high-concurrency WebSocket support with non-blocking/async I/O (e.g., thousands of simultaneous connections), this is not the one that you want. ## Features - **RFC 6455 compliant**: Full WebSocket protocol support - **Server and Client**: Both sides included - **SSL/TLS support**: `wss://` scheme for secure connections - **Text and Binary**: Both message types supported - **Automatic heartbeat**: Periodic Ping/Pong keeps connections alive - **Subprotocol negotiation**: `Sec-WebSocket-Protocol` support for GraphQL, MQTT, etc. ## Quick Start ### Server ```cpp httplib::Server svr; svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) { std::string msg; while (ws.read(msg)) { ws.send("echo: " + msg); } }); svr.listen("localhost", 8080); ``` ### Client ```cpp httplib::ws::WebSocketClient ws("ws://localhost:8080/ws"); if (ws.connect()) { ws.send("hello"); std::string msg; if (ws.read(msg)) { std::cout << msg << std::endl; // "echo: hello" } ws.close(); } ``` ## API Reference ### ReadResult ```cpp enum ReadResult : int { Fail = 0, // Connection closed or error Text = 1, // UTF-8 text message Binary = 2, // Binary message }; ``` Returned by `read()`. Since `Fail` is `0`, the result works naturally in boolean contexts — `while (ws.read(msg))` continues until the connection closes. When you need to distinguish text from binary, check the return value directly. ### CloseStatus ```cpp enum class CloseStatus : uint16_t { Normal = 1000, GoingAway = 1001, ProtocolError = 1002, UnsupportedData = 1003, NoStatus = 1005, Abnormal = 1006, InvalidPayload = 1007, PolicyViolation = 1008, MessageTooBig = 1009, MandatoryExtension = 1010, InternalError = 1011, }; ``` ### Server Registration ```cpp // Basic handler Server &WebSocket(const std::string &pattern, WebSocketHandler handler); // With subprotocol negotiation Server &WebSocket(const std::string &pattern, WebSocketHandler handler, SubProtocolSelector sub_protocol_selector); ``` **Type aliases:** ```cpp using WebSocketHandler = std::function; using SubProtocolSelector = std::function &protocols)>; ``` The `SubProtocolSelector` receives the list of subprotocols proposed by the client (from the `Sec-WebSocket-Protocol` header) and returns the selected one. Return an empty string to decline all proposed subprotocols. ### WebSocket (Server-side) Passed to the handler registered with `Server::WebSocket()`. The handler runs in a dedicated thread per connection. ```cpp // Read next message (blocks until received, returns Fail/Text/Binary) ReadResult read(std::string &msg); // Send messages bool send(const std::string &data); // Text bool send(const char *data, size_t len); // Binary // Close the connection void close(CloseStatus status = CloseStatus::Normal, const std::string &reason = ""); // Access the original HTTP upgrade request const Request &request() const; // Check if the connection is still open bool is_open() const; ``` ### WebSocketClient ```cpp // Constructor - accepts ws:// or wss:// URL explicit WebSocketClient(const std::string &scheme_host_port_path, const Headers &headers = {}); // Check if the URL was parsed successfully bool is_valid() const; // Connect (performs HTTP upgrade handshake) bool connect(); // Get the subprotocol selected by the server (empty if none) const std::string &subprotocol() const; // Read/Send/Close (same as server-side WebSocket) ReadResult read(std::string &msg); bool send(const std::string &data); bool send(const char *data, size_t len); void close(CloseStatus status = CloseStatus::Normal, const std::string &reason = ""); bool is_open() const; // Timeouts void set_read_timeout(time_t sec, time_t usec = 0); void set_write_timeout(time_t sec, time_t usec = 0); // SSL configuration (wss:// only, requires CPPHTTPLIB_OPENSSL_SUPPORT) void set_ca_cert_path(const std::string &path); void set_ca_cert_store(tls::ca_store_t store); void enable_server_certificate_verification(bool enabled); ``` ## Examples ### Echo Server with Connection Logging ```cpp httplib::Server svr; svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) { std::cout << "Connected from " << req.remote_addr << std::endl; std::string msg; while (ws.read(msg)) { ws.send("echo: " + msg); } std::cout << "Disconnected" << std::endl; }); svr.listen("localhost", 8080); ``` ### Client: Continuous Read Loop ```cpp httplib::ws::WebSocketClient ws("ws://localhost:8080/ws"); if (ws.connect()) { ws.send("hello"); ws.send("world"); std::string msg; while (ws.read(msg)) { // blocks until a message arrives std::cout << msg << std::endl; // "echo: hello", "echo: world" } // read() returns false when the server closes the connection } ``` ### Text and Binary Messages Check the `ReadResult` return value to distinguish between text and binary: ```cpp // Server svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) { std::string msg; httplib::ws::ReadResult ret; while ((ret = ws.read(msg))) { if (ret == httplib::ws::Text) { ws.send("echo: " + msg); } else { ws.send(msg.data(), msg.size()); // Binary echo } } }); // Client httplib::ws::WebSocketClient ws("ws://localhost:8080/ws"); if (ws.connect()) { // Send binary data const char binary[] = {0x00, 0x01, 0x02, 0x03}; ws.send(binary, sizeof(binary)); // Receive and check the type std::string msg; if (ws.read(msg) == httplib::ws::Binary) { // Process binary data in msg } ws.close(); } ``` ### SSL Client ```cpp httplib::ws::WebSocketClient ws("wss://echo.example.com/ws"); if (ws.connect()) { ws.send("hello over TLS"); std::string msg; if (ws.read(msg)) { std::cout << msg << std::endl; } ws.close(); } ``` ### Close with Status ```cpp // Client-side: close with a specific status code and reason ws.close(httplib::ws::CloseStatus::GoingAway, "shutting down"); // Server-side: close with a policy violation status ws.close(httplib::ws::CloseStatus::PolicyViolation, "forbidden"); ``` ### Accessing the Upgrade Request ```cpp svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) { // Access headers from the original HTTP upgrade request auto auth = req.get_header_value("Authorization"); if (auth.empty()) { ws.close(httplib::ws::CloseStatus::PolicyViolation, "unauthorized"); return; } std::string msg; while (ws.read(msg)) { ws.send("echo: " + msg); } }); ``` ### Custom Headers and Timeouts ```cpp httplib::Headers headers = { {"Authorization", "Bearer token123"} }; httplib::ws::WebSocketClient ws("ws://localhost:8080/ws", headers); ws.set_read_timeout(30, 0); // 30 seconds ws.set_write_timeout(10, 0); // 10 seconds if (ws.connect()) { std::string msg; while (ws.read(msg)) { std::cout << msg << std::endl; } } ``` ### Subprotocol Negotiation The server can negotiate a subprotocol with the client using `Sec-WebSocket-Protocol`. This is required for protocols like GraphQL over WebSocket (`graphql-ws`) and MQTT. ```cpp // Server: register a handler with a subprotocol selector svr.WebSocket( "/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) { std::string msg; while (ws.read(msg)) { ws.send("echo: " + msg); } }, [](const std::vector &protocols) -> std::string { // The client proposed a list of subprotocols; pick one for (const auto &p : protocols) { if (p == "graphql-ws" || p == "graphql-transport-ws") { return p; } } return ""; // Decline all }); // Client: propose subprotocols via Sec-WebSocket-Protocol header httplib::Headers headers = { {"Sec-WebSocket-Protocol", "graphql-ws, graphql-transport-ws"} }; httplib::ws::WebSocketClient ws("ws://localhost:8080/ws", headers); if (ws.connect()) { // Check which subprotocol the server selected std::cout << "Subprotocol: " << ws.subprotocol() << std::endl; // => "graphql-ws" ws.close(); } ``` ### SSL Client with Certificate Configuration ```cpp httplib::ws::WebSocketClient ws("wss://example.com/ws"); ws.set_ca_cert_path("/path/to/ca-bundle.crt"); ws.enable_server_certificate_verification(true); if (ws.connect()) { ws.send("secure message"); ws.close(); } ``` ## Configuration | Macro | Default | Description | |---------------------------------------------|-------------------|----------------------------------------------------------| | `CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH` | `16777216` (16MB) | Maximum payload size per message | | `CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND` | `300` | Read timeout for WebSocket connections (seconds) | | `CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND` | `5` | Timeout for waiting peer's Close response (seconds) | | `CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND` | `30` | Automatic Ping interval for heartbeat (seconds) | ### Runtime Ping Interval You can override the ping interval at runtime instead of changing the compile-time macro. Set it to `0` to disable automatic pings entirely. ```cpp // Server side httplib::Server svr; svr.set_websocket_ping_interval(10); // 10 seconds // Or using std::chrono svr.set_websocket_ping_interval(std::chrono::seconds(10)); // Client side httplib::ws::WebSocketClient ws("ws://localhost:8080/ws"); ws.set_websocket_ping_interval(10); // 10 seconds // Disable automatic pings ws.set_websocket_ping_interval(0); ``` ## Threading Model WebSocket connections share the same thread pool as HTTP requests. Each WebSocket connection occupies one thread for its entire lifetime. The default thread pool uses dynamic scaling: it maintains a base thread count of `CPPHTTPLIB_THREAD_POOL_COUNT` (8 or `std::thread::hardware_concurrency() - 1`, whichever is greater) and can scale up to 4x that count under load (`CPPHTTPLIB_THREAD_POOL_MAX_COUNT`). When all base threads are busy, temporary threads are spawned automatically up to the maximum. These dynamic threads exit after an idle timeout (`CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT`, default 3 seconds). This dynamic scaling helps accommodate WebSocket connections alongside HTTP requests. However, if you expect many simultaneous WebSocket connections, you should configure the thread pool accordingly: ```cpp httplib::Server svr; svr.new_task_queue = [] { return new httplib::ThreadPool(/*base_threads=*/8, /*max_threads=*/128); }; ``` Choose sizes that account for both your expected HTTP load and the maximum number of simultaneous WebSocket connections. ## Protocol The implementation follows [RFC 6455](https://tools.ietf.org/html/rfc6455): - Handshake via HTTP Upgrade with `Sec-WebSocket-Key` / `Sec-WebSocket-Accept` - Subprotocol negotiation via `Sec-WebSocket-Protocol` - Frame masking (client-to-server) - Control frames: Close, Ping, Pong - Message fragmentation and reassembly - Close handshake with status codes ## Browser Test Run the echo server example and open `http://localhost:8080` in a browser: ```bash cd example && make wsecho && ./wsecho ``` ================================================ FILE: README.md ================================================ # cpp-httplib [![](https://github.com/yhirose/cpp-httplib/workflows/test/badge.svg)](https://github.com/yhirose/cpp-httplib/actions) A C++11 single-file header-only cross platform HTTP/HTTPS library.
It's extremely easy to set up. Just include the **[httplib.h](https://raw.githubusercontent.com/yhirose/cpp-httplib/refs/heads/master/httplib.h)** file in your code! Learn more in the [official documentation](https://yhirose.github.io/cpp-httplib/) (built with [docs-gen](https://github.com/yhirose/docs-gen)). > [!IMPORTANT] > This library uses 'blocking' socket I/O. If you are looking for a library with 'non-blocking' socket I/O, this is not the one that you want. > [!WARNING] > 32-bit platforms are **NOT supported**. Use at your own risk. The library may compile on 32-bit targets, but no security review has been conducted for 32-bit environments. Integer truncation and other 32-bit-specific issues may exist. **Security reports that only affect 32-bit platforms will be closed without action.** The maintainer does not have access to 32-bit environments for testing or fixing issues. CI includes basic compile checks only, not functional or security testing. ## Main Features - HTTP Server/Client - SSL/TLS support (OpenSSL, MbedTLS, wolfSSL) - [Stream API](README-stream.md) - [Server-Sent Events](README-sse.md) - [WebSocket](README-websocket.md) ## Simple examples ### Server ```c++ #define CPPHTTPLIB_OPENSSL_SUPPORT #include "path/to/httplib.h" // HTTP httplib::Server svr; // HTTPS httplib::SSLServer svr; svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) { res.set_content("Hello World!", "text/plain"); }); svr.listen("0.0.0.0", 8080); ``` ### Client ```c++ #define CPPHTTPLIB_OPENSSL_SUPPORT #include "path/to/httplib.h" // HTTP httplib::Client cli("http://yhirose.github.io"); // HTTPS httplib::Client cli("https://yhirose.github.io"); if (auto res = cli.Get("/hi")) { res->status; res->body; } ``` ## SSL/TLS Support cpp-httplib supports multiple TLS backends through an abstraction layer: | Backend | Define | Libraries | Notes | | :------ | :----- | :-------- | :---- | | 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) | | wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | `libwolfssl` | 5.x supported; must build with `--enable-opensslall` | > [!NOTE] > **Mbed TLS / wolfSSL limitation:** `get_ca_certs()` and `get_ca_names()` only reflect CA certificates loaded via `load_ca_cert_store()`. Certificates loaded through `set_ca_cert_path()` or system certificates (`load_system_certs`) are not enumerable. ```c++ // Use either OpenSSL, Mbed TLS, or wolfSSL #define CPPHTTPLIB_OPENSSL_SUPPORT // or CPPHTTPLIB_MBEDTLS_SUPPORT or CPPHTTPLIB_WOLFSSL_SUPPORT #include "path/to/httplib.h" // Server httplib::SSLServer svr("./cert.pem", "./key.pem"); // Client httplib::Client cli("https://localhost:1234"); // scheme + host httplib::SSLClient cli("localhost:1234"); // host httplib::SSLClient cli("localhost", 1234); // host, port // Use your CA bundle cli.set_ca_cert_path("./ca-bundle.crt"); // Disable cert verification cli.enable_server_certificate_verification(false); // Disable host verification cli.enable_server_hostname_verification(false); ``` ### SSL Error Handling When SSL operations fail, cpp-httplib provides detailed error information through `ssl_error()` and `ssl_backend_error()`: - `ssl_error()` - Returns the TLS-level error code (e.g., `SSL_ERROR_SSL` for OpenSSL) - `ssl_backend_error()` - Returns the backend-specific error code (e.g., `ERR_get_error()` for OpenSSL/wolfSSL, return value for Mbed TLS) ```c++ #define CPPHTTPLIB_OPENSSL_SUPPORT // or CPPHTTPLIB_MBEDTLS_SUPPORT or CPPHTTPLIB_WOLFSSL_SUPPORT #include "path/to/httplib.h" httplib::Client cli("https://example.com"); auto res = cli.Get("/"); if (!res) { // Check the error type const auto err = res.error(); switch (err) { case httplib::Error::SSLConnection: std::cout << "SSL connection failed, SSL error: " << res.ssl_error() << std::endl; break; case httplib::Error::SSLLoadingCerts: std::cout << "SSL cert loading failed, backend error: " << std::hex << res.ssl_backend_error() << std::endl; break; case httplib::Error::SSLServerVerification: std::cout << "SSL verification failed, verify error: " << res.ssl_backend_error() << std::endl; break; case httplib::Error::SSLServerHostnameVerification: std::cout << "SSL hostname verification failed, verify error: " << res.ssl_backend_error() << std::endl; break; default: std::cout << "HTTP error: " << httplib::to_string(err) << std::endl; } } ``` ### Custom Certificate Verification You can set a custom verification callback using `tls::VerifyCallback`: ```c++ httplib::Client cli("https://example.com"); cli.set_server_certificate_verifier( [](const httplib::tls::VerifyContext &ctx) -> bool { std::cout << "Subject CN: " << ctx.subject_cn() << std::endl; std::cout << "Issuer: " << ctx.issuer_name() << std::endl; std::cout << "Depth: " << ctx.depth << std::endl; std::cout << "Pre-verified: " << ctx.preverify_ok << std::endl; // Inspect SANs (Subject Alternative Names) for (const auto &san : ctx.sans()) { std::cout << "SAN: " << san.value << std::endl; } // Return true to accept, false to reject return ctx.preverify_ok; }); ``` ### Peer Certificate Inspection On the server side, you can inspect the client's peer certificate from a request handler: ```c++ httplib::SSLServer svr("./cert.pem", "./key.pem", "./client-ca-cert.pem"); svr.Get("/", [](const httplib::Request &req, httplib::Response &res) { auto cert = req.peer_cert(); if (cert) { std::cout << "Client CN: " << cert.subject_cn() << std::endl; std::cout << "Serial: " << cert.serial() << std::endl; } auto sni = req.sni(); std::cout << "SNI: " << sni << std::endl; }); ``` ### Platform-specific Certificate Handling cpp-httplib automatically integrates with the OS certificate store on macOS and Windows. This works with all TLS backends. | Platform | Behavior | Disable (compile time) | | :------- | :------- | :--------------------- | | macOS | Loads system certs from Keychain (link `CoreFoundation` and `Security` with `-framework`) | `CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES` | | Windows | Verifies certs via CryptoAPI (`CertGetCertificateChain` / `CertVerifyCertificateChainPolicy`) with revocation checking | `CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE` | On Windows, verification can also be disabled at runtime: ```c++ cli.enable_windows_certificate_verification(false); ``` > [!NOTE] > When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself. ## Server ```c++ #include int main(void) { using namespace httplib; Server svr; svr.Get("/hi", [](const Request& req, Response& res) { res.set_content("Hello World!", "text/plain"); }); // Match the request path against a regular expression // and extract its captures svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) { auto numbers = req.matches[1]; res.set_content(numbers, "text/plain"); }); // Capture the second segment of the request path as "id" path param svr.Get("/users/:id", [&](const Request& req, Response& res) { auto user_id = req.path_params.at("id"); res.set_content(user_id, "text/plain"); }); // Extract values from HTTP headers and URL query params svr.Get("/body-header-param", [](const Request& req, Response& res) { if (req.has_header("Content-Length")) { auto val = req.get_header_value("Content-Length"); } if (req.has_param("key")) { auto val = req.get_param_value("key"); } res.set_content(req.body, "text/plain"); }); // If the handler takes time to finish, you can also poll the connection state svr.Get("/task", [&](const Request& req, Response& res) { const char * result = nullptr; process.run(); // for example, starting an external process while (result == nullptr) { sleep(1); if (req.is_connection_closed()) { process.kill(); // kill the process return; } result = process.stdout(); // != nullptr if the process finishes } res.set_content(result, "text/plain"); }); svr.Get("/stop", [&](const Request& req, Response& res) { svr.stop(); }); svr.listen("localhost", 1234); } ``` `Post`, `Put`, `Patch`, `Delete` and `Options` methods are also supported. ### Bind a socket to multiple interfaces and any available port ```cpp int port = svr.bind_to_any_port("0.0.0.0"); svr.listen_after_bind(); ``` ### Static File Server ```cpp // Mount / to ./www directory auto ret = svr.set_mount_point("/", "./www"); if (!ret) { // The specified base directory doesn't exist... } // Mount /public to ./www directory ret = svr.set_mount_point("/public", "./www"); // Mount /public to ./www1 and ./www2 directories ret = svr.set_mount_point("/public", "./www1"); // 1st order to search ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search // Remove mount / ret = svr.remove_mount_point("/"); // Remove mount /public ret = svr.remove_mount_point("/public"); ``` ```cpp // User defined file extension and MIME type mappings svr.set_file_extension_and_mimetype_mapping("cc", "text/x-c"); svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c"); svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h"); ``` The following are built-in mappings: | Extension | MIME Type | Extension | MIME Type | | :--------- | :-------------------------- | :--------- | :-------------------------- | | css | text/css | mpga | audio/mpeg | | csv | text/csv | weba | audio/webm | | txt | text/plain | wav | audio/wave | | vtt | text/vtt | otf | font/otf | | html, htm | text/html | ttf | font/ttf | | apng | image/apng | woff | font/woff | | avif | image/avif | woff2 | font/woff2 | | bmp | image/bmp | 7z | application/x-7z-compressed | | gif | image/gif | atom | application/atom+xml | | png | image/png | pdf | application/pdf | | svg | image/svg+xml | mjs, js | text/javascript | | webp | image/webp | json | application/json | | ico | image/x-icon | rss | application/rss+xml | | tif | image/tiff | tar | application/x-tar | | tiff | image/tiff | xhtml, xht | application/xhtml+xml | | jpeg, jpg | image/jpeg | xslt | application/xslt+xml | | mp4 | video/mp4 | xml | application/xml | | mpeg | video/mpeg | gz | application/gzip | | webm | video/webm | zip | application/zip | | mp3 | audio/mp3 | wasm | application/wasm | > [!WARNING] > These static file server methods are not thread-safe. > [!NOTE] > On POSIX systems, the static file server rejects requests that resolve (via symlinks) to a path outside the mounted base directory. Ensure that the served directory has appropriate permissions, as managing access to the served directory is the application developer's responsibility. ### File request handler ```cpp // The handler is called right before the response is sent to a client svr.set_file_request_handler([](const Request &req, Response &res) { ... }); ``` ### Logging cpp-httplib provides separate logging capabilities for access logs and error logs, similar to web servers like Nginx and Apache. #### Access Logging Access loggers capture successful HTTP requests and responses: ```cpp svr.set_logger([](const httplib::Request& req, const httplib::Response& res) { std::cout << req.method << " " << req.path << " -> " << res.status << std::endl; }); ``` #### Pre-compression Logging You can also set a pre-compression logger to capture request/response data before compression is applied: ```cpp svr.set_pre_compression_logger([](const httplib::Request& req, const httplib::Response& res) { // Log before compression - res.body contains uncompressed content // Content-Encoding header is not yet set your_pre_compression_logger(req, res); }); ``` The pre-compression logger is only called when compression would be applied. For responses without compression, only the access logger is called. #### Error Logging Error loggers capture failed requests and connection issues. Unlike access loggers, error loggers only receive the Error and Request information, as errors typically occur before a meaningful Response can be generated. ```cpp svr.set_error_logger([](const httplib::Error& err, const httplib::Request* req) { std::cerr << httplib::to_string(err) << " while processing request"; if (req) { std::cerr << ", client: " << req->get_header_value("X-Forwarded-For") << ", request: '" << req->method << " " << req->path << " " << req->version << "'" << ", host: " << req->get_header_value("Host"); } std::cerr << std::endl; }); ``` ### Error handler ```cpp svr.set_error_handler([](const auto& req, auto& res) { auto fmt = "

Error Status: %d

"; char buf[BUFSIZ]; snprintf(buf, sizeof(buf), fmt, res.status); res.set_content(buf, "text/html"); }); ``` ### Exception handler The exception handler gets called if a user routing handler throws an error. ```cpp svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep) { auto fmt = "

Error 500

%s

"; char buf[BUFSIZ]; try { std::rethrow_exception(ep); } catch (std::exception &e) { snprintf(buf, sizeof(buf), fmt, e.what()); } catch (...) { // See the following NOTE snprintf(buf, sizeof(buf), fmt, "Unknown Exception"); } res.set_content(buf, "text/html"); res.status = StatusCode::InternalServerError_500; }); ``` > [!CAUTION] > if you don't provide the `catch (...)` block for a rethrown exception pointer, an uncaught exception will end up causing the server crash. Be careful! ### Pre routing handler ```cpp svr.set_pre_routing_handler([](const auto& req, auto& res) { if (req.path == "/hello") { res.set_content("world", "text/html"); return Server::HandlerResponse::Handled; } return Server::HandlerResponse::Unhandled; }); ``` ### Post routing handler ```cpp svr.set_post_routing_handler([](const auto& req, auto& res) { res.set_header("ADDITIONAL_HEADER", "value"); }); ``` ### Pre request handler ```cpp svr.set_pre_request_handler([](const auto& req, auto& res) { if (req.matched_route == "/user/:user") { auto user = req.path_params.at("user"); if (user != "john") { res.status = StatusCode::Forbidden_403; res.set_content("error", "text/html"); return Server::HandlerResponse::Handled; } } return Server::HandlerResponse::Unhandled; }); ``` ### Response user data `res.user_data` is a `std::map` that lets pre-routing or pre-request handlers pass arbitrary data to route handlers. ```cpp struct AuthContext { std::string user_id; std::string role; }; svr.set_pre_routing_handler([](const auto& req, auto& res) { auto token = req.get_header_value("Authorization"); res.user_data["auth"] = AuthContext{decode_token(token)}; return Server::HandlerResponse::Unhandled; }); svr.Get("/me", [](const auto& /*req*/, auto& res) { auto* ctx = httplib::any_cast(&res.user_data["auth"]); if (!ctx) { res.status = StatusCode::Unauthorized_401; return; } res.set_content("Hello " + ctx->user_id, "text/plain"); }); ``` `httplib::any` mirrors the C++17 `std::any` API. On C++17 and later it is an alias for `std::any`; on C++11/14 a compatible implementation is provided. ### Form data handling #### URL-encoded form data ('application/x-www-form-urlencoded') ```cpp svr.Post("/form", [&](const auto& req, auto& res) { // URL query parameters and form-encoded data are accessible via req.params std::string username = req.get_param_value("username"); std::string password = req.get_param_value("password"); // Handle multiple values with same name auto interests = req.get_param_values("interests"); // Check existence if (req.has_param("newsletter")) { // Handle newsletter subscription } }); ``` #### 'multipart/form-data' POST data ```cpp svr.Post("/multipart", [&](const Request& req, Response& res) { // Access text fields (from form inputs without files) std::string username = req.form.get_field("username"); std::string bio = req.form.get_field("bio"); // Access uploaded files if (req.form.has_file("avatar")) { const auto& file = req.form.get_file("avatar"); std::cout << "Uploaded file: " << file.filename << " (" << file.content_type << ") - " << file.content.size() << " bytes" << std::endl; // Access additional headers if needed for (const auto& header : file.headers) { std::cout << "Header: " << header.first << " = " << header.second << std::endl; } // IMPORTANT: file.filename is an untrusted value from the client. // Always sanitize to prevent path traversal attacks. auto safe_name = httplib::sanitize_filename(file.filename); if (safe_name.empty()) { res.status = StatusCode::BadRequest_400; res.set_content("Invalid filename", "text/plain"); return; } // Save to disk std::ofstream ofs(upload_dir + "/" + safe_name, std::ios::binary); ofs << file.content; } // Handle multiple values with same name auto tags = req.form.get_fields("tags"); // e.g., multiple checkboxes for (const auto& tag : tags) { std::cout << "Tag: " << tag << std::endl; } auto documents = req.form.get_files("documents"); // multiple file upload for (const auto& doc : documents) { std::cout << "Document: " << doc.filename << " (" << doc.content.size() << " bytes)" << std::endl; } // Check existence before accessing if (req.form.has_field("newsletter")) { std::cout << "Newsletter subscription: " << req.form.get_field("newsletter") << std::endl; } // Get counts for validation if (req.form.get_field_count("tags") > 5) { res.status = StatusCode::BadRequest_400; res.set_content("Too many tags", "text/plain"); return; } // Summary std::cout << "Received " << req.form.fields.size() << " text fields and " << req.form.files.size() << " files" << std::endl; res.set_content("Upload successful", "text/plain"); }); ``` #### Filename Sanitization `file.filename` in multipart uploads is an untrusted value from the client. Always sanitize before using it in file paths: ```cpp auto safe = httplib::sanitize_filename(file.filename); ``` This function strips path separators (`/`, `\`), null bytes, leading/trailing whitespace, and rejects `.` and `..`. Returns an empty string if the filename is unsafe. ### Receive content with a content receiver ```cpp svr.Post("/content_receiver", [&](const Request &req, Response &res, const ContentReader &content_reader) { if (req.is_multipart_form_data()) { // NOTE: `content_reader` is blocking until every form data field is read // This approach allows streaming processing of large files std::vector items; content_reader( [&](const FormData &item) { items.push_back(item); return true; }, [&](const char *data, size_t data_length) { items.back().content.append(data, data_length); return true; }); // Process the received items for (const auto& item : items) { if (item.filename.empty()) { // Text field std::cout << "Field: " << item.name << " = " << item.content << std::endl; } else { // File std::cout << "File: " << item.name << " (" << item.filename << ") - " << item.content.size() << " bytes" << std::endl; } } } else { std::string body; content_reader([&](const char *data, size_t data_length) { body.append(data, data_length); return true; }); } }); ``` ### Send content with the content provider ```cpp const size_t DATA_CHUNK_SIZE = 4; svr.Get("/stream", [&](const Request &req, Response &res) { auto data = new std::string("abcdefg"); res.set_content_provider( data->size(), // Content length "text/plain", // Content type [&, data](size_t offset, size_t length, DataSink &sink) { const auto &d = *data; sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE)); return true; // return 'false' if you want to cancel the process. }, [data](bool success) { delete data; }); }); ``` Without content length: ```cpp svr.Get("/stream", [&](const Request &req, Response &res) { res.set_content_provider( "text/plain", // Content type [&](size_t offset, DataSink &sink) { if (/* there is still data */) { std::vector data; // prepare data... sink.write(data.data(), data.size()); } else { sink.done(); // No more data } return true; // return 'false' if you want to cancel the process. }); }); ``` ### Chunked transfer encoding ```cpp svr.Get("/chunked", [&](const Request& req, Response& res) { res.set_chunked_content_provider( "text/plain", [](size_t offset, DataSink &sink) { sink.write("123", 3); sink.write("345", 3); sink.write("789", 3); sink.done(); // No more data return true; // return 'false' if you want to cancel the process. } ); }); ``` With trailer: ```cpp svr.Get("/chunked", [&](const Request& req, Response& res) { res.set_header("Trailer", "Dummy1, Dummy2"); res.set_chunked_content_provider( "text/plain", [](size_t offset, DataSink &sink) { sink.write("123", 3); sink.write("345", 3); sink.write("789", 3); sink.done_with_trailer({ {"Dummy1", "DummyVal1"}, {"Dummy2", "DummyVal2"} }); return true; } ); }); ``` ### Send file content ```cpp svr.Get("/content", [&](const Request &req, Response &res) { res.set_file_content("./path/to/content.html"); }); svr.Get("/content", [&](const Request &req, Response &res) { res.set_file_content("./path/to/content", "text/html"); }); ``` ### 'Expect: 100-continue' handler By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header. ```cpp // Send a '417 Expectation Failed' response. svr.set_expect_100_continue_handler([](const Request &req, Response &res) { return StatusCode::ExpectationFailed_417; }); ``` ```cpp // Send a final status without reading the message body. svr.set_expect_100_continue_handler([](const Request &req, Response &res) { return res.status = StatusCode::Unauthorized_401; }); ``` ### Keep-Alive connection ```cpp svr.set_keep_alive_max_count(2); // Default is 100 svr.set_keep_alive_timeout(10); // Default is 5 ``` ### Timeout ```c++ svr.set_read_timeout(5, 0); // 5 seconds svr.set_write_timeout(5, 0); // 5 seconds svr.set_idle_interval(0, 100000); // 100 milliseconds ``` ### Set maximum payload length for reading a request body ```c++ svr.set_payload_max_length(1024 * 1024 * 512); // 512MB ``` > [!NOTE] > When the request body content type is 'www-form-urlencoded', the actual payload length shouldn't exceed `CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH`. ### Server-Sent Events Please see [Server example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssesvr.cc) and [Client example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssecli.cc). ### Default thread pool support `ThreadPool` is used as the **default** task queue, with dynamic scaling support. By default, it maintains a base thread count of 8 or `std::thread::hardware_concurrency() - 1` (whichever is greater), and can scale up to 4x that count under load. You can change these with `CPPHTTPLIB_THREAD_POOL_COUNT` and `CPPHTTPLIB_THREAD_POOL_MAX_COUNT`. When all threads are busy and a new task arrives, a temporary thread is spawned (up to the maximum). When a dynamic thread finishes its task and the queue is empty, or after an idle timeout, it exits automatically. The idle timeout defaults to 3 seconds, configurable via `CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT`. If you want to set the thread counts at runtime: ```cpp svr.new_task_queue = [] { return new ThreadPool(/*base_threads=*/8, /*max_threads=*/64); }; ``` #### Max queued requests You can also provide an optional parameter to limit the maximum number of pending requests, i.e. requests `accept()`ed by the listener but still waiting to be serviced by worker threads. ```cpp svr.new_task_queue = [] { return new ThreadPool(/*base_threads=*/12, /*max_threads=*/0, /*max_queued_requests=*/18); }; ``` Default limit is 0 (unlimited). Once the limit is reached, the listener will shutdown the client connection. ### Override the default thread pool with yours You can supply your own thread pool implementation according to your need. ```cpp class YourThreadPoolTaskQueue : public TaskQueue { public: YourThreadPoolTaskQueue(size_t n) { pool_.start_with_thread_count(n); } virtual bool enqueue(std::function fn) override { /* Return true if the task was actually enqueued, or false * if the caller must drop the corresponding connection. */ return pool_.enqueue(fn); } virtual void shutdown() override { pool_.shutdown_gracefully(); } private: YourThreadPool pool_; }; svr.new_task_queue = [] { return new YourThreadPoolTaskQueue(12); }; ``` ## Client ```c++ #include #include int main(void) { httplib::Client cli("localhost", 1234); if (auto res = cli.Get("/hi")) { if (res->status == StatusCode::OK_200) { std::cout << res->body << std::endl; } } else { auto err = res.error(); std::cout << "HTTP error: " << httplib::to_string(err) << std::endl; } } ``` > [!TIP] > Constructor with scheme-host-port string is now supported! ```c++ httplib::Client cli("localhost"); httplib::Client cli("localhost:8080"); httplib::Client cli("http://localhost"); httplib::Client cli("http://localhost:8080"); httplib::Client cli("https://localhost"); httplib::SSLClient cli("localhost"); ``` ### Error code Here is the list of errors from `Result::error()`. ```c++ enum class Error { Success = 0, Unknown, Connection, BindIPAddress, Read, Write, ExceedRedirectCount, Canceled, SSLConnection, SSLLoadingCerts, SSLServerVerification, SSLServerHostnameVerification, UnsupportedMultipartBoundaryChars, Compression, ConnectionTimeout, ProxyConnection, ConnectionClosed, Timeout, ResourceExhaustion, TooManyFormDataFiles, ExceedMaxPayloadSize, ExceedUriMaxLength, ExceedMaxSocketDescriptorCount, InvalidRequestLine, InvalidHTTPMethod, InvalidHTTPVersion, InvalidHeaders, MultipartParsing, OpenFile, Listen, GetSockName, UnsupportedAddressFamily, HTTPParsing, InvalidRangeHeader, }; ``` ### Client Logging #### Access Logging ```cpp cli.set_logger([](const httplib::Request& req, const httplib::Response& res) { auto duration = std::chrono::duration_cast( std::chrono::steady_clock::now() - start_time).count(); std::cout << "✓ " << req.method << " " << req.path << " -> " << res.status << " (" << res.body.size() << " bytes, " << duration << "ms)" << std::endl; }); ``` #### Error Logging ```cpp cli.set_error_logger([](const httplib::Error& err, const httplib::Request* req) { std::cerr << "✗ "; if (req) { std::cerr << req->method << " " << req->path << " "; } std::cerr << "failed: " << httplib::to_string(err); // Add specific guidance based on error type switch (err) { case httplib::Error::Connection: std::cerr << " (verify server is running and reachable)"; break; case httplib::Error::SSLConnection: std::cerr << " (check SSL certificate and TLS configuration)"; break; case httplib::Error::ConnectionTimeout: std::cerr << " (increase timeout or check network latency)"; break; case httplib::Error::Read: std::cerr << " (server may have closed connection prematurely)"; break; default: break; } std::cerr << std::endl; }); ``` ### GET with HTTP headers ```c++ httplib::Headers headers = { { "Hello", "World!" } }; auto res = cli.Get("/hi", headers); ``` or ```c++ auto res = cli.Get("/hi", {{"Hello", "World!"}}); ``` or ```c++ cli.set_default_headers({ { "Hello", "World!" } }); auto res = cli.Get("/hi"); ``` ### POST ```c++ res = cli.Post("/post", "text", "text/plain"); res = cli.Post("/person", "name=john1¬e=coder", "application/x-www-form-urlencoded"); ``` ### POST with parameters ```c++ httplib::Params params; params.emplace("name", "john"); params.emplace("note", "coder"); auto res = cli.Post("/post", params); ``` or ```c++ httplib::Params params{ { "name", "john" }, { "note", "coder" } }; auto res = cli.Post("/post", params); ``` ### POST with Multipart Form Data ```c++ httplib::UploadFormDataItems items = { { "text1", "text default", "", "" }, { "text2", "aωb", "", "" }, { "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" }, { "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" }, { "file3", "", "", "application/octet-stream" }, }; auto res = cli.Post("/multipart", items); ``` To upload files from disk without loading them entirely into memory, use `make_file_provider`. The file is sent with chunked transfer encoding. ```cpp httplib::FormDataProviderItems providers = { httplib::make_file_provider("file1", "/path/to/large.bin", "large.bin", "application/octet-stream"), httplib::make_file_provider("avatar", "/path/to/photo.jpg", "photo.jpg", "image/jpeg"), }; auto res = cli.Post("/upload", {}, {}, providers); ``` ### POST with a file body To POST a file as a raw binary body with `Content-Length`, use `make_file_body`. ```cpp auto [size, provider] = httplib::make_file_body("/path/to/data.bin"); auto res = cli.Post("/upload", size, provider, "application/octet-stream"); ``` ### PUT ```c++ res = cli.Put("/resource/foo", "text", "text/plain"); ``` ### PATCH ```c++ res = cli.Patch("/resource/foo", "text", "text/plain"); ``` ### DELETE ```c++ res = cli.Delete("/resource/foo"); ``` ### OPTIONS ```c++ res = cli.Options("*"); res = cli.Options("/resource/foo"); ``` ### Timeout ```c++ cli.set_connection_timeout(0, 300000); // 300 milliseconds cli.set_read_timeout(5, 0); // 5 seconds cli.set_write_timeout(5, 0); // 5 seconds // This method works the same as curl's `--max-time` option cli.set_max_timeout(5000); // 5 seconds ``` ### Set maximum payload length for reading a response body ```c++ cli.set_payload_max_length(1024 * 1024 * 512); // 512MB ``` ### Receive content with a content receiver ```c++ std::string body; auto res = cli.Get("/large-data", [&](const char *data, size_t data_length) { body.append(data, data_length); return true; }); ``` ```cpp std::string body; auto res = cli.Get( "/stream", Headers(), [&](const Response &response) { EXPECT_EQ(StatusCode::OK_200, response.status); return true; // return 'false' if you want to cancel the request. }, [&](const char *data, size_t data_length) { body.append(data, data_length); return true; // return 'false' if you want to cancel the request. }); ``` ### Send content with a content provider ```cpp std::string body = ...; auto res = cli.Post( "/stream", body.size(), [](size_t offset, size_t length, DataSink &sink) { sink.write(body.data() + offset, length); return true; // return 'false' if you want to cancel the request. }, "text/plain"); ``` ### Chunked transfer encoding ```cpp auto res = cli.Post( "/stream", [](size_t offset, DataSink &sink) { sink.os << "chunked data 1"; sink.os << "chunked data 2"; sink.os << "chunked data 3"; sink.done(); return true; // return 'false' if you want to cancel the request. }, "text/plain"); ``` ### With Progress Callback ```cpp httplib::Client cli(url, port); // prints: 0 / 000 bytes => 50% complete auto res = cli.Get("/", [](size_t len, size_t total) { printf("%lld / %lld bytes => %d%% complete\n", len, total, (int)(len*100/total)); return true; // return 'false' if you want to cancel the request. } ); ``` ![progress](https://user-images.githubusercontent.com/236374/33138910-495c4ecc-cf86-11e7-8693-2fc6d09615c4.gif) ### Authentication ```cpp // Basic Authentication cli.set_basic_auth("user", "pass"); // Digest Authentication cli.set_digest_auth("user", "pass"); // Bearer Token Authentication cli.set_bearer_token_auth("token"); ``` > [!NOTE] > OpenSSL is required for Digest Authentication. ### Proxy server support ```cpp cli.set_proxy("host", port); // Basic Authentication cli.set_proxy_basic_auth("user", "pass"); // Digest Authentication cli.set_proxy_digest_auth("user", "pass"); // Bearer Token Authentication cli.set_proxy_bearer_token_auth("pass"); ``` > [!NOTE] > OpenSSL is required for Digest Authentication. ### Range ```cpp httplib::Client cli("httpcan.org"); auto res = cli.Get("/range/32", { httplib::make_range_header({{1, 10}}) // 'Range: bytes=1-10' }); // res->status should be 206. // res->body should be "bcdefghijk". ``` ```cpp httplib::make_range_header({{1, 10}, {20, -1}}) // 'Range: bytes=1-10, 20-' httplib::make_range_header({{100, 199}, {500, 599}}) // 'Range: bytes=100-199, 500-599' httplib::make_range_header({{0, 0}, {-1, 1}}) // 'Range: bytes=0-0, -1' ``` ### Keep-Alive connection ```cpp httplib::Client cli("localhost", 1234); cli.Get("/hello"); // with "Connection: close" cli.set_keep_alive(true); cli.Get("/world"); cli.set_keep_alive(false); cli.Get("/last-request"); // with "Connection: close" ``` ### Redirect ```cpp httplib::Client cli("yahoo.com"); auto res = cli.Get("/"); res->status; // 301 cli.set_follow_location(true); res = cli.Get("/"); res->status; // 200 ``` ### Use a specific network interface > [!NOTE] > This feature is not available on Windows, yet. ```cpp cli.set_interface("eth0"); // Interface name, IP address or host name ``` ### Automatic Path Encoding The client automatically encodes special characters in URL paths by default: ```cpp httplib::Client cli("https://example.com"); // Automatic path encoding (default behavior) cli.set_path_encode(true); auto res = cli.Get("/path with spaces/file.txt"); // Automatically encodes spaces // Disable automatic path encoding cli.set_path_encode(false); auto res = cli.Get("/already%20encoded/path"); // Use pre-encoded paths ``` - `set_path_encode(bool on)` - Controls automatic encoding of special characters in URL paths - `true` (default): Automatically encodes spaces, plus signs, newlines, and other special characters - `false`: Sends paths as-is without encoding (useful for pre-encoded URLs) ### Performance Note for Local Connections > [!WARNING] > On Windows systems with improperly configured IPv6 settings, using "localhost" as the hostname may cause significant connection delays (up to 2 seconds per request) due to DNS resolution issues. This affects both client and server operations. For better performance when connecting to local services, use "127.0.0.1" instead of "localhost". > > See: https://github.com/yhirose/cpp-httplib/issues/366#issuecomment-593004264 ```cpp // May be slower on Windows due to DNS resolution delays httplib::Client cli("localhost", 8080); httplib::Server svr; svr.listen("localhost", 8080); // Faster alternative for local connections httplib::Client cli("127.0.0.1", 8080); httplib::Server svr; svr.listen("127.0.0.1", 8080); ``` ## 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. ## Compression The server can apply compression to the following MIME type contents: - all text types except text/event-stream - image/svg+xml - application/javascript - application/json - application/xml - application/protobuf - application/xhtml+xml ### Zlib Support 'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. `libz` should be linked. ### Brotli Support Brotli compression is available with `CPPHTTPLIB_BROTLI_SUPPORT`. Necessary libraries should be linked. Please see https://github.com/google/brotli for more detail. ### Zstd Support Zstd compression is available with `CPPHTTPLIB_ZSTD_SUPPORT`. Necessary libraries should be linked. Please see https://github.com/facebook/zstd for more detail. ### Default `Accept-Encoding` value The default `Accept-Encoding` value contains all possible compression types. So, the following two examples are same. ```c++ res = cli.Get("/resource/foo"); res = cli.Get("/resource/foo", {{"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. ```c++ res = cli.Get("/resource/foo", {{"Accept-Encoding", ""}}); ``` ### Compress request body on client ```c++ cli.set_compress(true); res = cli.Post("/resource/foo", "...", "text/plain"); ``` ### Compress response body on client ```c++ cli.set_decompress(false); res = cli.Get("/resource/foo"); res->body; // Compressed data ``` Unix Domain Socket Support -------------------------- Unix Domain Socket support is available on Linux and macOS. ```c++ // Server httplib::Server svr; svr.set_address_family(AF_UNIX).listen("./my-socket.sock", 80); // Client httplib::Client cli("./my-socket.sock"); cli.set_address_family(AF_UNIX); ``` "my-socket.sock" can be a relative path or an absolute path. Your application must have the appropriate permissions for the path. You can also use an abstract socket address on Linux. To use an abstract socket address, prepend a null byte ('\x00') to the path. This library automatically sets the Host header to "localhost" for Unix socket connections, similar to curl's behavior: URI Encoding/Decoding Utilities ------------------------------- cpp-httplib provides utility functions for URI encoding and decoding: ```cpp #include std::string url = "https://example.com/search?q=hello world"; std::string encoded = httplib::encode_uri(url); std::string decoded = httplib::decode_uri(encoded); std::string param = "hello world"; std::string encoded_component = httplib::encode_uri_component(param); std::string decoded_component = httplib::decode_uri_component(encoded_component); ``` ### Functions - `encode_uri(const std::string &value)` - Encodes a full URI, preserving reserved characters like `://`, `?`, `&`, `=` - `decode_uri(const std::string &value)` - Decodes a URI-encoded string - `encode_uri_component(const std::string &value)` - Encodes a URI component (query parameter, path segment), encoding all reserved characters - `decode_uri_component(const std::string &value)` - Decodes a URI component Use `encode_uri()` for full URLs and `encode_uri_component()` for individual query parameters or path segments. ## Stream API Process large responses without loading everything into memory. ```c++ httplib::Client cli("localhost", 8080); cli.set_follow_location(true); ... auto result = httplib::stream::Get(cli, "/large-file"); if (result) { while (result.next()) { process(result.data(), result.size()); // Process each chunk as it arrives } } // Or read the entire body at once auto result2 = httplib::stream::Get(cli, "/file"); if (result2) { std::string body = result2.read_all(); } ``` All HTTP methods are supported: `stream::Get`, `Post`, `Put`, `Patch`, `Delete`, `Head`, `Options`. See [README-stream.md](README-stream.md) for more details. ## SSE Client ```cpp #include int main() { httplib::Client cli("http://localhost:8080"); httplib::sse::SSEClient sse(cli, "/events"); sse.on_message([](const httplib::sse::SSEMessage &msg) { std::cout << "Event: " << msg.event << std::endl; std::cout << "Data: " << msg.data << std::endl; }); sse.start(); // Blocking, with auto-reconnect return 0; } ``` See [README-sse.md](README-sse.md) for more details. ## WebSocket ```cpp // Server httplib::Server svr; svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) { httplib::ws::Message msg; while (ws.read(msg)) { if (msg.is_text()) { ws.send("Echo: " + msg.data); } } }); svr.listen("localhost", 8080); ``` ```cpp // Client httplib::ws::WebSocketClient ws("ws://localhost:8080/ws"); if (ws.connect()) { ws.send("Hello, WebSocket!"); std::string msg; if (ws.read(msg)) { std::cout << "Received: " << msg << std::endl; } ws.close(); } ``` SSL is also supported via `wss://` scheme (e.g. `WebSocketClient("wss://example.com/ws")`). Subprotocol negotiation (`Sec-WebSocket-Protocol`) is supported via `SubProtocolSelector` callback. > **Note:** WebSocket connections occupy a thread for their entire lifetime. If you plan to handle many simultaneous WebSocket connections, consider using a dynamic thread pool: `svr.new_task_queue = [] { return new ThreadPool(8, 64); };` See [README-websocket.md](README-websocket.md) for more details. ## Split httplib.h into .h and .cc ```console $ ./split.py -h usage: split.py [-h] [-e EXTENSION] [-o OUT] This script splits httplib.h into .h and .cc parts. optional arguments: -h, --help show this help message and exit -e EXTENSION, --extension EXTENSION extension of the implementation file (default: cc) -o OUT, --out OUT where to write the files (default: out) $ ./split.py Wrote out/httplib.h and out/httplib.cc ``` ## Dockerfile for Static HTTP Server Dockerfile for static HTTP server is available. Port number of this HTTP server is 80, and it serves static files from `/html` directory in the container. ```bash > docker build -t cpp-httplib-server . ... > docker run --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server Serving HTTP on 0.0.0.0 port 80 ... 192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1" 192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..." 192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 "-" "Mozilla/5.0 ..." ``` From Docker Hub ```bash > docker run --rm -it -p 8080:80 -v ./docker/html:/html yhirose4dockerhub/cpp-httplib-server Serving HTTP on 0.0.0.0 port 80 ... 192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1" 192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..." 192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 "-" "Mozilla/5.0 ..." ``` NOTE ---- ### Regular Expression Stack Overflow > [!CAUTION] > When using complex regex patterns in route handlers, be aware that certain patterns may cause stack overflow during pattern matching. This is a known issue with `std::regex` implementations and affects the `dispatch_request()` method. > > ```cpp > // This pattern can cause stack overflow with large input > svr.Get(".*", handler); > ``` > > Consider using simpler patterns or path parameters to avoid this issue: > > ```cpp > // Safer alternatives > svr.Get("/users/:id", handler); // Path parameters > svr.Get(R"(/api/v\d+/.*)", handler); // More specific patterns > ``` ### g++ g++ 4.8 and below cannot build this library since `` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions). ### Windows Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32_LEAN_AND_MEAN` beforehand. ```cpp #include #include ``` ```cpp #define WIN32_LEAN_AND_MEAN #include #include ``` > [!NOTE] > cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance. > [!NOTE] > Windows 8 or lower, Visual Studio 2015 or lower, and Cygwin and MSYS2 including MinGW are neither supported nor tested. ## License MIT license (© 2026 Yuji Hirose) ## Special Thanks To [These folks](https://github.com/yhirose/cpp-httplib/graphs/contributors) made great contributions to polish this library to totally another level from a simple toy! ================================================ FILE: benchmark/Makefile ================================================ CXXFLAGS = -O2 -I.. CPPHTTPLIB_CXXFLAGS = -std=c++11 CROW_CXXFLAGS = -std=c++17 CPPHTTPLIB_FLAGS = -DCPPHTTPLIB_THREAD_POOL_COUNT=16 BENCH = bombardier -c 10 -d 5s localhost:8080 MONITOR = ali http://localhost:8080 # cpp-httplib bench: server @echo "--------------------\n cpp-httplib latest\n--------------------\n" @./server & export PID=$$!; $(BENCH); kill $${PID} @echo "" monitor: server @./server & export PID=$$!; $(MONITOR); kill $${PID} run : server @./server server : cpp-httplib/main.cpp ../httplib.h @g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib/main.cpp # crow bench-crow: server-crow @echo "-------------\n Crow v1.3.1\n-------------\n" @./server-crow & export PID=$$!; $(BENCH); kill $${PID} @echo "" monitor-crow: server-crow @./server-crow & export PID=$$!; $(MONITOR); kill $${PID} run-crow : server-crow @./server-crow server-crow : crow/main.cpp crow/crow_all.h @g++ -o $@ $(CXXFLAGS) $(CROW_CXXFLAGS) crow/main.cpp # misc build: server server-crow bench-all: bench-crow bench issue: bombardier -c 10 -d 30s localhost:8080 clean: rm -rf server* ================================================ FILE: benchmark/cpp-httplib/main.cpp ================================================ #include "httplib.h" using namespace httplib; int main() { Server svr; svr.Get("/", [](const Request &, Response &res) { res.set_content("Hello World!", "text/plain"); }); svr.listen("0.0.0.0", 8080); } ================================================ FILE: benchmark/crow/crow_all.h ================================================ // SPDX-License-Identifier: BSD-3-Clause AND ISC AND MIT /*BSD 3-Clause License Copyright (c) 2014-2017, ipkn 2020-2026, CrowCpp All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The Crow logo and other graphic material (excluding third party logos) used are under exclusive Copyright (c) 2021-2022, Farook Al-Sammarraie (The-EDev), All rights reserved. */ #pragma once #ifdef CROW_ENABLE_COMPRESSION #include #include // http://zlib.net/manual.html namespace crow // NOTE: Already documented in "crow/app.h" { namespace compression { // Values used in the 'windowBits' parameter for deflateInit2. enum algorithm { // 15 is the default value for deflate DEFLATE = 15, // windowBits can also be greater than 15 for optional gzip encoding. // Add 16 to windowBits to write a simple gzip header and trailer around the // compressed data instead of a zlib wrapper. GZIP = 15 | 16, }; inline std::string compress_string(std::string const &str, algorithm algo) { std::string compressed_str; z_stream stream{}; // Initialize with the default values if (::deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, algo, 8, Z_DEFAULT_STRATEGY) == Z_OK) { char buffer[8192]; stream.avail_in = str.size(); // zlib does not take a const pointer. The data is not altered. stream.next_in = const_cast(reinterpret_cast(str.c_str())); int code = Z_OK; do { stream.avail_out = sizeof(buffer); stream.next_out = reinterpret_cast(&buffer[0]); code = ::deflate(&stream, Z_FINISH); // Successful and non-fatal error code returned by deflate when used with // Z_FINISH flush if (code == Z_OK || code == Z_STREAM_END) { std::copy(&buffer[0], &buffer[sizeof(buffer) - stream.avail_out], std::back_inserter(compressed_str)); } } while (code == Z_OK); if (code != Z_STREAM_END) compressed_str.clear(); ::deflateEnd(&stream); } return compressed_str; } inline std::string decompress_string(std::string const &deflated_string) { std::string inflated_string; Bytef tmp[8192]; z_stream zstream{}; zstream.avail_in = deflated_string.size(); // Nasty const_cast but zlib won't alter its contents zstream.next_in = const_cast( reinterpret_cast(deflated_string.c_str())); // Initialize with automatic header detection, for gzip support if (::inflateInit2(&zstream, MAX_WBITS | 32) == Z_OK) { do { zstream.avail_out = sizeof(tmp); zstream.next_out = &tmp[0]; auto ret = ::inflate(&zstream, Z_NO_FLUSH); if (ret == Z_OK || ret == Z_STREAM_END) { std::copy(&tmp[0], &tmp[sizeof(tmp) - zstream.avail_out], std::back_inserter(inflated_string)); } else { // Something went wrong with inflate; make sure we return an empty // string inflated_string.clear(); break; } } while (zstream.avail_out == 0); // Free zlib's internal memory ::inflateEnd(&zstream); } return inflated_string; } } // namespace compression } // namespace crow #endif namespace crow { constexpr const char VERSION[] = "master"; } /* * SHA1 Wikipedia Page: http://en.wikipedia.org/wiki/SHA-1 * * Copyright (c) 2012-22 SAURAV MOHAPATRA * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /** * \file TinySHA1.hpp * \author SAURAV MOHAPATRA * \date 2012-22 * \brief TinySHA1 - a header only implementation of the SHA1 algorithm in C++. * Based on the implementation in boost::uuid::details. * * In this file are defined: * - sha1::SHA1 */ #ifndef _TINY_SHA1_HPP_ #define _TINY_SHA1_HPP_ #include #include #include #include /** * \namespace sha1 * \brief Here is defined the SHA1 class */ namespace sha1 { /** * \class SHA1 * \brief A tiny SHA1 algorithm implementation used internally in the * Crow server (specifically in crow/websocket.h). */ class SHA1 { public: typedef uint32_t digest32_t[5]; typedef uint8_t digest8_t[20]; inline static uint32_t LeftRotate(uint32_t value, size_t count) { return (value << count) ^ (value >> (32 - count)); } SHA1() { reset(); } virtual ~SHA1() {} SHA1(const SHA1 &s) { *this = s; } const SHA1 &operator=(const SHA1 &s) { memcpy(m_digest, s.m_digest, 5 * sizeof(uint32_t)); memcpy(m_block, s.m_block, 64); m_blockByteIndex = s.m_blockByteIndex; m_byteCount = s.m_byteCount; return *this; } SHA1 &reset() { m_digest[0] = 0x67452301; m_digest[1] = 0xEFCDAB89; m_digest[2] = 0x98BADCFE; m_digest[3] = 0x10325476; m_digest[4] = 0xC3D2E1F0; m_blockByteIndex = 0; m_byteCount = 0; return *this; } SHA1 &processByte(uint8_t octet) { this->m_block[this->m_blockByteIndex++] = octet; ++this->m_byteCount; if (m_blockByteIndex == 64) { this->m_blockByteIndex = 0; processBlock(); } return *this; } SHA1 &processBlock(const void *const start, const void *const end) { const uint8_t *begin = static_cast(start); const uint8_t *finish = static_cast(end); while (begin != finish) { processByte(*begin); begin++; } return *this; } SHA1 &processBytes(const void *const data, size_t len) { const uint8_t *block = static_cast(data); processBlock(block, block + len); return *this; } const uint32_t *getDigest(digest32_t digest) { size_t bitCount = this->m_byteCount * 8; processByte(0x80); if (this->m_blockByteIndex > 56) { while (m_blockByteIndex != 0) { processByte(0); } while (m_blockByteIndex < 56) { processByte(0); } } else { while (m_blockByteIndex < 56) { processByte(0); } } processByte(0); processByte(0); processByte(0); processByte(0); processByte(static_cast((bitCount >> 24) & 0xFF)); processByte(static_cast((bitCount >> 16) & 0xFF)); processByte(static_cast((bitCount >> 8) & 0xFF)); processByte(static_cast((bitCount) & 0xFF)); memcpy(digest, m_digest, 5 * sizeof(uint32_t)); return digest; } const uint8_t *getDigestBytes(digest8_t digest) { digest32_t d32; getDigest(d32); size_t di = 0; digest[di++] = ((d32[0] >> 24) & 0xFF); digest[di++] = ((d32[0] >> 16) & 0xFF); digest[di++] = ((d32[0] >> 8) & 0xFF); digest[di++] = ((d32[0]) & 0xFF); digest[di++] = ((d32[1] >> 24) & 0xFF); digest[di++] = ((d32[1] >> 16) & 0xFF); digest[di++] = ((d32[1] >> 8) & 0xFF); digest[di++] = ((d32[1]) & 0xFF); digest[di++] = ((d32[2] >> 24) & 0xFF); digest[di++] = ((d32[2] >> 16) & 0xFF); digest[di++] = ((d32[2] >> 8) & 0xFF); digest[di++] = ((d32[2]) & 0xFF); digest[di++] = ((d32[3] >> 24) & 0xFF); digest[di++] = ((d32[3] >> 16) & 0xFF); digest[di++] = ((d32[3] >> 8) & 0xFF); digest[di++] = ((d32[3]) & 0xFF); digest[di++] = ((d32[4] >> 24) & 0xFF); digest[di++] = ((d32[4] >> 16) & 0xFF); digest[di++] = ((d32[4] >> 8) & 0xFF); digest[di++] = ((d32[4]) & 0xFF); return digest; } protected: void processBlock() { uint32_t w[80]; for (size_t i = 0; i < 16; i++) { w[i] = (m_block[i * 4 + 0] << 24); w[i] |= (m_block[i * 4 + 1] << 16); w[i] |= (m_block[i * 4 + 2] << 8); w[i] |= (m_block[i * 4 + 3]); } for (size_t i = 16; i < 80; i++) { w[i] = LeftRotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1); } uint32_t a = m_digest[0]; uint32_t b = m_digest[1]; uint32_t c = m_digest[2]; uint32_t d = m_digest[3]; uint32_t e = m_digest[4]; for (std::size_t i = 0; i < 80; ++i) { uint32_t f = 0; uint32_t k = 0; if (i < 20) { f = (b & c) | (~b & d); k = 0x5A827999; } else if (i < 40) { f = b ^ c ^ d; k = 0x6ED9EBA1; } else if (i < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8F1BBCDC; } else { f = b ^ c ^ d; k = 0xCA62C1D6; } uint32_t temp = LeftRotate(a, 5) + f + e + k + w[i]; e = d; d = c; c = LeftRotate(b, 30); b = a; a = temp; } m_digest[0] += a; m_digest[1] += b; m_digest[2] += c; m_digest[3] += d; m_digest[4] += e; } private: digest32_t m_digest; uint8_t m_block[64]; size_t m_blockByteIndex; size_t m_byteCount; }; } // namespace sha1 #endif #include #include #include #include #include #include #include namespace crow { // ---------------------------------------------------------------------------- // qs_parse (modified) // https://github.com/bartgrantham/qs_parse // ---------------------------------------------------------------------------- /* Similar to strncmp, but handles URL-encoding for either string */ int qs_strncmp(const char *s, const char *qs, size_t n); /* Finds the beginning of each key/value pair and stores a pointer in qs_kv. * Also decodes the value portion of the k/v pair *in-place*. In a future * enhancement it will also have a compile-time option of sorting qs_kv * alphabetically by key. */ size_t qs_parse(char *qs, char *qs_kv[], size_t qs_kv_size, bool parse_url); /* Used by qs_parse to decode the value portion of a k/v pair */ int qs_decode(char *qs); /* Looks up the value according to the key on a pre-processed query string * A future enhancement will be a compile-time option to look up the key * in a pre-sorted qs_kv array via a binary search. */ // char * qs_k2v(const char * key, char * qs_kv[], int qs_kv_size); char *qs_k2v(const char *key, char *const *qs_kv, size_t qs_kv_size, int nth); /* Non-destructive lookup of value, based on key. User provides the * destinaton string and length. */ char *qs_scanvalue(const char *key, const char *qs, char *val, size_t val_len); // TODO: implement sorting of the qs_kv array; for now ensure it's not compiled #undef _qsSORTING // isxdigit _is_ available in , but let's avoid another header instead #define CROW_QS_ISHEX(x) \ ((((x) >= '0' && (x) <= '9') || ((x) >= 'A' && (x) <= 'F') || \ ((x) >= 'a' && (x) <= 'f')) \ ? 1 \ : 0) #define CROW_QS_HEX2DEC(x) \ (((x) >= '0' && (x) <= '9') ? (x) - 48 \ : ((x) >= 'A' && (x) <= 'F') ? (x) - 55 \ : ((x) >= 'a' && (x) <= 'f') ? (x) - 87 \ : 0) #define CROW_QS_ISQSCHR(x) \ ((((x) == '=') || ((x) == '#') || ((x) == '&') || ((x) == '\0')) ? 0 : 1) inline int qs_strncmp(const char *s, const char *qs, size_t n) { unsigned char u1, u2, unyb, lnyb; while (n-- > 0) { u1 = static_cast(*s++); u2 = static_cast(*qs++); if (!CROW_QS_ISQSCHR(u1)) { u1 = '\0'; } if (!CROW_QS_ISQSCHR(u2)) { u2 = '\0'; } if (u1 == '+') { u1 = ' '; } if (u1 == '%') // easier/safer than scanf { unyb = static_cast(*s++); lnyb = static_cast(*s++); if (CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb)) u1 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb); else u1 = '\0'; } if (u2 == '+') { u2 = ' '; } if (u2 == '%') // easier/safer than scanf { unyb = static_cast(*qs++); lnyb = static_cast(*qs++); if (CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb)) u2 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb); else u2 = '\0'; } if (u1 != u2) return u1 - u2; if (u1 == '\0') return 0; } if (CROW_QS_ISQSCHR(*qs)) return -1; else return 0; } inline size_t qs_parse(char *qs, char *qs_kv[], size_t qs_kv_size, bool parse_url = true) { size_t i, j; char *substr_ptr; for (i = 0; i < qs_kv_size; i++) qs_kv[i] = NULL; // find the beginning of the k/v substrings or the fragment substr_ptr = parse_url ? qs + strcspn(qs, "?#") : qs; if (parse_url) { if (substr_ptr[0] != '\0') substr_ptr++; else return 0; // no query or fragment } i = 0; while (i < qs_kv_size) { qs_kv[i] = substr_ptr; j = strcspn(substr_ptr, "&"); if (substr_ptr[j] == '\0') { i++; break; } // x &'s -> means x iterations of this loop -> means *x+1* k/v pairs substr_ptr += j + 1; i++; } // we only decode the values in place, the keys could have '='s in them // which will hose our ability to distinguish keys from values later for (j = 0; j < i; j++) { substr_ptr = qs_kv[j] + strcspn(qs_kv[j], "=&#"); if (substr_ptr[0] == '&' || substr_ptr[0] == '\0') // blank value: skip decoding substr_ptr[0] = '\0'; else qs_decode(++substr_ptr); } #ifdef _qsSORTING // TODO: qsort qs_kv, using qs_strncmp() for the comparison #endif return i; } inline int qs_decode(char *qs) { int i = 0, j = 0; while (CROW_QS_ISQSCHR(qs[j])) { if (qs[j] == '+') { qs[i] = ' '; } else if (qs[j] == '%') // easier/safer than scanf { if (!CROW_QS_ISHEX(qs[j + 1]) || !CROW_QS_ISHEX(qs[j + 2])) { qs[i] = '\0'; return i; } qs[i] = (CROW_QS_HEX2DEC(qs[j + 1]) * 16) + CROW_QS_HEX2DEC(qs[j + 2]); j += 2; } else { qs[i] = qs[j]; } i++; j++; } qs[i] = '\0'; return i; } inline char *qs_k2v(const char *key, char *const *qs_kv, size_t qs_kv_size, int nth = 0) { size_t i; size_t key_len, skip; key_len = strlen(key); #ifdef _qsSORTING // TODO: binary search for key in the sorted qs_kv #else // _qsSORTING for (i = 0; i < qs_kv_size; i++) { // we rely on the unambiguous '=' to find the value in our k/v pair if (qs_strncmp(key, qs_kv[i], key_len) == 0) { skip = strcspn(qs_kv[i], "="); if (qs_kv[i][skip] == '=') skip++; // return (zero-char value) ? ptr to trailing '\0' : ptr to value if (nth == 0) return qs_kv[i] + skip; else --nth; } } #endif // _qsSORTING return nullptr; } inline std::unique_ptr> qs_dict_name2kv(const char *dict_name, char *const *qs_kv, size_t qs_kv_size, int nth = 0) { size_t i; size_t name_len, skip_to_eq, skip_to_brace_open, skip_to_brace_close; name_len = strlen(dict_name); #ifdef _qsSORTING // TODO: binary search for key in the sorted qs_kv #else // _qsSORTING for (i = 0; i < qs_kv_size; i++) { if (strncmp(dict_name, qs_kv[i], name_len) == 0) { skip_to_eq = strcspn(qs_kv[i], "="); if (qs_kv[i][skip_to_eq] == '=') skip_to_eq++; skip_to_brace_open = strcspn(qs_kv[i], "["); if (qs_kv[i][skip_to_brace_open] == '[') skip_to_brace_open++; skip_to_brace_close = strcspn(qs_kv[i], "]"); if (skip_to_brace_open <= skip_to_brace_close && skip_to_brace_open > 0 && skip_to_brace_close > 0 && nth == 0) { auto key = std::string(qs_kv[i] + skip_to_brace_open, skip_to_brace_close - skip_to_brace_open); auto value = std::string(qs_kv[i] + skip_to_eq); return std::unique_ptr>( new std::pair(key, value)); } else { --nth; } } } #endif // _qsSORTING return nullptr; } inline char *qs_scanvalue(const char *key, const char *qs, char *val, size_t val_len) { const char *tmp = strchr(qs, '?'); // find the beginning of the k/v substrings if (tmp != nullptr) qs = tmp + 1; const size_t key_len = strlen(key); while (*qs != '#' && *qs != '\0') { if (qs_strncmp(key, qs, key_len) == 0) break; qs += strcspn(qs, "&"); if (*qs == '&') qs++; } if (qs[0] == '\0') return nullptr; qs += strcspn(qs, "=&#"); if (qs[0] == '=') { qs++; size_t i = strcspn(qs, "&=#"); #ifdef _MSC_VER strncpy_s(val, val_len, qs, (val_len - 1) < (i + 1) ? (val_len - 1) : (i + 1)); #else strncpy(val, qs, (val_len - 1) < (i + 1) ? (val_len - 1) : (i + 1)); #endif qs_decode(val); } else { if (val_len > 0) val[0] = '\0'; } return val; } } // namespace crow // ---------------------------------------------------------------------------- namespace crow { struct request; /// A class to represent any data coming after the `?` in the request URL into /// key-value pairs. class query_string { public: static const int MAX_KEY_VALUE_PAIRS_COUNT = 256; query_string() = default; query_string(const query_string &qs) : url_(qs.url_) { for (auto p : qs.key_value_pairs_) { key_value_pairs_.push_back((char *)(p - qs.url_.c_str() + url_.c_str())); } } query_string &operator=(const query_string &qs) { url_ = qs.url_; key_value_pairs_.clear(); for (auto p : qs.key_value_pairs_) { key_value_pairs_.push_back((char *)(p - qs.url_.c_str() + url_.c_str())); } return *this; } query_string &operator=(query_string &&qs) noexcept { key_value_pairs_ = std::move(qs.key_value_pairs_); char *old_data = (char *)qs.url_.c_str(); url_ = std::move(qs.url_); for (auto &p : key_value_pairs_) { p += (char *)url_.c_str() - old_data; } return *this; } query_string(std::string params, bool url = true) : url_(std::move(params)) { if (url_.empty()) return; key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT); size_t count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT, url); key_value_pairs_.resize(count); key_value_pairs_.shrink_to_fit(); } void clear() { key_value_pairs_.clear(); url_.clear(); } friend std::ostream &operator<<(std::ostream &os, const query_string &qs) { os << "[ "; for (size_t i = 0; i < qs.key_value_pairs_.size(); ++i) { if (i) os << ", "; os << qs.key_value_pairs_[i]; } os << " ]"; return os; } /// Get a value from a name, used for `?name=value`. /// /// Note: this method returns the value of the first occurrence of the key /// only, to return all occurrences, see \ref get_list(). char *get(const std::string &name) const { char *ret = qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size()); return ret; } /// Works similar to \ref get() except it removes the item from the query /// string. char *pop(const std::string &name) { char *ret = get(name); if (ret != nullptr) { const std::string key_name = name + '='; for (unsigned int i = 0; i < key_value_pairs_.size(); i++) { std::string str_item(key_value_pairs_[i]); if (str_item.find(key_name) == 0) { key_value_pairs_.erase(key_value_pairs_.begin() + i); break; } } } return ret; } /// Returns a list of values, passed as /// `?name[]=value1&name[]=value2&...name[]=valuen` with n being the size of /// the list. /// /// Note: Square brackets in the above example are controlled by /// `use_brackets` boolean (true by default). If set to false, the example /// becomes `?name=value1,name=value2...name=valuen` std::vector get_list(const std::string &name, bool use_brackets = true) const { std::vector ret; std::string plus = name + (use_brackets ? "[]" : ""); char *element = nullptr; int count = 0; while (1) { element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++); if (!element) break; ret.push_back(element); } return ret; } /// Similar to \ref get_list() but it removes the std::vector pop_list(const std::string &name, bool use_brackets = true) { std::vector ret = get_list(name, use_brackets); const size_t name_len = name.length(); if (!ret.empty()) { for (unsigned int i = 0; i < key_value_pairs_.size(); i++) { std::string str_item(key_value_pairs_[i]); if (str_item.find(name) == 0) { if (use_brackets && str_item.find("[]=", name_len) == name_len) { key_value_pairs_.erase(key_value_pairs_.begin() + i--); } else if (!use_brackets && str_item.find('=', name_len) == name_len) { key_value_pairs_.erase(key_value_pairs_.begin() + i--); } } } } return ret; } /// Works similar to \ref get_list() except the brackets are mandatory must /// not be empty. /// /// For example calling `get_dict(yourname)` on /// `?yourname[sub1]=42&yourname[sub2]=84` would give a map containing `{sub1 /// : 42, sub2 : 84}`. /// /// if your query string has both empty brackets and ones with a key inside, /// use pop_list() to get all the values without a key before running this /// method. std::unordered_map get_dict(const std::string &name) const { std::unordered_map ret; int count = 0; while (1) { if (auto element = qs_dict_name2kv(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++)) ret.insert(*element); else break; } return ret; } /// Works the same as \ref get_dict() but removes the values from the query /// string. std::unordered_map pop_dict(const std::string &name) { const std::string name_value = name + '['; std::unordered_map ret = get_dict(name); if (!ret.empty()) { for (unsigned int i = 0; i < key_value_pairs_.size(); i++) { std::string str_item(key_value_pairs_[i]); if (str_item.find(name_value) == 0) { key_value_pairs_.erase(key_value_pairs_.begin() + i--); } } } return ret; } std::vector keys() const { std::vector keys; keys.reserve(key_value_pairs_.size()); for (const char *const element : key_value_pairs_) { const char *delimiter = strchr(element, '='); if (delimiter) keys.emplace_back(element, delimiter); else keys.emplace_back(element); } return keys; } private: std::string url_; std::vector key_value_pairs_; }; } // namespace crow // This file is generated from nginx/conf/mime.types using nginx_mime2cpp.py on // 2021-12-03. #include #include namespace crow { const std::unordered_map mime_types{ {"gz", "application/gzip"}, {"shtml", "text/html"}, {"htm", "text/html"}, {"html", "text/html"}, {"css", "text/css"}, {"xml", "text/xml"}, {"gif", "image/gif"}, {"jpg", "image/jpeg"}, {"jpeg", "image/jpeg"}, {"js", "application/javascript"}, {"atom", "application/atom+xml"}, {"rss", "application/rss+xml"}, {"mml", "text/mathml"}, {"txt", "text/plain"}, {"jad", "text/vnd.sun.j2me.app-descriptor"}, {"wml", "text/vnd.wap.wml"}, {"htc", "text/x-component"}, {"avif", "image/avif"}, {"png", "image/png"}, {"svgz", "image/svg+xml"}, {"svg", "image/svg+xml"}, {"tiff", "image/tiff"}, {"tif", "image/tiff"}, {"wbmp", "image/vnd.wap.wbmp"}, {"webp", "image/webp"}, {"ico", "image/x-icon"}, {"jng", "image/x-jng"}, {"bmp", "image/x-ms-bmp"}, {"woff", "font/woff"}, {"woff2", "font/woff2"}, {"ear", "application/java-archive"}, {"war", "application/java-archive"}, {"jar", "application/java-archive"}, {"json", "application/json"}, {"hqx", "application/mac-binhex40"}, {"doc", "application/msword"}, {"pdf", "application/pdf"}, {"ai", "application/postscript"}, {"eps", "application/postscript"}, {"ps", "application/postscript"}, {"rtf", "application/rtf"}, {"m3u8", "application/vnd.apple.mpegurl"}, {"kml", "application/vnd.google-earth.kml+xml"}, {"kmz", "application/vnd.google-earth.kmz"}, {"xls", "application/vnd.ms-excel"}, {"eot", "application/vnd.ms-fontobject"}, {"ppt", "application/vnd.ms-powerpoint"}, {"odg", "application/vnd.oasis.opendocument.graphics"}, {"odp", "application/vnd.oasis.opendocument.presentation"}, {"ods", "application/vnd.oasis.opendocument.spreadsheet"}, {"odt", "application/vnd.oasis.opendocument.text"}, {"pptx", "application/" "vnd.openxmlformats-officedocument.presentationml.presentation"}, {"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, {"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, {"wmlc", "application/vnd.wap.wmlc"}, {"wasm", "application/wasm"}, {"7z", "application/x-7z-compressed"}, {"cco", "application/x-cocoa"}, {"jardiff", "application/x-java-archive-diff"}, {"jnlp", "application/x-java-jnlp-file"}, {"run", "application/x-makeself"}, {"pm", "application/x-perl"}, {"pl", "application/x-perl"}, {"pdb", "application/x-pilot"}, {"prc", "application/x-pilot"}, {"rar", "application/x-rar-compressed"}, {"rpm", "application/x-redhat-package-manager"}, {"sea", "application/x-sea"}, {"swf", "application/x-shockwave-flash"}, {"sit", "application/x-stuffit"}, {"tk", "application/x-tcl"}, {"tcl", "application/x-tcl"}, {"crt", "application/x-x509-ca-cert"}, {"pem", "application/x-x509-ca-cert"}, {"der", "application/x-x509-ca-cert"}, {"xpi", "application/x-xpinstall"}, {"xhtml", "application/xhtml+xml"}, {"xspf", "application/xspf+xml"}, {"zip", "application/zip"}, {"dll", "application/octet-stream"}, {"exe", "application/octet-stream"}, {"bin", "application/octet-stream"}, {"deb", "application/octet-stream"}, {"dmg", "application/octet-stream"}, {"img", "application/octet-stream"}, {"iso", "application/octet-stream"}, {"msm", "application/octet-stream"}, {"msp", "application/octet-stream"}, {"msi", "application/octet-stream"}, {"kar", "audio/midi"}, {"midi", "audio/midi"}, {"mid", "audio/midi"}, {"mp3", "audio/mpeg"}, {"ogg", "audio/ogg"}, {"m4a", "audio/x-m4a"}, {"ra", "audio/x-realaudio"}, {"3gp", "video/3gpp"}, {"3gpp", "video/3gpp"}, {"ts", "video/mp2t"}, {"mp4", "video/mp4"}, {"mpg", "video/mpeg"}, {"mpeg", "video/mpeg"}, {"mov", "video/quicktime"}, {"webm", "video/webm"}, {"flv", "video/x-flv"}, {"m4v", "video/x-m4v"}, {"mng", "video/x-mng"}, {"asf", "video/x-ms-asf"}, {"asx", "video/x-ms-asf"}, {"wmv", "video/x-ms-wmv"}, {"avi", "video/x-msvideo"}}; } // settings for crow // TODO(ipkn) replace with runtime config. libucl? /* #ifdef - enables debug mode */ // #define CROW_ENABLE_DEBUG /* #ifdef - enables logging */ #define CROW_ENABLE_LOGGING /* #ifdef - enforces section 5.2 and 6.1 of RFC6455 (only accepting masked * messages from clients) */ // #define CROW_ENFORCE_WS_SPEC /* #define - specifies log level */ /* Debug = 0 Info = 1 Warning = 2 Error = 3 Critical = 4 default to INFO */ #ifndef CROW_LOG_LEVEL #define CROW_LOG_LEVEL 1 #endif #ifndef CROW_STATIC_DIRECTORY #define CROW_STATIC_DIRECTORY "static/" #endif #ifndef CROW_STATIC_ENDPOINT #define CROW_STATIC_ENDPOINT "/static/" #endif // compiler flags #if defined(_MSC_VER) #if _MSC_VER < 1900 #define CROW_MSVC_WORKAROUND #define constexpr const #define noexcept throw() #endif #endif #ifdef CROW_USE_BOOST #include #include #ifdef CROW_ENABLE_SSL #include #endif #else #ifndef ASIO_STANDALONE #define ASIO_STANDALONE #endif #include #include #ifdef CROW_ENABLE_SSL #include #endif #endif #if (defined(CROW_USE_BOOST) && BOOST_VERSION >= 107000) || \ (ASIO_VERSION >= 101008) #define GET_IO_CONTEXT(s) ((asio::io_context &)(s).get_executor().context()) #else #define GET_IO_CONTEXT(s) ((s).get_io_service()) #endif namespace crow { #ifdef CROW_USE_BOOST namespace asio = boost::asio; using error_code = boost::system::error_code; #else using error_code = asio::error_code; #endif using tcp = asio::ip::tcp; using stream_protocol = asio::local::stream_protocol; /// A wrapper for the asio::ip::tcp::socket and asio::ssl::stream struct SocketAdaptor { using context = void; SocketAdaptor(asio::io_context &io_context, context *) : socket_(io_context) {} asio::io_context &get_io_context() { return GET_IO_CONTEXT(socket_); } /// Get the TCP socket handling data transfers, regardless of what layer is /// handling transfers on top of the socket. tcp::socket &raw_socket() { return socket_; } /// Get the object handling data transfers, this can be either a TCP socket or /// an SSL stream (if SSL is enabled). tcp::socket &socket() { return socket_; } tcp::endpoint remote_endpoint() const { return socket_.remote_endpoint(); } std::string address() const { return socket_.remote_endpoint().address().to_string(); } bool is_open() const { return socket_.is_open(); } void close() { error_code ec; socket_.close(ec); } void shutdown_readwrite() { error_code ec; socket_.shutdown(asio::socket_base::shutdown_type::shutdown_both, ec); } void shutdown_write() { error_code ec; socket_.shutdown(asio::socket_base::shutdown_type::shutdown_send, ec); } void shutdown_read() { error_code ec; socket_.shutdown(asio::socket_base::shutdown_type::shutdown_receive, ec); } template void start(F f) { f(error_code()); } tcp::socket socket_; }; struct UnixSocketAdaptor { using context = void; UnixSocketAdaptor(asio::io_context &io_context, context *) : socket_(io_context) {} asio::io_context &get_io_context() { return GET_IO_CONTEXT(socket_); } stream_protocol::socket &raw_socket() { return socket_; } stream_protocol::socket &socket() { return socket_; } stream_protocol::endpoint remote_endpoint() { return socket_.local_endpoint(); } std::string address() const { return ""; } bool is_open() { return socket_.is_open(); } void close() { error_code ec; socket_.close(ec); } void shutdown_readwrite() { error_code ec; socket_.shutdown(asio::socket_base::shutdown_type::shutdown_both, ec); } void shutdown_write() { error_code ec; socket_.shutdown(asio::socket_base::shutdown_type::shutdown_send, ec); } void shutdown_read() { error_code ec; socket_.shutdown(asio::socket_base::shutdown_type::shutdown_receive, ec); } template void start(F f) { f(error_code()); } stream_protocol::socket socket_; }; #ifdef CROW_ENABLE_SSL struct SSLAdaptor { using context = asio::ssl::context; using ssl_socket_t = asio::ssl::stream; SSLAdaptor(asio::io_context &io_context, context *ctx) : ssl_socket_(new ssl_socket_t(io_context, *ctx)) {} asio::ssl::stream &socket() { return *ssl_socket_; } tcp::socket::lowest_layer_type &raw_socket() { return ssl_socket_->lowest_layer(); } tcp::endpoint remote_endpoint() { return raw_socket().remote_endpoint(); } std::string address() const { return ssl_socket_->lowest_layer().remote_endpoint().address().to_string(); } bool is_open() { return ssl_socket_ ? raw_socket().is_open() : false; } void close() { if (is_open()) { error_code ec; raw_socket().close(ec); } } void shutdown_readwrite() { if (is_open()) { error_code ec; raw_socket().shutdown(asio::socket_base::shutdown_type::shutdown_both, ec); } } void shutdown_write() { if (is_open()) { error_code ec; raw_socket().shutdown(asio::socket_base::shutdown_type::shutdown_send, ec); } } void shutdown_read() { if (is_open()) { error_code ec; raw_socket().shutdown(asio::socket_base::shutdown_type::shutdown_receive, ec); } } asio::io_context &get_io_context() { return GET_IO_CONTEXT(raw_socket()); } template void start(F f) { ssl_socket_->async_handshake(asio::ssl::stream_base::server, [f](const error_code &ec) { f(ec); }); } std::unique_ptr> ssl_socket_; }; #endif } // namespace crow #include #include #include #include #include #include namespace crow { enum class LogLevel { #ifndef ERROR #ifndef DEBUG DEBUG = 0, INFO, WARNING, ERROR, CRITICAL, #endif #endif Debug = 0, Info, Warning, Error, Critical, }; class ILogHandler { public: virtual ~ILogHandler() = default; virtual void log(const std::string &message, LogLevel level) = 0; }; class CerrLogHandler : public ILogHandler { public: void log(const std::string &message, LogLevel level) override { std::string log_msg; log_msg.reserve(message.length() + 1 + 32 + 3 + 8 + 2); log_msg.append("(").append(timestamp()).append(") ["); switch (level) { case LogLevel::Debug: log_msg.append("DEBUG "); break; case LogLevel::Info: log_msg.append("INFO "); break; case LogLevel::Warning: log_msg.append("WARNING "); break; case LogLevel::Error: log_msg.append("ERROR "); break; case LogLevel::Critical: log_msg.append("CRITICAL"); break; } log_msg.append("] ").append(message); std::cerr << log_msg << std::endl; } private: static std::string timestamp() { char date[32]; time_t t = time(0); tm my_tm; #if defined(_MSC_VER) || defined(__MINGW32__) #ifdef CROW_USE_LOCALTIMEZONE localtime_s(&my_tm, &t); #else gmtime_s(&my_tm, &t); #endif #else #ifdef CROW_USE_LOCALTIMEZONE localtime_r(&t, &my_tm); #else gmtime_r(&t, &my_tm); #endif #endif size_t sz = strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", &my_tm); return std::string(date, date + sz); } }; class logger { public: logger(LogLevel level) : level_(level) {} ~logger() { #ifdef CROW_ENABLE_LOGGING if (level_ >= get_current_log_level()) { get_handler_ref()->log(stringstream_.str(), level_); } #endif } // template logger &operator<<(T const &value) { #ifdef CROW_ENABLE_LOGGING if (level_ >= get_current_log_level()) { stringstream_ << value; } #endif return *this; } // static void setLogLevel(LogLevel level) { get_log_level_ref() = level; } static void setHandler(ILogHandler *handler) { get_handler_ref() = handler; } static LogLevel get_current_log_level() { return get_log_level_ref(); } private: // static LogLevel &get_log_level_ref() { static LogLevel current_level = static_cast(CROW_LOG_LEVEL); return current_level; } static ILogHandler *&get_handler_ref() { static CerrLogHandler default_handler; static ILogHandler *current_handler = &default_handler; return current_handler; } // std::ostringstream stringstream_; LogLevel level_; }; } // namespace crow #define CROW_LOG_CRITICAL \ if (crow::logger::get_current_log_level() <= crow::LogLevel::Critical) \ crow::logger(crow::LogLevel::Critical) #define CROW_LOG_ERROR \ if (crow::logger::get_current_log_level() <= crow::LogLevel::Error) \ crow::logger(crow::LogLevel::Error) #define CROW_LOG_WARNING \ if (crow::logger::get_current_log_level() <= crow::LogLevel::Warning) \ crow::logger(crow::LogLevel::Warning) #define CROW_LOG_INFO \ if (crow::logger::get_current_log_level() <= crow::LogLevel::Info) \ crow::logger(crow::LogLevel::Info) #define CROW_LOG_DEBUG \ if (crow::logger::get_current_log_level() <= crow::LogLevel::Debug) \ crow::logger(crow::LogLevel::Debug) #include namespace crow { /// An abstract class that allows any other class to be returned by a handler. struct returnable { std::string content_type; virtual std::string dump() const = 0; returnable(std::string ctype) : content_type{ctype} {} virtual ~returnable() {} }; } // namespace crow #ifdef CROW_USE_BOOST #include #ifdef CROW_ENABLE_SSL #include #endif #else #ifndef ASIO_STANDALONE #define ASIO_STANDALONE #endif #include #ifdef CROW_ENABLE_SSL #include #endif #endif namespace crow { #ifdef CROW_USE_BOOST namespace asio = boost::asio; using error_code = boost::system::error_code; #else using error_code = asio::error_code; #endif using tcp = asio::ip::tcp; using stream_protocol = asio::local::stream_protocol; struct TCPAcceptor { using endpoint = tcp::endpoint; tcp::acceptor acceptor_; TCPAcceptor(asio::io_context &io_context) : acceptor_(io_context) {} int16_t port() const { return acceptor_.local_endpoint().port(); } std::string address() const { return acceptor_.local_endpoint().address().to_string(); } std::string url_display(bool ssl_used) const { auto address = acceptor_.local_endpoint().address(); return (ssl_used ? "https://" : "http://") + (address.is_v4() ? address.to_string() : "[" + address.to_string() + "]") + ":" + std::to_string(acceptor_.local_endpoint().port()); } tcp::acceptor &raw_acceptor() { return acceptor_; } endpoint local_endpoint() const { return acceptor_.local_endpoint(); } inline static tcp::acceptor::reuse_address reuse_address_option() { return tcp::acceptor::reuse_address(true); } }; struct UnixSocketAcceptor { using endpoint = stream_protocol::endpoint; stream_protocol::acceptor acceptor_; UnixSocketAcceptor(asio::io_context &io_context) : acceptor_(io_context) {} int16_t port() const { return 0; } std::string address() const { return acceptor_.local_endpoint().path(); } std::string url_display(bool) const { return acceptor_.local_endpoint().path(); } stream_protocol::acceptor &raw_acceptor() { return acceptor_; } endpoint local_endpoint() const { return acceptor_.local_endpoint(); } inline static stream_protocol::acceptor::reuse_address reuse_address_option() { // reuse addr must be false // (https://github.com/chriskohlhoff/asio/issues/622) return stream_protocol::acceptor::reuse_address(false); } }; } // namespace crow #include #include #include #include #include #include #include #include #include #include #include #include #include #include // TODO(EDev): Adding C++20's [[likely]] and [[unlikely]] attributes might be // useful #if defined(__GNUG__) || defined(__clang__) #define CROW_LIKELY(X) __builtin_expect(!!(X), 1) #define CROW_UNLIKELY(X) __builtin_expect(!!(X), 0) #else #define CROW_LIKELY(X) (X) #define CROW_UNLIKELY(X) (X) #endif namespace crow { /// @cond SKIP namespace black_magic { #ifndef CROW_MSVC_WORKAROUND /// Out of Range Exception for const_str struct OutOfRange { OutOfRange(unsigned /*pos*/, unsigned /*length*/) {} }; /// Helper function to throw an exception if i is larger than len constexpr unsigned requires_in_range(unsigned i, unsigned len) { return i >= len ? throw OutOfRange(i, len) : i; } /// A constant string implementation. class const_str { const char *const begin_; unsigned size_; public: template constexpr const_str(const char (&arr)[N]) : begin_(arr), size_(N - 1) { static_assert(N >= 1, "not a string literal"); } constexpr char operator[](unsigned i) const { return requires_in_range(i, size_), begin_[i]; } constexpr operator const char *() const { return begin_; } constexpr const char *begin() const { return begin_; } constexpr const char *end() const { return begin_ + size_; } constexpr unsigned size() const { return size_; } }; constexpr unsigned find_closing_tag(const_str s, unsigned p) { return s[p] == '>' ? p : find_closing_tag(s, p + 1); } /// Check that the CROW_ROUTE string is valid constexpr bool is_valid(const_str s, unsigned i = 0, int f = 0) { return i == s.size() ? f == 0 : f < 0 || f >= 2 ? false : s[i] == '<' ? is_valid(s, i + 1, f + 1) : s[i] == '>' ? is_valid(s, i + 1, f - 1) : is_valid(s, i + 1, f); } constexpr bool is_equ_p(const char *a, const char *b, unsigned n) { return *a == 0 && *b == 0 && n == 0 ? true : (*a == 0 || *b == 0) ? false : n == 0 ? true : *a != *b ? false : is_equ_p(a + 1, b + 1, n - 1); } constexpr bool is_equ_n(const_str a, unsigned ai, const_str b, unsigned bi, unsigned n) { return ai + n > a.size() || bi + n > b.size() ? false : n == 0 ? true : a[ai] != b[bi] ? false : is_equ_n(a, ai + 1, b, bi + 1, n - 1); } constexpr bool is_int(const_str s, unsigned i) { return is_equ_n(s, i, "", 0, 5); } constexpr bool is_uint(const_str s, unsigned i) { return is_equ_n(s, i, "", 0, 6); } constexpr bool is_float(const_str s, unsigned i) { return is_equ_n(s, i, "", 0, 7) || is_equ_n(s, i, "", 0, 8); } constexpr bool is_str(const_str s, unsigned i) { return is_equ_n(s, i, "", 0, 5) || is_equ_n(s, i, "", 0, 8); } constexpr bool is_path(const_str s, unsigned i) { return is_equ_n(s, i, "", 0, 6); } #endif template struct parameter_tag { static const int value = 0; }; #define CROW_INTERNAL_PARAMETER_TAG(t, i) \ template <> struct parameter_tag { \ static const int value = i; \ } CROW_INTERNAL_PARAMETER_TAG(int, 1); CROW_INTERNAL_PARAMETER_TAG(char, 1); CROW_INTERNAL_PARAMETER_TAG(short, 1); CROW_INTERNAL_PARAMETER_TAG(long, 1); CROW_INTERNAL_PARAMETER_TAG(long long, 1); CROW_INTERNAL_PARAMETER_TAG(unsigned int, 2); CROW_INTERNAL_PARAMETER_TAG(unsigned char, 2); CROW_INTERNAL_PARAMETER_TAG(unsigned short, 2); CROW_INTERNAL_PARAMETER_TAG(unsigned long, 2); CROW_INTERNAL_PARAMETER_TAG(unsigned long long, 2); CROW_INTERNAL_PARAMETER_TAG(double, 3); CROW_INTERNAL_PARAMETER_TAG(std::string, 4); #undef CROW_INTERNAL_PARAMETER_TAG template struct compute_parameter_tag_from_args_list; template <> struct compute_parameter_tag_from_args_list<> { static const int value = 0; }; template struct compute_parameter_tag_from_args_list { static const int sub_value = compute_parameter_tag_from_args_list::value; static const int value = parameter_tag::type>::value ? sub_value * 6 + parameter_tag::type>::value : sub_value; }; static inline bool is_parameter_tag_compatible(uint64_t a, uint64_t b) { if (a == 0) return b == 0; if (b == 0) return a == 0; int sa = a % 6; int sb = a % 6; if (sa == 5) sa = 4; if (sb == 5) sb = 4; if (sa != sb) return false; return is_parameter_tag_compatible(a / 6, b / 6); } static inline unsigned find_closing_tag_runtime(const char *s, unsigned p) { return s[p] == 0 ? throw std::runtime_error("unmatched tag <") : s[p] == '>' ? p : find_closing_tag_runtime(s, p + 1); } static inline uint64_t get_parameter_tag_runtime(const char *s, unsigned p = 0) { return s[p] == 0 ? 0 : s[p] == '<' ? (std::strncmp(s + p, "", 5) == 0 ? get_parameter_tag_runtime( s, find_closing_tag_runtime(s, p)) * 6 + 1 : std::strncmp(s + p, "", 6) == 0 ? get_parameter_tag_runtime( s, find_closing_tag_runtime(s, p)) * 6 + 2 : (std::strncmp(s + p, "", 7) == 0 || std::strncmp(s + p, "", 8) == 0) ? get_parameter_tag_runtime( s, find_closing_tag_runtime(s, p)) * 6 + 3 : (std::strncmp(s + p, "", 5) == 0 || std::strncmp(s + p, "", 8) == 0) ? get_parameter_tag_runtime( s, find_closing_tag_runtime(s, p)) * 6 + 4 : std::strncmp(s + p, "", 6) == 0 ? get_parameter_tag_runtime( s, find_closing_tag_runtime(s, p)) * 6 + 5 : throw std::runtime_error("invalid parameter type")) : get_parameter_tag_runtime(s, p + 1); } #ifndef CROW_MSVC_WORKAROUND constexpr uint64_t get_parameter_tag(const_str s, unsigned p = 0) { return p == s.size() ? 0 : s[p] == '<' ? (is_int(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 1 : is_uint(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 2 : is_float(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 3 : is_str(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 4 : is_path(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 5 : throw std::runtime_error("invalid parameter type")) : get_parameter_tag(s, p + 1); } #endif template struct S { template using push = S; template using push_back = S; template