Compare commits

...

104 Commits

Author SHA1 Message Date
yhirose eacc1ca98e Release v0.27.0 2025-10-27 19:57:53 -04:00
yhirose ac9ebb0ee3 Merge commit from fork
* Fix "Untrusted HTTP Header Handling (REMOTE*/LOCAL*)"

* Fix "Untrusted HTTP Header Handling (X-Forwarded-For)"

* Fix security problems in docker/main.cc
2025-10-27 19:54:12 -04:00
yhirose 11eed05ce7 Fix #2255 and #2256 2025-10-27 19:51:55 -04:00
yhirose f3bba0646a Fix benchmark test issue on Windows (#2258) 2025-10-27 18:27:55 -04:00
yhirose 2da189f88c Fix EventDispatcher problem (#2257) 2025-10-27 18:10:52 -04:00
yhirose 6e0f211cff Fix problem with .gitignore for examples 2025-10-27 17:54:27 -04:00
yhirose 318a3fe425 Fix problem with installing OpenSSL for Windows (#2254) 2025-10-25 22:35:21 -04:00
yhirose 2d8d524178 Fix #2251 2025-10-25 22:06:42 -04:00
yhirose afa88dbe70 Fix #2250 2025-10-25 21:36:53 -04:00
yhirose 08133b593b Merge branch 'staticlibs-ssl_error_reporting' 2025-10-25 19:32:31 -04:00
yhirose 8aedbf4547 Add a unit test 2025-10-25 19:31:48 -04:00
yhirose cde29362ef Merge branch 'ssl_error_reporting' of github.com:staticlibs/cpp-httplib into staticlibs-ssl_error_reporting 2025-10-25 19:09:09 -04:00
yhirose bae40fcdf2 Resolve #2237 2025-10-25 16:48:45 -04:00
crueter db561f5552 [cmake] FindBrotli: do not add Brotli:: targets if they already exist (#2249)
Not checking for this is terrible practice.
2025-10-16 09:59:15 -04:00
Andrea Pappacoda 35c52c1ab9 build(meson): use C++17 for gtest >= 1.17.0 (#2241) 2025-09-20 15:06:49 -04:00
Alex Kasko 23ff9a5605 Fix error reporting in SSLClient
When the `SSLClient` is used to connect to a plain-HTTP server (which
can happen in clients due to some end-user misconfiguration) it can
return a failure from the `send()` call without setting the `Error`
reference to the corresponding error code. This can cause problems to
callers, that may expect that, when the check like this is passed on
the response:

```c++
if (res.error() == Error::Success)
```

then they can access the response contents with `res.value()`. When
`SSLClient`'s connection fails - the contents `unique_ptr` is not set
and an attemt to access it causes UB.

This change fixes the `SSLClient::create_and_connect_socket` method
making sure that, the `Error` value is set correctly when the
`is_valid()` check fails.
2025-09-18 13:44:39 +01:00
yhirose 41be1e24e3 Code cleanup 2025-09-15 07:59:53 -04:00
Jonas van den Berg 6e52d0a057 Fix UB by use of dangling references in getaddrinfo_with_timeout (#2232)
* Fix use of dangling references

When the resolve thread is detached, local variables were still used, which could lead to a program crash.

* Add test to verify dangling ref fix

* Add missing brace initialization

* Assert that the remaining fields are really zeroed

* Fix use of chrono literals
2025-09-14 20:05:09 -04:00
apocelipes f72b4582e6 Fix: Fix Windows Cross-Compilation (#2234) 2025-09-14 08:05:51 -04:00
yhirose 89c932f313 Release v0.26.0 2025-08-29 16:05:44 -04:00
yhirose eb5a65e0df Fix #2217 2025-08-29 15:01:59 -04:00
kgokalp 7a3b92bbd9 Fix: handle EAI_ALLDONE from gai_suspend in getaddrinfo_with_timeout (#2228) 2025-08-28 11:08:32 -04:00
yhirose eb11032797 Fix platform problem 2025-08-27 00:31:14 -04:00
yhirose 54e75dc8ef Add manual run 2025-08-26 23:34:18 -04:00
yhirose b20b5fdd1f Add 'release-docker' workflow 2025-08-26 23:18:59 -04:00
yhirose f4cc542d4b Fix Dockerfile problem with CMD 2025-08-26 22:17:54 -04:00
yhirose 4285d33992 Fix #2223 (#2224)
* Fix #2223

* Fix build error
2025-08-26 21:42:13 -04:00
yhirose 92b4f53012 clang-format 2025-08-26 20:29:43 -04:00
tejas b8e21eac89 Initialize start time for server (#2220)
* Initialize start time for server

By initializing start_time_ for server, I hope to measure the time taken to process a request at the end maybe in the set_logger callback and print it.

I only see current usage in client with server retaining the inital min value

* Add test to verify start time is initialized

* Address review comments

* run clang format
2025-08-26 15:34:13 -04:00
Sergey 3fae5f1473 osx: fix inconsistent use of the macro TARGET_OS_OSX (#2222)
* osx: fix inconsistent use of the macro `TARGET_OS_OSX`

Fixed the build error on iOS:

```
httplib.h:3583:3: error: unknown type name 'CFStringRef'
  870 |   CFStringRef hostname_ref = CFStringCreateWithCString(
```

Note, `TARGET_OS_OSX` is defined but is 0 when `TARGET_OS_IOS` is 1,
and vise versa. Hence, `TARGET_OS_MAC` should have been used, that is
set to 1 for the both targets.

* improve: non-blocking getaddrinfo() for all mac target variants

`TARGET_OS_MAC` should have been used, that is set to 1 for all other
targets: OSX, IPHONE (IOS, TV, WATCH, VISION, BRIDGE), SIMULATOR,
DRIVERKIT.
2025-08-26 12:46:51 -04:00
Andrea Pappacoda fe7fe15d2e build(meson): fix new build option names (#2208)
This is a follow-up to commit 4ff7a1c858,
which introduced new simplified build options and deprecated the old
ones. I forgot to also change the various get_option() calls,
effectively rendering the new option names useless, as they would not
get honoured.
2025-08-19 15:22:08 -04:00
Thomas Beutlich fbd6ce7a3f Make code sample compilable (#2207) 2025-08-14 06:57:39 -04:00
Thomas Beutlich dffce89514 #2201 Fix 32-bit MSVC compiler error due to unknown command #warning (#2202) 2025-08-12 17:06:09 -04:00
yhirose 3f44c80fd3 Release v0.25.0 2025-08-07 20:58:39 -04:00
yhirose a2bb6f6c1e Update docker/main.cc 2025-08-07 20:57:37 -04:00
Thomas Beutlich 7012e765e1 CMake: Check pointer size at configure time (#2197)
* Check pointer size at configure time

* Relax check to warning
2025-08-07 17:14:19 -04:00
yhirose b1c1fa2dc6 Code cleanup 2025-08-07 00:09:09 -04:00
yhirose fbee136dca Fix #2193. Allow _WIN32 2025-08-06 23:12:33 -04:00
yhirose 70cca55caf Workaround for chocolatey issue with the latest OpenSSL 2025-08-06 17:54:08 -04:00
yhirose cdaed14925 Update README 2025-08-06 17:41:03 -04:00
yhirose b52d7d8411 ErrorLogger support (#870) (#2195) 2025-08-06 17:38:18 -04:00
Thomas Beutlich acf28a362d #2191 Check for minimum required Windows version (#2192) 2025-08-01 20:16:43 -04:00
yhirose 0b3758ec36 Fix problem with Windows version check 2025-07-30 17:39:40 -04:00
yhirose a5d4c143e5 Release v0.24.0 2025-07-29 19:47:48 -04:00
yhirose c0c36f021d Fix #2184, #2185 (#2190)
* Fix #2184, #2185

* Fix build error

* Update

* Update
2025-07-29 19:29:37 -04:00
yhirose 8e8a23e3d2 Fix #2187 2025-07-24 19:35:47 -04:00
yhirose 890a2dd85d Fix #2189 2025-07-24 17:04:59 -04:00
yhirose ca5fe354fb Release v0.23.1 2025-07-16 17:59:52 -04:00
Benjamin Gilbert dd98d2a24d build(meson): warn/fail on 32-bit machines (#2181)
On 32-bit Windows, meson setup fails with an unclear error:

    meson.build:25:16: ERROR: Could not get define 'CPPHTTPLIB_VERSION'

The actual problem is that httplib.h #errors out.

Have the Meson logic explicitly check for a 32-bit host and warn or error,
matching the check in httplib.h.  Phrase the Windows error in a way that
triggers WrapDB CI's unsupported architecture check.
2025-07-16 12:47:51 -04:00
yhirose 1f110b54d8 Chang #error to #warning for the 32-bit environment check except 32-bit Windows 2025-07-11 22:44:29 -04:00
yhirose 7b6867bcdf Fix #2021 (#2180) 2025-07-10 22:01:41 -04:00
yhirose 8b28577ec6 Resolve #366 2025-07-10 01:07:44 -04:00
yhirose 55b38907dc Resolve #1264 2025-07-10 00:58:52 -04:00
yhirose 53ea9e8bb4 Fix #2111 (#2179) 2025-07-10 00:47:45 -04:00
yhirose ecfd84c171 Release v0.23.0 2025-07-09 23:57:47 -04:00
yhirose b5b2a1d1c8 Change uint64_t to size_t 2025-07-09 18:11:38 -04:00
yhirose 17ba303889 Merge commit from fork
* Fix HTTP Header Smuggling due to insecure trailers merge

* Improve performance
2025-07-09 07:10:09 -04:00
yhirose 802743264c Remove incorrect comment 2025-07-08 23:53:52 -04:00
yhirose 9dbaed75ef Fix #2175 (#2177)
* Fix #2175

* Update
2025-07-08 23:04:34 -04:00
yhirose c551e97297 Add .pre-commit-config.yaml 2025-07-08 21:46:03 -04:00
Andrea Pappacoda 4ff7a1c858 build(meson): simplify build options (#2176)
The "cpp-httplib_" prefix of build options is now dropped, as Meson
build options are already namespaced for each project. The old names
remain as deprecated aliases for the new ones.
2025-07-08 17:23:46 -04:00
yhirose 082acacd45 Merge commit from fork
* Fix Persistency of Unbounded Memory Allocation in Chunked/No-Length Requests Vulnerability

* Revert HTTP status code from 413 to 400
2025-07-08 17:11:13 -04:00
yhirose 52163ed982 Fix #2148 (#2173)
* Fix #2148

* Removed 32bit environment

* buld-error-check-on-32bit

* Use 32bit depedency from Windows
2025-07-07 21:30:08 -04:00
Andrea Pappacoda af7a69bcf6 build(meson): add non_blocking_getaddrinfo option (#2174)
This new option automatically enables the new non-blocking name
resolution when the appropriate libraries are found, automatically
adding them to the list of required dependencies. It will gracefully
fall back to the old behaviour when no library is found.

This complements commit ea850cbfa7.
2025-07-07 21:20:29 -04:00
yhirose 145fc8b021 Proxy test (#2172)
* Add proxy test on CI

* Add Brotli and Zstd dev packages to proxy test workflow

* Fix Docker Compose command for GitHub Actions compatibility

* Add proxy readiness check and netcat dependency

* Use netcat-openbsd instead of virtual netcat package

* Add proxy startup delay and debug logging
2025-07-06 22:00:41 -04:00
yhirose af73377611 Fix #1578 (#2171)
* Fix #1578

* Update README

* Update

* Update

* Update

* Update

* Update

* Update
2025-07-06 21:27:24 -04:00
yhirose a3f5569196 Fix #2082 (#2170) 2025-07-05 20:30:31 -04:00
yhirose a636a094bf Fix #1656 2025-07-05 20:22:57 -04:00
yhirose cb85e573de Fix #1416 (#2169)
* Fix #1416

* Update

* Update
2025-07-05 15:17:53 -04:00
yhirose 120405beac clang-format 2025-07-05 07:13:13 -04:00
KTGH ceff2c1154 Add non-blocking getaddrinfo option to Cmake (#2168)
Adds Cmake option HTTPLIB_USE_NON_BLOCKING_GETADDRINFO default on.
Also adds the HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO

Ref #1601, #2167, and https://github.com/yhirose/cpp-httplib/issues/1601#issuecomment-3021357070
2025-07-05 07:07:59 -04:00
yhirose 0c08c378d7 Simplify benchmark 2025-07-05 00:11:59 -04:00
yhirose 9a0571513e Fix Makefile 2025-07-04 22:30:33 -04:00
yhirose cfb56c0b78 Fix #1818 2025-07-04 21:09:05 -04:00
yhirose 083fe43ad3 Remove httpwatch.com dependency 2025-07-04 20:32:48 -04:00
yhirose 9e36247665 clang-format 2025-07-04 20:31:31 -04:00
yhirose fd03483237 Fix warnings 2025-07-04 18:15:39 -04:00
yhirose d5409ab541 Fix warnings 2025-07-03 19:58:28 -04:00
yhirose 0b875e0747 Remove unnecessary parameters 2025-06-30 21:32:29 -04:00
yhirose 292f9a6c55 Add CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO 2025-06-30 21:14:58 -04:00
yhirose 7c303bb871 Launch proxy server before proxy test 2025-06-29 00:17:19 -04:00
yhirose ea850cbfa7 Fix #1601 (#2167)
* Fix #1601

* clang-format

* Fix Windows problem

* Use GetAddrInfoEx on Windows

* Fix Windows problem

* Add getaddrinfo_a

* clang-format

* Adjust Benchmark Test

* Test

* Fix Bench test

* Fix build error

* Fix build error

* Fix Makefile

* Fix build error

* Fix buid error
2025-06-29 00:13:09 -04:00
yhirose e6ff3d7ac2 Cleaner API (#2166)
* Cleaner API

* Fix Windows build error
2025-06-28 00:37:01 -04:00
yhirose e1ab5a604b Proxy problems (#2165)
* Fix proxy problems

* Auto redirect problem (http → https → https)
2025-06-28 00:14:01 -04:00
yhirose 696c02f2bc Update README 2025-06-26 23:52:03 -04:00
yhirose a183dba9fc clang-format 2025-06-25 17:08:00 -04:00
yhirose d37373a983 Performance investigation 2025-06-25 16:46:49 -04:00
yhirose b2bf172393 Fix #1551 2025-06-24 19:40:20 -04:00
yhirose 1729aa8c1f Issue 2162 (#2163)
* Resolve #2162

* Update
2025-06-24 17:37:30 -04:00
yhirose aabd0634ae Fix warnings created by #2152 2025-06-24 17:12:12 -04:00
herbrechtsmeier de5a255ac6 Fix bad request for multipart form data with boundary split (#2159)
* Add test for multipart form data with boundary split

Add a test for multipart form data requests with a large header which
leads to a split inside the boundary because of the read buffer size
inside the SocketStream class.

* Fix bad request for multipart form data with boundary split

Fix a bad request (400) response for multipart form data requests with
a large header which leads to a split inside the boundary because of the
read buffer size inside the SocketStream class. The parse function
inside the MultipartFormDataParser wrongly erase the receive buffer if
it doesn't find the boundary inside the buffer during first call.
2025-06-24 12:57:32 -04:00
yhirose b6c55c6030 Release v0.22.0 2025-06-24 08:01:53 -04:00
yhirose 28dcf379e8 Merge commit from fork 2025-06-24 07:56:00 -04:00
yhirose 91e79e9a63 Fix #1777 (#2160)
* Add benchmark unit test

* Update

* Update

* Update

* Change the default value of CPPHTTPLIB_IDLE_INTERVAL_USECOND to 1ms
2025-06-24 07:44:10 -04:00
yhirose 27879c4874 Fix #2157 (#2158)
* Fix #2157

* Fix Windows build error: wrap std::max in parentheses to avoid macro conflict

- On Windows, max/min are often defined as macros by windows.h
- This causes compilation errors with std::max/std::min
- Solution: use (std::max) to prevent macro expansion
- Fixes CI build error: error C2589: '(': illegal token on right side of '::'

Fixes: error in coalesce_ranges function on line 5376
2025-06-23 08:35:56 -04:00
DSiekmeier 08a0452fb2 Update README.md (#2156)
According to the curl manpage, the option is called "--max-time".
2025-06-18 07:05:23 -04:00
yhirose 3a1f379e75 Release v0.21.0 2025-06-09 22:19:30 -04:00
Marek Kwasecki fd8da4d8e4 Feature/multipart headers (#2152)
* Adds headers to multipart form data

Adds a `headers` field to the `MultipartFormData` struct.

Populates this field by parsing headers from the multipart form data.
This allows access to specific headers associated with each form data part.

* Adds multipart header access test

Verifies the correct retrieval of headers from multipart form data file parts.

Ensures that custom and content-related headers are accessible and parsed as expected.

* Enables automatic test discovery with GoogleTest

Uses `gtest_discover_tests` to automatically find and run tests, simplifying test maintenance and improving discoverability.

* Removes explicit GoogleTest include

* Refactors header parsing logic

Improves header parsing by using a dedicated parsing function,
resulting in cleaner and more robust code.

This change also adds error handling during header parsing,
returning an error and marking the request as invalid
if parsing fails.

* clang-format corrected

* Renames variable for better readability.

Renames the `customHeader` variable to `custom_header` for improved code readability and consistency.

* typo
2025-06-09 15:59:25 -04:00
yhirose 365cbe37fa Fix #2101 2025-05-25 21:56:28 -04:00
Piotr 4a7aae5469 Detect if afunix.h exists (#2145)
Signed-off-by: Piotr Stankiewicz <piotr.stankiewicz@docker.com>
2025-05-23 23:14:48 -04:00
yhirose fd324e1412 Add KeepAliveTest.MaxCount 2025-05-20 21:41:50 +09:00
QWERTIOX 366eda72dc Specify version in meson.build (#2139)
I prefer to specify version when I'm calling dependency() and lack of makes me unable to do it
2025-05-16 10:02:32 -04:00
yhirose c216dc94d2 Code cleanup 2025-05-09 18:45:31 +09:00
yhirose 61893a00a4 Fix #2135 2025-05-03 22:50:47 +09:00
34 changed files with 6793 additions and 22326 deletions
+51
View File
@@ -0,0 +1,51 @@
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 }}
+23 -5
View File
@@ -40,7 +40,7 @@ jobs:
clang-format --version
cd test && make style_check
ubuntu:
build-error-check-on-32bit:
runs-on: ubuntu-latest
if: >
(github.event_name == 'push') ||
@@ -53,10 +53,28 @@ jobs:
- arch_flags: -m32
arch_suffix: :i386
name: (32-bit)
- arch_flags:
arch_suffix:
name: (64-bit)
name: ubuntu ${{ matrix.config.name }}
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 (expect failure)
run: cd test && make test EXTRA_CXXFLAGS="${{ matrix.config.arch_flags }}"
continue-on-error: true
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')
steps:
- name: checkout
uses: actions/checkout@v4
+20
View File
@@ -0,0 +1,20 @@
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
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 netcat-openbsd
- name: Run proxy tests
run: |
cd test && make proxy
+14 -2
View File
@@ -1,16 +1,28 @@
tags
# Ignore executables (no extension) but not source files
example/server
!example/server.*
example/client
!example/client.*
example/hello
!example/hello.*
example/simplecli
!example/simplecli.*
example/simplesvr
!example/simplesvr.*
example/benchmark
!example/benchmark.*
example/redirect
example/sse*
!example/redirect.*
example/ssecli
example/ssesvr
example/upload
!example/upload.*
example/one_time_request
!example/one_time_request.*
example/server_and_client
!example/server_and_client.*
example/*.pem
test/httplib.cc
test/httplib.h
@@ -42,4 +54,4 @@ ipch
*.pyc
.*
!/.gitattributes
!/.travis.yml
!/.github
+7
View File
@@ -0,0 +1,7 @@
repos:
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v18.1.8 # 最新バージョンを使用
hooks:
- id: clang-format
files: \.(cpp|cc|h)$
args: [-i] # インプレースで修正
+24 -5
View File
@@ -10,6 +10,7 @@
* HTTPLIB_REQUIRE_BROTLI (default off)
* HTTPLIB_REQUIRE_ZSTD (default off)
* HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on)
* HTTPLIB_USE_NON_BLOCKING_GETADDRINFO (default on)
* HTTPLIB_COMPILE (default off)
* HTTPLIB_INSTALL (default on)
* HTTPLIB_TEST (default off)
@@ -49,6 +50,7 @@
* 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_CERTS_FROM_MACOSX_KEYCHAIN - 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).
@@ -104,19 +106,35 @@ 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_USE_CERTS_FROM_MACOSX_KEYCHAIN "Enable feature to load system certs from the Apple Keychain." ON)
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)
# Defaults to static library
option(BUILD_SHARED_LIBS "Build the library as a shared library instead of static. Has no effect if using header-only." OFF)
if (BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
if(BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
# Necessary for Windows if building shared libs
# See https://stackoverflow.com/a/40743080
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
if(CMAKE_SYSTEM_VERSION)
if(${CMAKE_SYSTEM_VERSION} VERSION_LESS "10.0.0")
message(SEND_ERROR "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()
if(CMAKE_SIZEOF_VOID_P LESS 8)
message(WARNING "Pointer size ${CMAKE_SIZEOF_VOID_P} is not supported. Please use a 64-bit compiler.")
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_CERTS_FROM_MACOSX_KEYCHAIN ${HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN})
set(HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO ${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO})
# Threads needed for <thread> on some systems, and for <pthread.h> on Linux
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
@@ -239,8 +257,8 @@ add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_11)
target_include_directories(${PROJECT_NAME} SYSTEM ${_INTERFACE_OR_PUBLIC}
$<BUILD_INTERFACE:${_httplib_build_includedir}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
$<BUILD_INTERFACE:${_httplib_build_includedir}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
# Always require threads
@@ -269,6 +287,7 @@ target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
$<$<BOOL:${HTTPLIB_IS_USING_ZSTD}>:CPPHTTPLIB_ZSTD_SUPPORT>
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>,$<BOOL:${HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN}>>:CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN>
$<$<BOOL:${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO}>:CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO>
)
# CMake configuration files installation directory
@@ -335,6 +354,6 @@ if(HTTPLIB_INSTALL)
endif()
if(HTTPLIB_TEST)
include(CTest)
add_subdirectory(test)
include(CTest)
add_subdirectory(test)
endif()
+3 -1
View File
@@ -8,4 +8,6 @@ FROM scratch
COPY --from=builder /build/server /server
COPY docker/html/index.html /html/index.html
EXPOSE 80
CMD ["/server"]
ENTRYPOINT ["/server"]
CMD ["--host", "0.0.0.0", "--port", "80", "--mount", "/:./html"]
+334 -24
View File
@@ -5,7 +5,7 @@ cpp-httplib
A C++11 single-file header-only cross platform HTTP/HTTPS library.
It's extremely easy to setup. Just include the **httplib.h** file in your code!
It's extremely easy to set up. Just include the **httplib.h** file in your code!
> [!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.
@@ -85,6 +85,48 @@ cli.enable_server_hostname_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.
### SSL Error Handling
When SSL operations fail, cpp-httplib provides detailed error information through two separate error fields:
```c++
#define CPPHTTPLIB_OPENSSL_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, OpenSSL error: "
<< std::hex << res.ssl_openssl_error() << std::endl;
break;
case httplib::Error::SSLServerVerification:
std::cout << "SSL verification failed, X509 error: "
<< res.ssl_openssl_error() << std::endl;
break;
case httplib::Error::SSLServerHostnameVerification:
std::cout << "SSL hostname verification failed, X509 error: "
<< res.ssl_openssl_error() << std::endl;
break;
default:
std::cout << "HTTP error: " << httplib::to_string(err) << std::endl;
}
}
```
Server
------
@@ -187,7 +229,7 @@ svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c");
svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h");
```
The followings are built-in mappings:
The following are built-in mappings:
| Extension | MIME Type | Extension | MIME Type |
| :--------- | :-------------------------- | :--------- | :-------------------------- |
@@ -201,7 +243,7 @@ The followings are built-in mappings:
| 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 | application/javascript |
| 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 |
@@ -226,9 +268,45 @@ 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 auto& req, const auto& res) {
your_logger(req, res);
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;
});
```
@@ -285,16 +363,96 @@ svr.set_post_routing_handler([](const auto& req, auto& res) {
});
```
### 'multipart/form-data' POST data
### Pre request handler
```cpp
svr.Post("/multipart", [&](const auto& req, auto& res) {
auto size = req.files.size();
auto ret = req.has_file("name1");
const auto& file = req.get_file_value("name1");
// file.filename;
// file.content_type;
// file.content;
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;
});
```
### 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;
}
// Save to disk
std::ofstream ofs(file.filename, 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");
});
```
@@ -305,16 +463,29 @@ 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
MultipartFormDataItems files;
// This approach allows streaming processing of large files
std::vector<FormData> items;
content_reader(
[&](const MultipartFormData &file) {
files.push_back(file);
[&](const FormData &item) {
items.push_back(item);
return true;
},
[&](const char *data, size_t data_length) {
files.back().content.append(data, 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) {
@@ -435,7 +606,7 @@ svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
### Keep-Alive connection
```cpp
svr.set_keep_alive_max_count(2); // Default is 5
svr.set_keep_alive_max_count(2); // Default is 100
svr.set_keep_alive_timeout(10); // Default is 5
```
@@ -562,12 +733,59 @@ enum Error {
SSLConnection,
SSLLoadingCerts,
SSLServerVerification,
SSLServerHostnameVerification,
UnsupportedMultipartBoundaryChars,
Compression,
ConnectionTimeout,
ProxyConnection,
};
```
### Client Logging
#### Access Logging
```cpp
cli.set_logger([](const httplib::Request& req, const httplib::Response& res) {
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
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++
@@ -618,7 +836,7 @@ auto res = cli.Post("/post", params);
### POST with Multipart Form Data
```c++
httplib::MultipartFormDataItems items = {
httplib::UploadFormDataItems items = {
{ "text1", "text default", "", "" },
{ "text2", "aωb", "", "" },
{ "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
@@ -655,8 +873,8 @@ 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-timeout` option
svr.set_max_timeout(5000); // 5 seconds
// This method works the same as curl's `--max-time` option
cli.set_max_timeout(5000); // 5 seconds
```
### Receive content with a content receiver
@@ -721,7 +939,7 @@ auto res = cli.Post(
httplib::Client cli(url, port);
// prints: 0 / 000 bytes => 50% complete
auto res = cli.Get("/", [](uint64_t len, uint64_t total) {
auto res = cli.Get("/", [](size_t len, size_t total) {
printf("%lld / %lld bytes => %d%% complete\n",
len, total,
(int)(len*100/total));
@@ -820,6 +1038,45 @@ res->status; // 200
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);
```
Compression
-----------
@@ -830,6 +1087,7 @@ The server can apply compression to the following MIME type contents:
* application/javascript
* application/json
* application/xml
* application/protobuf
* application/xhtml+xml
### Zlib Support
@@ -841,13 +1099,18 @@ The server can apply compression to the following MIME type contents:
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", "gzip, deflate, br"}});
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.
@@ -887,7 +1150,36 @@ httplib::Client cli("./my-socket.sock");
cli.set_address_family(AF_UNIX);
```
"my-socket.sock" can be a relative path or an absolute path. You 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.
"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 <httplib.h>
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.
Split httplib.h into .h and .cc
@@ -938,6 +1230,24 @@ Serving HTTP on 0.0.0.0 port 80 ...
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 `<regex>` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions).
+2 -32
View File
@@ -20,36 +20,6 @@ run : server
server : cpp-httplib/main.cpp ../httplib.h
@g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib/main.cpp
# cpp-httplib v0.19.0
bench-v19: server-v19
@echo "---------------------\n cpp-httplib v0.19.0\n---------------------\n"
@./server-v19 & export PID=$$!; $(BENCH); kill $${PID}
@echo ""
monitor-v19: server-v19
@./server-v19 & export PID=$$!; $(MONITOR); kill $${PID}
run-v19 : server-v19
@./server-v19
server-v19 : cpp-httplib-v19/main.cpp cpp-httplib-v19/httplib.h
@g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib-v19/main.cpp
# cpp-httplib v0.18.0
bench-v18: server-v18
@echo "---------------------\n cpp-httplib v0.18.0\n---------------------\n"
@./server-v18 & export PID=$$!; $(BENCH); kill $${PID}
@echo ""
monitor-v18: server-v18
@./server-v18 & export PID=$$!; $(MONITOR); kill $${PID}
run-v18 : server-v18
@./server-v18
server-v18 : cpp-httplib-v18/main.cpp cpp-httplib-v18/httplib.h
@g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib-v18/main.cpp
# crow
bench-crow: server-crow
@echo "-------------\n Crow v1.2.0\n-------------\n"
@@ -66,9 +36,9 @@ server-crow : crow/main.cpp
@g++ -o $@ $(CXXFLAGS) crow/main.cpp
# misc
build: server server-v18 server-v19 server-crow
build: server server-crow
bench-all: bench-crow bench bench-v19 bench-v18
bench-all: bench-crow bench
issue:
bombardier -c 10 -d 30s localhost:8080
File diff suppressed because it is too large Load Diff
-12
View File
@@ -1,12 +0,0 @@
#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 diff suppressed because it is too large Load Diff
-12
View File
@@ -1,12 +0,0 @@
#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);
}
-2
View File
@@ -1,2 +0,0 @@
rm -f httplib.h
wget https://raw.githubusercontent.com/yhirose/cpp-httplib/v$1/httplib.h
+7 -2
View File
@@ -86,7 +86,9 @@ foreach(_listvar "common;common" "decoder;dec" "encoder;enc")
# Check if the target was already found by Pkgconf
if(TARGET PkgConfig::Brotli_${_component_name}${_brotli_stat_str})
# ALIAS since we don't want the PkgConfig namespace on the Cmake library (for end-users)
add_library(Brotli::${_component_name} ALIAS PkgConfig::Brotli_${_component_name}${_brotli_stat_str})
if (NOT TARGET Brotli::${_component_name})
add_library(Brotli::${_component_name} ALIAS PkgConfig::Brotli_${_component_name}${_brotli_stat_str})
endif()
# Tells HANDLE_COMPONENTS we found the component
set(Brotli_${_component_name}_FOUND TRUE)
@@ -139,7 +141,10 @@ foreach(_listvar "common;common" "decoder;dec" "encoder;enc")
# Tells HANDLE_COMPONENTS we found the component
set(Brotli_${_component_name}_FOUND TRUE)
add_library("Brotli::${_component_name}" UNKNOWN IMPORTED)
if (NOT TARGET Brotli::${_component_name})
add_library("Brotli::${_component_name}" UNKNOWN IMPORTED)
endif()
# Attach the literal library and include dir to the IMPORTED target for the end-user
set_target_properties("Brotli::${_component_name}" PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${Brotli_INCLUDE_DIR}"
+1
View File
@@ -7,6 +7,7 @@ set(HTTPLIB_IS_USING_OPENSSL @HTTPLIB_IS_USING_OPENSSL@)
set(HTTPLIB_IS_USING_ZLIB @HTTPLIB_IS_USING_ZLIB@)
set(HTTPLIB_IS_COMPILED @HTTPLIB_COMPILE@)
set(HTTPLIB_IS_USING_BROTLI @HTTPLIB_IS_USING_BROTLI@)
set(HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO @HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO@)
set(HTTPLIB_VERSION @PROJECT_VERSION@)
include(CMakeFindDependencyMacro)
+264 -42
View File
@@ -5,77 +5,299 @@
// MIT License
//
#include <atomic>
#include <chrono>
#include <ctime>
#include <format>
#include <iomanip>
#include <iostream>
#include <signal.h>
#include <sstream>
#include <httplib.h>
constexpr auto error_html = R"(<html>
<head><title>{} {}</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>cpp-httplib/{}</center>
</body>
</html>
)";
using namespace httplib;
void sigint_handler(int s) { exit(1); }
const auto SERVER_NAME =
std::format("cpp-httplib-server/{}", CPPHTTPLIB_VERSION);
std::string time_local() {
auto p = std::chrono::system_clock::now();
auto t = std::chrono::system_clock::to_time_t(p);
Server svr;
void signal_handler(int signal) {
if (signal == SIGINT || signal == SIGTERM) {
std::cout << "\nReceived signal, shutting down gracefully...\n";
svr.stop();
}
}
std::string get_time_format() {
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::localtime(&t), "%d/%b/%Y:%H:%M:%S %z");
ss << std::put_time(std::localtime(&time_t), "%d/%b/%Y:%H:%M:%S %z");
return ss.str();
}
std::string log(auto &req, auto &res) {
auto remote_user = "-"; // TODO:
auto request = std::format("{} {} {}", req.method, req.path, req.version);
auto body_bytes_sent = res.get_header_value("Content-Length");
auto http_referer = "-"; // TODO:
auto http_user_agent = req.get_header_value("User-Agent", "-");
std::string get_error_time_format() {
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
// NOTE: From NGINX default access log format
// log_format combined '$remote_addr - $remote_user [$time_local] '
// '"$request" $status $body_bytes_sent '
// '"$http_referer" "$http_user_agent"';
return std::format(R"({} - {} [{}] "{}" {} {} "{}" "{}")", req.remote_addr,
remote_user, time_local(), request, res.status,
body_bytes_sent, http_referer, http_user_agent);
std::stringstream ss;
ss << std::put_time(std::localtime(&time_t), "%Y/%m/%d %H:%M:%S");
return ss.str();
}
int main(int argc, const char **argv) {
signal(SIGINT, sigint_handler);
// NGINX Combined log format:
// $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent
// "$http_referer" "$http_user_agent"
void nginx_access_logger(const Request &req, const Response &res) {
std::string remote_user =
"-"; // cpp-httplib doesn't have built-in auth user tracking
auto time_local = get_time_format();
auto request = std::format("{} {} {}", req.method, req.path, req.version);
auto status = res.status;
auto body_bytes_sent = res.body.size();
auto http_referer = req.get_header_value("Referer");
if (http_referer.empty()) http_referer = "-";
auto http_user_agent = req.get_header_value("User-Agent");
if (http_user_agent.empty()) http_user_agent = "-";
auto base_dir = "./html";
auto host = "0.0.0.0";
auto port = 80;
std::cout << std::format("{} - {} [{}] \"{}\" {} {} \"{}\" \"{}\"",
req.remote_addr, remote_user, time_local, request,
status, body_bytes_sent, http_referer,
http_user_agent)
<< std::endl;
}
httplib::Server svr;
// NGINX Error log format:
// YYYY/MM/DD HH:MM:SS [level] message, client: client_ip, request: "request",
// host: "host"
void nginx_error_logger(const Error &err, const Request *req) {
auto time_local = get_error_time_format();
std::string level = "error";
svr.set_error_handler([](auto & /*req*/, auto &res) {
auto body =
std::format(error_html, res.status, httplib::status_message(res.status),
CPPHTTPLIB_VERSION);
if (req) {
auto request =
std::format("{} {} {}", req->method, req->path, req->version);
auto host = req->get_header_value("Host");
if (host.empty()) host = "-";
res.set_content(body, "text/html");
std::cerr << std::format("{} [{}] {}, client: {}, request: "
"\"{}\", host: \"{}\"",
time_local, level, to_string(err),
req->remote_addr, request, host)
<< std::endl;
} else {
// If no request context, just log the error
std::cerr << std::format("{} [{}] {}", time_local, level, to_string(err))
<< std::endl;
}
}
void print_usage(const char *program_name) {
std::cout << "Usage: " << program_name << " [OPTIONS]" << std::endl;
std::cout << std::endl;
std::cout << "Options:" << std::endl;
std::cout << " --host <hostname> Server hostname (default: localhost)"
<< std::endl;
std::cout << " --port <port> Server port (default: 8080)"
<< std::endl;
std::cout << " --mount <mount:path> Mount point and document root"
<< std::endl;
std::cout << " Format: mount_point:document_root"
<< std::endl;
std::cout << " (default: /:./html)" << std::endl;
std::cout << " --trusted-proxy <ip> Add trusted proxy IP address"
<< std::endl;
std::cout << " (can be specified multiple times)"
<< std::endl;
std::cout << " --version Show version information"
<< std::endl;
std::cout << " --help Show this help message" << std::endl;
std::cout << std::endl;
std::cout << "Examples:" << std::endl;
std::cout << " " << program_name
<< " --host localhost --port 8080 --mount /:./html" << std::endl;
std::cout << " " << program_name
<< " --host 0.0.0.0 --port 3000 --mount /api:./api" << std::endl;
std::cout << " " << program_name
<< " --trusted-proxy 192.168.1.100 --trusted-proxy 10.0.0.1"
<< std::endl;
}
struct ServerConfig {
std::string hostname = "localhost";
int port = 8080;
std::string mount_point = "/";
std::string document_root = "./html";
std::vector<std::string> trusted_proxies;
};
enum class ParseResult { SUCCESS, HELP_REQUESTED, VERSION_REQUESTED, ERROR };
ParseResult parse_command_line(int argc, char *argv[], ServerConfig &config) {
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
print_usage(argv[0]);
return ParseResult::HELP_REQUESTED;
} else if (strcmp(argv[i], "--host") == 0) {
if (i + 1 >= argc) {
std::cerr << "Error: --host requires a hostname argument" << std::endl;
print_usage(argv[0]);
return ParseResult::ERROR;
}
config.hostname = argv[++i];
} else if (strcmp(argv[i], "--port") == 0) {
if (i + 1 >= argc) {
std::cerr << "Error: --port requires a port number argument"
<< std::endl;
print_usage(argv[0]);
return ParseResult::ERROR;
}
config.port = std::atoi(argv[++i]);
if (config.port <= 0 || config.port > 65535) {
std::cerr << "Error: Invalid port number. Must be between 1 and 65535"
<< std::endl;
return ParseResult::ERROR;
}
} else if (strcmp(argv[i], "--mount") == 0) {
if (i + 1 >= argc) {
std::cerr
<< "Error: --mount requires mount_point:document_root argument"
<< std::endl;
print_usage(argv[0]);
return ParseResult::ERROR;
}
std::string mount_arg = argv[++i];
auto colon_pos = mount_arg.find(':');
if (colon_pos == std::string::npos) {
std::cerr << "Error: --mount argument must be in format "
"mount_point:document_root"
<< std::endl;
print_usage(argv[0]);
return ParseResult::ERROR;
}
config.mount_point = mount_arg.substr(0, colon_pos);
config.document_root = mount_arg.substr(colon_pos + 1);
if (config.mount_point.empty() || config.document_root.empty()) {
std::cerr
<< "Error: Both mount_point and document_root must be non-empty"
<< std::endl;
return ParseResult::ERROR;
}
} else if (strcmp(argv[i], "--version") == 0) {
std::cout << CPPHTTPLIB_VERSION << std::endl;
return ParseResult::VERSION_REQUESTED;
} else if (strcmp(argv[i], "--trusted-proxy") == 0) {
if (i + 1 >= argc) {
std::cerr << "Error: --trusted-proxy requires an IP address argument"
<< std::endl;
print_usage(argv[0]);
return ParseResult::ERROR;
}
config.trusted_proxies.push_back(argv[++i]);
} else {
std::cerr << "Error: Unknown option '" << argv[i] << "'" << std::endl;
print_usage(argv[0]);
return ParseResult::ERROR;
}
}
return ParseResult::SUCCESS;
}
bool setup_server(Server &svr, const ServerConfig &config) {
svr.set_logger(nginx_access_logger);
svr.set_error_logger(nginx_error_logger);
// Set trusted proxies if specified
if (!config.trusted_proxies.empty()) {
svr.set_trusted_proxies(config.trusted_proxies);
}
auto ret = svr.set_mount_point(config.mount_point, config.document_root);
if (!ret) {
std::cerr
<< std::format(
"Error: Cannot mount '{}' to '{}'. Directory may not exist.",
config.mount_point, config.document_root)
<< std::endl;
return false;
}
svr.set_file_extension_and_mimetype_mapping("html", "text/html");
svr.set_file_extension_and_mimetype_mapping("htm", "text/html");
svr.set_file_extension_and_mimetype_mapping("css", "text/css");
svr.set_file_extension_and_mimetype_mapping("js", "text/javascript");
svr.set_file_extension_and_mimetype_mapping("json", "application/json");
svr.set_file_extension_and_mimetype_mapping("xml", "application/xml");
svr.set_file_extension_and_mimetype_mapping("png", "image/png");
svr.set_file_extension_and_mimetype_mapping("jpg", "image/jpeg");
svr.set_file_extension_and_mimetype_mapping("jpeg", "image/jpeg");
svr.set_file_extension_and_mimetype_mapping("gif", "image/gif");
svr.set_file_extension_and_mimetype_mapping("svg", "image/svg+xml");
svr.set_file_extension_and_mimetype_mapping("ico", "image/x-icon");
svr.set_file_extension_and_mimetype_mapping("pdf", "application/pdf");
svr.set_file_extension_and_mimetype_mapping("zip", "application/zip");
svr.set_file_extension_and_mimetype_mapping("txt", "text/plain");
svr.set_error_handler([](const Request & /*req*/, Response &res) {
if (res.status == 404) {
res.set_content(
std::format(
"<html><head><title>404 Not Found</title></head>"
"<body><h1>404 Not Found</h1>"
"<p>The requested resource was not found on this server.</p>"
"<hr><p>{}</p></body></html>",
SERVER_NAME),
"text/html");
}
});
svr.set_logger(
[](auto &req, auto &res) { std::cout << log(req, res) << std::endl; });
svr.set_pre_routing_handler([](const Request & /*req*/, Response &res) {
res.set_header("Server", SERVER_NAME);
return Server::HandlerResponse::Unhandled;
});
svr.set_mount_point("/", base_dir);
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
std::cout << std::format("Serving HTTP on {0} port {1} ...", host, port)
return true;
}
int main(int argc, char *argv[]) {
ServerConfig config;
auto result = parse_command_line(argc, argv, config);
switch (result) {
case ParseResult::HELP_REQUESTED:
case ParseResult::VERSION_REQUESTED: return 0;
case ParseResult::ERROR: return 1;
case ParseResult::SUCCESS: break;
}
if (!setup_server(svr, config)) { return 1; }
std::cout << "Serving HTTP on " << config.hostname << ":" << config.port
<< std::endl;
std::cout << "Mount point: " << config.mount_point << " -> "
<< config.document_root << std::endl;
auto ret = svr.listen(host, port);
if (!config.trusted_proxies.empty()) {
std::cout << "Trusted proxies: ";
for (size_t i = 0; i < config.trusted_proxies.size(); ++i) {
if (i > 0) std::cout << ", ";
std::cout << config.trusted_proxies[i];
}
std::cout << std::endl;
}
std::cout << "Press Ctrl+C to shutdown gracefully..." << std::endl;
auto ret = svr.listen(config.hostname, config.port);
std::cout << "Server has been shut down." << std::endl;
return ret ? 0 : 1;
}
+5 -2
View File
@@ -18,7 +18,7 @@ ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
BROTLI_DIR = $(PREFIX)/opt/brotli
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client accept_header
server : server.cc ../httplib.h Makefile
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
@@ -56,9 +56,12 @@ one_time_request : one_time_request.cc ../httplib.h Makefile
server_and_client : server_and_client.cc ../httplib.h Makefile
$(CXX) -o server_and_client $(CXXFLAGS) server_and_client.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
accept_header : accept_header.cc ../httplib.h Makefile
$(CXX) -o accept_header $(CXXFLAGS) accept_header.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
pem:
openssl genrsa 2048 > key.pem
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
clean:
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client *.pem
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client accept_header *.pem
+134
View File
@@ -0,0 +1,134 @@
#include "httplib.h"
#include <iostream>
int main() {
using namespace httplib;
// Example usage of parse_accept_header function
std::cout << "=== Accept Header Parser Example ===" << std::endl;
// Example 1: Simple Accept header
std::string accept1 = "text/html,application/json,text/plain";
std::vector<std::string> result1;
if (detail::parse_accept_header(accept1, result1)) {
std::cout << "\nExample 1: " << accept1 << std::endl;
std::cout << "Parsed order:" << std::endl;
for (size_t i = 0; i < result1.size(); ++i) {
std::cout << " " << (i + 1) << ". " << result1[i] << std::endl;
}
} else {
std::cout << "\nExample 1: Failed to parse Accept header" << std::endl;
}
// Example 2: Accept header with quality values
std::string accept2 = "text/html;q=0.9,application/json;q=1.0,text/plain;q=0.8";
std::vector<std::string> result2;
if (detail::parse_accept_header(accept2, result2)) {
std::cout << "\nExample 2: " << accept2 << std::endl;
std::cout << "Parsed order (sorted by priority):" << std::endl;
for (size_t i = 0; i < result2.size(); ++i) {
std::cout << " " << (i + 1) << ". " << result2[i] << std::endl;
}
} else {
std::cout << "\nExample 2: Failed to parse Accept header" << std::endl;
}
// Example 3: Browser-like Accept header
std::string accept3 = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8";
std::vector<std::string> result3;
if (detail::parse_accept_header(accept3, result3)) {
std::cout << "\nExample 3: " << accept3 << std::endl;
std::cout << "Parsed order:" << std::endl;
for (size_t i = 0; i < result3.size(); ++i) {
std::cout << " " << (i + 1) << ". " << result3[i] << std::endl;
}
} else {
std::cout << "\nExample 3: Failed to parse Accept header" << std::endl;
}
// Example 4: Invalid Accept header examples
std::cout << "\n=== Invalid Accept Header Examples ===" << std::endl;
std::vector<std::string> invalid_examples = {
"text/html;q=1.5,application/json", // q > 1.0
"text/html;q=-0.1,application/json", // q < 0.0
"text/html;q=invalid,application/json", // invalid q value
"invalidtype,application/json", // invalid media type
",application/json" // empty entry
};
for (const auto& invalid_accept : invalid_examples) {
std::vector<std::string> temp_result;
std::cout << "\nTesting invalid: " << invalid_accept << std::endl;
if (detail::parse_accept_header(invalid_accept, temp_result)) {
std::cout << " Unexpectedly succeeded!" << std::endl;
} else {
std::cout << " Correctly rejected as invalid" << std::endl;
}
}
// Example 4: Server usage example
std::cout << "\n=== Server Usage Example ===" << std::endl;
Server svr;
svr.Get("/api/data", [](const Request& req, Response& res) {
// Get Accept header
auto accept_header = req.get_header_value("Accept");
if (accept_header.empty()) {
accept_header = "*/*"; // Default if no Accept header
}
// Parse accept header to get preferred content types
std::vector<std::string> preferred_types;
if (!detail::parse_accept_header(accept_header, preferred_types)) {
// Invalid Accept header
res.status = 400; // Bad Request
res.set_content("Invalid Accept header", "text/plain");
return;
}
std::cout << "Client Accept header: " << accept_header << std::endl;
std::cout << "Preferred types in order:" << std::endl;
for (size_t i = 0; i < preferred_types.size(); ++i) {
std::cout << " " << (i + 1) << ". " << preferred_types[i] << std::endl;
}
// Choose response format based on client preference
std::string response_content;
std::string content_type;
for (const auto& type : preferred_types) {
if (type == "application/json" || type == "application/*" || type == "*/*") {
response_content = "{\"message\": \"Hello, World!\", \"data\": [1, 2, 3]}";
content_type = "application/json";
break;
} else if (type == "text/html" || type == "text/*") {
response_content = "<html><body><h1>Hello, World!</h1><p>Data: 1, 2, 3</p></body></html>";
content_type = "text/html";
break;
} else if (type == "text/plain") {
response_content = "Hello, World!\nData: 1, 2, 3";
content_type = "text/plain";
break;
}
}
if (response_content.empty()) {
// No supported content type found
res.status = 406; // Not Acceptable
res.set_content("No acceptable content type found", "text/plain");
return;
}
res.set_content(response_content, content_type);
std::cout << "Responding with: " << content_type << std::endl;
});
std::cout << "Server configured. You can test it with:" << std::endl;
std::cout << " curl -H \"Accept: application/json\" http://localhost:8080/api/data" << std::endl;
std::cout << " curl -H \"Accept: text/html\" http://localhost:8080/api/data" << std::endl;
std::cout << " curl -H \"Accept: text/plain\" http://localhost:8080/api/data" << std::endl;
std::cout << " curl -H \"Accept: text/html;q=0.9,application/json;q=1.0\" http://localhost:8080/api/data" << std::endl;
return 0;
}
+17 -4
View File
@@ -27,13 +27,26 @@ string dump_headers(const Headers &headers) {
return s;
}
string dump_multipart_files(const MultipartFormDataMap &files) {
string dump_multipart_formdata(const MultipartFormData &form) {
string s;
char buf[BUFSIZ];
s += "--------------------------------\n";
for (const auto &x : files) {
for (const auto &x : form.fields) {
const auto &name = x.first;
const auto &field = x.second;
snprintf(buf, sizeof(buf), "name: %s\n", name.c_str());
s += buf;
snprintf(buf, sizeof(buf), "text length: %zu\n", field.content.size());
s += buf;
s += "----------------\n";
}
for (const auto &x : form.files) {
const auto &name = x.first;
const auto &file = x.second;
@@ -77,7 +90,7 @@ string log(const Request &req, const Response &res) {
s += buf;
s += dump_headers(req.headers);
s += dump_multipart_files(req.files);
s += dump_multipart_formdata(req.form);
s += "--------------------------------\n";
@@ -101,7 +114,7 @@ int main(int argc, const char **argv) {
#endif
svr.Post("/multipart", [](const Request &req, Response &res) {
auto body = dump_headers(req.headers) + dump_multipart_files(req.files);
auto body = dump_headers(req.headers) + dump_multipart_formdata(req.form);
res.set_content(body, "text/plain");
});
+11 -6
View File
@@ -14,11 +14,18 @@ class EventDispatcher {
public:
EventDispatcher() {}
void wait_event(DataSink *sink) {
bool wait_event(DataSink *sink) {
unique_lock<mutex> lk(m_);
int id = id_;
cv_.wait(lk, [&] { return cid_ == id; });
// Wait with timeout to prevent hanging if client disconnects
if (!cv_.wait_for(lk, std::chrono::seconds(5),
[&] { return cid_ == id; })) {
return false; // Timeout occurred
}
sink->write(message_.data(), message_.size());
return true;
}
void send_event(const string &message) {
@@ -71,8 +78,7 @@ int main(void) {
cout << "connected to event1..." << endl;
res.set_chunked_content_provider("text/event-stream",
[&](size_t /*offset*/, DataSink &sink) {
ed.wait_event(&sink);
return true;
return ed.wait_event(&sink);
});
});
@@ -80,8 +86,7 @@ int main(void) {
cout << "connected to event2..." << endl;
res.set_chunked_content_provider("text/event-stream",
[&](size_t /*offset*/, DataSink &sink) {
ed.wait_event(&sink);
return true;
return ed.wait_event(&sink);
});
});
+2 -2
View File
@@ -37,8 +37,8 @@ int main(void) {
});
svr.Post("/post", [](const Request &req, Response &res) {
auto image_file = req.get_file_value("image_file");
auto text_file = req.get_file_value("text_file");
const auto &image_file = req.form.get_file("image_file");
const auto &text_file = req.form.get_file("text_file");
cout << "image file length: " << image_file.content.length() << endl
<< "image file name: " << image_file.filename << endl
+2757 -1210
View File
File diff suppressed because it is too large Load Diff
+36 -12
View File
@@ -13,14 +13,23 @@ project(
'b_lto=true',
'warning_level=3'
],
meson_version: '>=0.62.0'
meson_version: '>=0.63.0'
)
cxx = meson.get_compiler('cpp')
if cxx.sizeof('void *') != 8
if host_machine.system() == 'windows'
error('unsupported architecture: cpp-httplib doesn\'t support 32-bit Windows. Please use a 64-bit compiler.')
else
warning('cpp-httplib doesn\'t support 32-bit platforms. Please use a 64-bit compiler.')
endif
endif
# Check just in case downstream decides to edit the source
# and add a project version
version = meson.project_version()
if version == 'undefined'
cxx = meson.get_compiler('cpp')
version = cxx.get_define('CPPHTTPLIB_VERSION',
prefix: '#include <httplib.h>',
include_directories: include_directories('.')).strip('"')
@@ -30,12 +39,12 @@ endif
deps = [dependency('threads')]
args = []
openssl_dep = dependency('openssl', version: '>=3.0.0', required: get_option('cpp-httplib_openssl'))
openssl_dep = dependency('openssl', version: '>=3.0.0', required: get_option('openssl'))
if openssl_dep.found()
deps += openssl_dep
args += '-DCPPHTTPLIB_OPENSSL_SUPPORT'
if host_machine.system() == 'darwin'
macosx_keychain_dep = dependency('appleframeworks', modules: ['CoreFoundation', 'Security'], required: get_option('cpp-httplib_macosx_keychain'))
macosx_keychain_dep = dependency('appleframeworks', modules: ['CoreFoundation', 'Security'], required: get_option('macosx_keychain'))
if macosx_keychain_dep.found()
deps += macosx_keychain_dep
args += '-DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN'
@@ -43,15 +52,15 @@ if openssl_dep.found()
endif
endif
zlib_dep = dependency('zlib', required: get_option('cpp-httplib_zlib'))
zlib_dep = dependency('zlib', required: get_option('zlib'))
if zlib_dep.found()
deps += zlib_dep
args += '-DCPPHTTPLIB_ZLIB_SUPPORT'
endif
brotli_deps = [dependency('libbrotlicommon', required: get_option('cpp-httplib_brotli'))]
brotli_deps += dependency('libbrotlidec', required: get_option('cpp-httplib_brotli'))
brotli_deps += dependency('libbrotlienc', required: get_option('cpp-httplib_brotli'))
brotli_deps = [dependency('libbrotlicommon', required: get_option('brotli'))]
brotli_deps += dependency('libbrotlidec', required: get_option('brotli'))
brotli_deps += dependency('libbrotlienc', required: get_option('brotli'))
brotli_found_all = true
foreach brotli_dep : brotli_deps
@@ -65,9 +74,24 @@ if brotli_found_all
args += '-DCPPHTTPLIB_BROTLI_SUPPORT'
endif
async_ns_opt = get_option('non_blocking_getaddrinfo')
if host_machine.system() == 'windows'
async_ns_dep = cxx.find_library('ws2_32', required: async_ns_opt)
elif host_machine.system() == 'darwin'
async_ns_dep = dependency('appleframeworks', modules: ['CFNetwork'], required: async_ns_opt)
else
async_ns_dep = cxx.find_library('anl', required: async_ns_opt)
endif
if async_ns_dep.found()
deps += async_ns_dep
args += '-DCPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO'
endif
cpp_httplib_dep = dependency('', required: false)
if get_option('cpp-httplib_compile')
if get_option('compile')
python3 = find_program('python3')
httplib_ch = custom_target(
@@ -87,7 +111,7 @@ if get_option('cpp-httplib_compile')
soversion: version.split('.')[0] + '.' + version.split('.')[1],
install: true
)
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, link_with: lib, sources: httplib_ch[1])
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, link_with: lib, sources: httplib_ch[1], version: version)
import('pkgconfig').generate(
lib,
@@ -98,7 +122,7 @@ if get_option('cpp-httplib_compile')
)
else
install_headers('httplib.h')
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, include_directories: '.')
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, include_directories: '.', version: version)
import('pkgconfig').generate(
name: 'cpp-httplib',
@@ -111,6 +135,6 @@ endif
meson.override_dependency('cpp-httplib', cpp_httplib_dep)
if get_option('cpp-httplib_test')
if get_option('test')
subdir('test')
endif
+16 -6
View File
@@ -2,9 +2,19 @@
#
# SPDX-License-Identifier: MIT
option('cpp-httplib_openssl', type: 'feature', value: 'auto', description: 'Enable OpenSSL support')
option('cpp-httplib_zlib', type: 'feature', value: 'auto', description: 'Enable zlib support')
option('cpp-httplib_brotli', type: 'feature', value: 'auto', description: 'Enable Brotli support')
option('cpp-httplib_macosx_keychain', type: 'feature', value: 'auto', description: 'Enable loading certs from the Keychain on Apple devices')
option('cpp-httplib_compile', type: 'boolean', value: false, description: 'Split the header into a compilable header & source file (requires python3)')
option('cpp-httplib_test', type: 'boolean', value: false, description: 'Build tests')
option('openssl', type: 'feature', value: 'auto', description: 'Enable OpenSSL support')
option('zlib', type: 'feature', value: 'auto', description: 'Enable zlib support')
option('brotli', type: 'feature', value: 'auto', description: 'Enable Brotli support')
option('macosx_keychain', type: 'feature', value: 'auto', description: 'Enable loading certs from the Keychain on Apple devices')
option('non_blocking_getaddrinfo', type: 'feature', value: 'auto', description: 'Enable asynchronous name lookup')
option('compile', type: 'boolean', value: false, description: 'Split the header into a compilable header & source file (requires python3)')
option('test', type: 'boolean', value: false, description: 'Build tests')
# Old option names
option('cpp-httplib_openssl', type: 'feature', deprecated: 'openssl')
option('cpp-httplib_zlib', type: 'feature', deprecated: 'zlib')
option('cpp-httplib_brotli', type: 'feature', deprecated: 'brotli')
option('cpp-httplib_macosx_keychain', type: 'feature', deprecated: 'macosx_keychain')
option('cpp-httplib_non_blocking_getaddrinfo', type: 'feature', deprecated: 'non_blocking_getaddrinfo')
option('cpp-httplib_compile', type: 'boolean', value: false, deprecated: 'compile')
option('cpp-httplib_test', type: 'boolean', value: false, deprecated: 'test')
+32 -5
View File
@@ -1,5 +1,5 @@
CXX = clang++
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow $(EXTRA_CXXFLAGS) # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS -fsanitize=address
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow $(EXTRA_CXXFLAGS) -DCPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS -fsanitize=address
PREFIX ?= $(shell brew --prefix)
@@ -9,7 +9,7 @@ OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPEN
ifneq ($(OS), Windows_NT)
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S), Darwin)
OPENSSL_SUPPORT += -DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN -framework CoreFoundation -framework Security
OPENSSL_SUPPORT += -DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN -framework Security
endif
endif
@@ -21,7 +21,18 @@ BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_
ZSTD_DIR = $(PREFIX)/opt/zstd
ZSTD_SUPPORT = -DCPPHTTPLIB_ZSTD_SUPPORT -I$(ZSTD_DIR)/include -L$(ZSTD_DIR)/lib -lzstd
TEST_ARGS = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(ZSTD_SUPPORT) -pthread -lcurl
LIBS = -lpthread -lcurl
ifneq ($(OS), Windows_NT)
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S), Darwin)
LIBS += -framework CoreFoundation -framework CFNetwork
endif
ifneq ($(UNAME_S), Darwin)
LIBS += -lanl
endif
endif
TEST_ARGS = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(ZSTD_SUPPORT) $(LIBS)
# By default, use standalone_fuzz_target_runner.
# This runner does no fuzzing, but simply executes the inputs
@@ -40,7 +51,23 @@ all : test test_split
./test
proxy : test_proxy
./test_proxy
@echo "Starting proxy server..."
cd proxy && \
docker compose up -d
@echo "Waiting for proxy to be ready..."
@until nc -z localhost 3128 && nc -z localhost 3129; do sleep 1; done
@echo "Proxy servers are ready, waiting additional 5 seconds for full startup..."
@sleep 5
@echo "Checking proxy server status..."
@cd proxy && docker compose ps
@echo "Checking proxy server logs..."
@cd proxy && docker compose logs --tail=20
@echo "Running proxy tests..."
./test_proxy; \
exit_code=$$?; \
echo "Stopping proxy server..."; \
cd proxy && docker compose down; \
exit $$exit_code
test : test.cc include_httplib.cc ../httplib.h Makefile cert.pem
$(CXX) -o $@ -I.. $(CXXFLAGS) test.cc include_httplib.cc $(TEST_ARGS)
@@ -86,7 +113,7 @@ fuzz_test: server_fuzzer
# Fuzz target, so that you can choose which $(LIB_FUZZING_ENGINE) to use.
server_fuzzer : fuzzing/server_fuzzer.cc ../httplib.h standalone_fuzz_target_runner.o
$(CXX) -o $@ -I.. $(CXXFLAGS) $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread
$(CXX) -o $@ -I.. $(CXXFLAGS) $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) $(ZSTD_SUPPORT) $(LIBS)
@file $@
# Standalone fuzz runner, which just reads inputs from fuzzing/corpus/ dir and
+1 -1
View File
@@ -20,7 +20,7 @@ all : server_fuzzer
# Fuzz target, so that you can choose which $(LIB_FUZZING_ENGINE) to use.
server_fuzzer : server_fuzzer.cc ../../httplib.h
# $(CXX) $(CXXFLAGS) -o $@ $< -Wl,-Bstatic $(OPENSSL_SUPPORT) -Wl,-Bdynamic -ldl $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread
$(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread
$(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread -lanl
zip -q -r server_fuzzer_seed_corpus.zip corpus
clean:
+4 -2
View File
@@ -108,9 +108,11 @@ subdir('www')
subdir('www2'/'dir')
subdir('www3'/'dir')
# GoogleTest 1.13.0 requires C++14
# New GoogleTest versions require new C++ standards
test_options = []
if gtest_dep.version().version_compare('>=1.13.0')
if gtest_dep.version().version_compare('>=1.17.0')
test_options += 'cpp_std=c++17'
elif gtest_dep.version().version_compare('>=1.13.0')
test_options += 'cpp_std=c++14'
endif
+2 -2
View File
@@ -1,9 +1,9 @@
FROM centos:7
FROM alpine:latest
ARG auth="basic"
ARG port="3128"
RUN yum install -y squid
RUN apk update && apk add --no-cache squid
COPY ./${auth}_squid.conf /etc/squid/squid.conf
COPY ./${auth}_passwd /etc/squid/passwd
+1 -1
View File
@@ -27,7 +27,7 @@ acl Safe_ports port 591 # filemaker
acl Safe_ports port 777 # multiling http
acl CONNECT method CONNECT
auth_param basic program /usr/lib64/squid/basic_ncsa_auth /etc/squid/passwd
auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwd
auth_param basic realm proxy
acl authenticated proxy_auth REQUIRED
http_access allow authenticated
+1 -1
View File
@@ -27,7 +27,7 @@ acl Safe_ports port 591 # filemaker
acl Safe_ports port 777 # multiling http
acl CONNECT method CONNECT
auth_param digest program /usr/lib64/squid/digest_file_auth /etc/squid/passwd
auth_param digest program /usr/lib/squid/digest_file_auth /etc/squid/passwd
auth_param digest realm proxy
acl authenticated proxy_auth REQUIRED
http_access allow authenticated
-2
View File
@@ -1,5 +1,3 @@
version: '2'
services:
squid_basic:
image: squid_basic
+3005 -293
View File
File diff suppressed because it is too large Load Diff
+19 -9
View File
@@ -1,3 +1,4 @@
#include <chrono>
#include <future>
#include <gtest/gtest.h>
#include <httplib.h>
@@ -5,6 +6,14 @@
using namespace std;
using namespace httplib;
std::string normalizeJson(const std::string &json) {
std::string result;
for (char c : json) {
if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { result += c; }
}
return result;
}
template <typename T> void ProxyTest(T &cli, bool basic) {
cli.set_proxy("localhost", basic ? 3128 : 3129);
auto res = cli.Get("/httpbin/get");
@@ -81,7 +90,7 @@ TEST(RedirectTest, YouTubeNoSSLBasic) {
RedirectProxyText(cli, "/", true);
}
TEST(RedirectTest, DISABLED_YouTubeNoSSLDigest) {
TEST(RedirectTest, YouTubeNoSSLDigest) {
Client cli("youtube.com");
RedirectProxyText(cli, "/", false);
}
@@ -92,6 +101,7 @@ TEST(RedirectTest, YouTubeSSLBasic) {
}
TEST(RedirectTest, YouTubeSSLDigest) {
std::this_thread::sleep_for(std::chrono::seconds(3));
SSLClient cli("youtube.com");
RedirectProxyText(cli, "/", false);
}
@@ -113,8 +123,8 @@ template <typename T> void BaseAuthTestFromHTTPWatch(T &cli) {
auto res = cli.Get("/basic-auth/hello/world",
{make_basic_authentication_header("hello", "world")});
ASSERT_TRUE(res != nullptr);
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body);
EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\"}\n"),
normalizeJson(res->body));
EXPECT_EQ(StatusCode::OK_200, res->status);
}
@@ -122,8 +132,8 @@ template <typename T> void BaseAuthTestFromHTTPWatch(T &cli) {
cli.set_basic_auth("hello", "world");
auto res = cli.Get("/basic-auth/hello/world");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body);
EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\"}\n"),
normalizeJson(res->body));
EXPECT_EQ(StatusCode::OK_200, res->status);
}
@@ -179,8 +189,8 @@ template <typename T> void DigestAuthTestFromHTTPWatch(T &cli) {
for (auto path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr);
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body);
EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\"}\n"),
normalizeJson(res->body));
EXPECT_EQ(StatusCode::OK_200, res->status);
}
@@ -249,8 +259,8 @@ template <typename T> void KeepAliveTest(T &cli, bool basic) {
for (auto path : paths) {
auto res = cli.Get(path.c_str());
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body);
EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\"}\n"),
normalizeJson(res->body));
EXPECT_EQ(StatusCode::OK_200, res->status);
}
}