Compare commits

..

202 Commits

Author SHA1 Message Date
yhirose 2867b74f13 Release v0.31.0 2026-02-08 16:04:29 -10:00
yhirose 4e14bc8948 Fix memory leak (#2348)
* Fix memory leak

* Fix flaky errors
2026-02-08 15:49:30 -10:00
yhirose 5d717e6d91 Resolve #2347 2026-02-08 09:40:26 -10:00
yhirose 8b4146324f Fix #2116 (#2346)
* Fix #2116

* Fix problem
2026-02-07 19:26:11 -10:00
yhirose 94b5038eb3 Fix build error on Windows 2026-02-06 22:08:28 -10:00
yhirose 9248ce3bfe Fix problem with PayloadMaxLengthZeroMeansNoLimit 2026-02-06 22:02:22 -10:00
yhirose 4639b696ab Fix #2339 (#2344)
* Fix #2339

* Fix CI errors

* Fix Windows build error

* Fix CI errors on Windows

* Fix payload_max_length initialization in BodyReader

* Initialize payload_max_length with CPPHTTPLIB_PAYLOAD_MAX_LENGTH in BodyReader

* Update README and tests to clarify payload_max_length behavior and add no limit case

* Fix server thread lambda capture in ClientVulnerabilityTest
2026-02-06 19:30:33 -10:00
yhirose 5ead179b8e Update README 2026-02-02 11:32:23 -05:00
Miko 1942e0ef01 Add C++ modules support (#2291)
* Add C++ modules support

* Add module examples

* Missing semicolon

* Update GitHub Actions script and create a modules updating script

* Name the unused param

* Use the guarded/direct export of header approach

* Update CMakeLists.txt

Co-authored-by: Andrea Pappacoda <andrea@pappacoda.it>

* Update CMakeLists.txt

Co-authored-by: Andrea Pappacoda <andrea@pappacoda.it>

* Split scripts into split.py and generate_module.py

---------

Co-authored-by: Andrea Pappacoda <andrea@pappacoda.it>
2026-02-02 11:27:09 -05:00
yhirose 6be32a540d Abstract TLS API support (Resolve #2309) (#2342)
Abstract TLS API support (OpenSSL and MbedTLS backends)
2026-02-01 23:48:03 -05:00
yhirose dc6faf5c17 Release v0.30.2 2026-02-01 19:11:44 -05:00
yhirose 71bb17fb0e Fix problems with CPPHTTPLIB_NO_EXCEPTIONS 2026-02-01 08:43:13 -05:00
yhirose 094bf112bb Fix #2340 2026-01-30 22:01:43 -05:00
ctabor-itracs fbec2a3466 case insensitive hostname validation (fix #2333) (#2337)
* Add Tests for case sensitive hostname verfication

* Modify check_host_name to perform case insensitive comparisons during ssl hostname validation

---------

Co-authored-by: Tabor, Chris <chris.tabor@commscope.com>
2026-01-23 14:58:47 -05:00
Sung Po-Han c3fa06112b Fix set_ca_cert_store() to skip system certs like set_ca_cert_path() (#2335)
Both APIs conceptually do the same thing: "use these CA certs for
verification." However, set_ca_cert_store() falls through to the else
branch in load_certs() where system certs are added to the user's
custom store, defeating the purpose of certificate pinning.

This change makes set_ca_cert_store() behave consistently with
set_ca_cert_path() by checking ca_cert_store_ before loading system
certificates.

Added test to verify system certs are not loaded when custom store is set.

Co-authored-by: Your <you@example.com>
2026-01-22 18:58:25 -05:00
Prajwal B Mehendarkar f73e694f0c timegm api absent in AIX (#2336) 2026-01-22 18:57:23 -05:00
TH 191bfb2ea4 Fix build error when zstd < 1.5.6 lacks zstd::libzstd CMake target (#2334)
Fix #2313
2026-01-22 18:56:34 -05:00
yhirose ad5839f0d1 Add retry logic to BenchmarkTest test on Windows 2026-01-20 18:43:56 -05:00
yhirose 02dfb97fd6 Add Expect: 100-continue support 2026-01-18 22:38:25 -05:00
yhirose a38a076571 Resolve #2262 (#2332)
* Resolve #2262

* Enhance request handling on Windows by adding early response check for large request bodies

* Enhance early response handling for large requests with long URIs on Windows
2026-01-18 00:38:43 -05:00
yhirose 0e1b52b23e Fix #2325 (#2331)
* Fix #2325

* clang-format
2026-01-16 18:19:14 -05:00
yhirose c0469eba96 Revert "Fix #2325"
This reverts commit 7dec57d1eb.
2026-01-16 17:28:28 -05:00
yhirose 7dec57d1eb Fix #2325 2026-01-16 16:25:06 -05:00
yhirose b85aa76bd2 Fix #2321, #2322, #2326 2026-01-16 11:29:09 -05:00
yhirose cea018f2cd Fix #2324 2026-01-11 21:23:15 -05:00
yhirose 1111219f17 Fix #2324 2026-01-10 21:05:30 -05:00
yhirose a7e1d14b15 Fix warning on Windows 2026-01-10 19:23:35 -05:00
yhirose 6eff49e1fb Problem with CI test on Windows without OpenSSL (#2323)
* Fix problem with 'windows without SSL`

* Fix payload limit enforcement for requests without Content-Length on Windows

- Enable MSG_PEEK on Windows (non-SSL builds) to detect payloads without Content-Length
- Only use MSG_PEEK when payload_max_length is set to a finite value to avoid blocking
- Use read_content_without_length for actual size checking to support any payload limit
- Set 413 Payload Too Large status before rejecting oversized requests

This fixes three test cases on Windows:
- RequestWithoutContentLengthOrTransferEncoding (no payload limit)
- NoContentLengthPayloadLimit (8-byte limit)
- NoContentLengthExceeds10MB (10MB limit)

* clang-format
2026-01-10 19:23:24 -05:00
yhirose bd95e67c23 Release v0.30.1 2026-01-09 21:35:03 -05:00
yhirose 2e2e47bab1 Merge commit from fork
* Ensure payload_max_length_ is respected for compressed payloads

* Fix Denial of service (DOS) using zip bomb

---------

Co-authored-by: Hritik Vijay <hey@hritik.sh>
2026-01-09 21:09:07 -05:00
seragh 59905c7f0d Prevent redefinition of ssize_t (#2319)
On Windows cpp-httplib defines ssize_t, therefore applications needing
to define ssize_t for their own needs or are using libraries that do
require a means to avoid a possible incompatible redefinition.

Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
2026-01-05 20:03:18 -05:00
yhirose 8d03ef1615 Fix #2318 on macOS 2026-01-02 22:28:15 -05:00
yhirose 23a1d79a66 Fix #2318 2026-01-02 20:45:01 -05:00
yhirose 781c55f120 Release v0.30.0 2025-12-31 22:19:05 -05:00
yhirose 40f7985e02 Update copyright year 2025-12-31 22:18:11 -05:00
PerseoGI f85f30a637 Apple frameworks: match CoreFoundation and CFNetwork linkage (#2317)
* Apple frameworks: match linkage with CoreFoundation and CFNetwork with actual code

* Fix appleframeworks in Meson code
2025-12-31 12:49:59 -05:00
yhirose 6da7f0c61c Fix port reuse problem 2025-12-31 00:34:15 -05:00
yhirose 2ba0e7a797 Fix build error 2025-12-30 19:46:21 -05:00
yhirose ded82448aa clang-format 2025-12-30 17:34:52 -05:00
yhirose 98048a033a Merge commit from fork 2025-12-30 17:32:42 -05:00
yhirose 7ae794a6bf Fix #2315 2025-12-26 16:17:43 -05:00
yhirose 385adefb11 Use HOST and PORT in test.cc 2025-12-26 00:24:19 -05:00
yhirose b7c2f04318 Fix potential arithmatic overflow problem 2025-12-25 22:19:37 -05:00
yhirose d23cf77cd0 Resolve #2313 2025-12-23 20:10:59 -05:00
yhirose 5304464a53 Release v0.29.0 2025-12-23 00:03:49 -05:00
yhirose db98efee5a Fix problem with Proxy test 2025-12-22 23:10:24 -05:00
yhirose cdf0d33258 Fix #2301 2025-12-22 22:37:56 -05:00
Aaron Gokaslan 25688258ad Add another missing std::move for _base_dirs vector (#2314) 2025-12-22 20:43:46 -05:00
Aaron Gokaslan f0990ca96d Use std::move for request redirection (#2311)
Prevents an additional copy
2025-12-17 12:04:37 -05:00
Aaron Gokaslan 0461cb770c Avoid unncessary copying of request and response objects (#2310) 2025-12-17 12:04:17 -05:00
yhirose 51b704b902 Implement SSEClient (#2308)
* Implement SSEClient

* Fix Windows problem
2025-12-15 00:00:42 -05:00
yhirose 7eb03e81fc Refactoring 2025-12-14 17:41:11 -05:00
yhirose 6a6d4161d1 Removed DigestAuthTest.FromHTTPWatch_Online_HTTPCan 2025-12-14 14:45:48 -05:00
Copilot 63b07ada43 Initial plan (#2307)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2025-12-14 13:54:29 -05:00
Jean-Francois Simoneau 2de4c59bc2 Add Zstd support through meson (#2293)
* Add Zstd support through meson

* Add libzstd-dev to abitest
2025-12-13 23:55:58 -05:00
Jean-Francois Simoneau b7097f1386 Replace httpbin.org with httpcan.org (#2300)
* Replace httpbin.org with httpcan.org

* Fix DigestAuthTest.FromHTTPWatch_Online test
2025-12-13 23:52:05 -05:00
Aaron Gokaslan 681d388247 Use move semantics for auth key and value (#2306) 2025-12-13 22:53:10 -05:00
Aaron Gokaslan ae94d64f67 Remove another unnecessary string copy (#2305) 2025-12-13 22:52:26 -05:00
Aaron Gokaslan 3401877d3d Change single char string literals to chars (#2304) 2025-12-13 22:52:12 -05:00
Aaron Gokaslan bce08e62f9 Remove unnecessary copies for AcceptEntry (#2303) 2025-12-13 22:49:58 -05:00
Carter Green f4ecb96e54 Fix linker error on macOS (#2299) 2025-12-11 20:12:31 -05:00
Aaron Gokaslan c23764269d Use std::move for boundary in set_boundary method (#2298) 2025-12-09 22:24:33 -05:00
Aaron Gokaslan f441cd2a44 Use std::move for content_provider in adapter (#2297) 2025-12-09 22:23:45 -05:00
Miko c3613c6977 Update the split.py file (#2295) 2025-12-08 22:18:01 -05:00
yhirose 87c2b4e584 Fix #2294 2025-12-08 19:32:35 -05:00
yhirose c795ad1c32 Fix #2259. Add query string normalization to preserve parameter order in requests 2025-12-05 21:39:40 -05:00
yhirose 3e0fa33559 Implement ETag and Last-Modified support for static file responses and If-Range requests (#2286)
* Fix #2242: Implement ETag and Last-Modified support for static file responses

* Add ETag and Last-Modified handling for If-Range requests

* Enhance HTTP date parsing with improved error handling and locale support

* Update httplib.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update test/test.cc

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update httplib.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Refactor ETag handling: separate strong and weak ETag checks for If-Range requests

* Fix type for mtime in FileStat and improve ETag handling comments

* Update httplib.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Resolved code review comments

* Update httplib.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update httplib.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Refactor ETag handling: use 'auto' for type inference and improve code readability

* Refactor ETag handling: extract check_if_not_modified and check_if_range methods for improved readability and maintainability

* Code cleanup

* Update httplib.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update test/test.cc

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update httplib.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update httplib.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Enhance ETag handling and validation in httplib.h and add comprehensive tests in test.cc

* Refactor ETag comparison logic and add test for If-None-Match with non-existent file

* Fix #2287

* Code cleanup

* Add tests for extreme date values and negative file modification time in ETag handling

* Update HTTP-date parsing comments to reference RFC 9110

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-05 18:58:54 -05:00
yhirose 27b73f050e Fix #2287 2025-12-05 08:50:12 -05:00
yhirose dbd5ca4bf2 Add error handling for stream read timeouts and connection closures 2025-12-04 19:19:18 -05:00
yhirose 143019a38c Fix #2284 2025-12-02 17:24:27 -05:00
yhirose 1d36013fc3 Update README 2025-12-02 17:08:53 -05:00
yhirose 8bba34eebc Add New Streaming API support (#2281) 2025-12-01 21:40:13 -05:00
Aaron Gokaslan 0a9102ff6b Optimize ThreadPool and MatcherBase constructors (#2283)
Add a missing reserve and missing std::move to each ctor respectively. The latter should really be caught by a clang-tidy perf linter.
2025-11-30 21:49:23 -05:00
bigmoonbit c1fa5e1710 chore: fix some typos in comments (#2282)
Signed-off-by: bigmoonbit <bigmoonbit@outlook.com>
2025-11-27 20:34:18 -05:00
yhirose 84796738fc Fix #2248 2025-11-25 22:22:07 -05:00
yhirose adf58bf474 Release v0.28.0 2025-11-25 21:06:51 -05:00
yhirose 337fbb0793 Fix #2279
Enhance request handling: add support for requests without Content-Length or Transfer-Encoding headers
2025-11-25 20:30:43 -05:00
Copilot 9e7861b0b4 Add #undef _res after including resolv.h to prevent macro conflicts (#2280)
* Initial plan

* Add #undef _res after including resolv.h to prevent macro conflicts

Co-authored-by: yhirose <357397+yhirose@users.noreply.github.com>

* Complete task - added #undef _res after resolv.h include

Co-authored-by: yhirose <357397+yhirose@users.noreply.github.com>

* Remove accidentally committed codeql build artifacts

Co-authored-by: yhirose <357397+yhirose@users.noreply.github.com>

* Add inline comment explaining why #undef _res is necessary

Co-authored-by: yhirose <357397+yhirose@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yhirose <357397+yhirose@users.noreply.github.com>
2025-11-25 20:00:10 -05:00
Clarkok Zhang 27ee115a60 Fix android getaddrinfo issue (#2273)
Co-authored-by: Clarkok Zhang <clarkok8@gmail.com>
2025-11-14 16:48:21 -05:00
Clarkok Zhang 59882752aa Add Client::Post with both content provider and receiver (#2268)
Co-authored-by: Clarkok Zhang <clarkok8@gmail.com>
2025-11-14 14:52:06 -05:00
yhirose 61e9f7ce8f Fix #2270 2025-11-14 14:17:04 -05:00
VZ 1acf18876f CMake: Add HTTPLIB_SHARED option, don't define BUILD_SHARED_LIBS (#2266)
To avoid surprises in the projects consuming the library, don't define
BUILD_SHARED_LIBS option ourselves but just use its value, if provided,
to define HTTPLIB_SHARED option which can be also set directly to
specify whether we should build static or shared library.

Closes #2263.
2025-11-10 22:17:43 -05:00
chansikpark 4b2b851dbb Fix HTTP 414 errors hanging until timeout (#2260)
* Fix HTTP 414 errors hanging until timeout

* All errors (status code 400+) close the connection

* 🧹

---------

Co-authored-by: Wor Ker <worker@factory>
2025-11-02 22:23:42 -05:00
yhirose 551f96d4a2 Remove REMOTE_PORT dependency from UnixSocketTest.PeerPid 2025-10-27 20:40:12 -04:00
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
yhirose 3af7f2c161 Release v0.20.1 2025-05-03 21:24:22 +09:00
yhirose a0de42ebc4 clang-format 2025-05-03 17:40:34 +09:00
Ville Vesilehto 7b752106ac Merge commit from fork
* fix(parser): Limit line length in getline

Prevents potential infinite loop and memory exhaustion in
stream_line_reader::getline by enforcing max line length.

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>

* fix: increase default max line length to 32k

LONG_QUERY_VALUE test is set at 25k.

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>

* test(client): expect read error with too long query

Adds a test case (`TooLongQueryValue`) to verify client behavior
when the request URI is excessively long, exceeding
`CPPHTTPLIB_MAX_LINE_LENGTH`. In this scenario, the server is
expected to reset the connection.

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>

---------

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
2025-05-03 04:39:01 -04:00
yhirose 9589519d58 Fix #2130 2025-04-17 11:52:22 -04:00
Alexey Sokolov caf7c55785 Fix regression of #2121 (#2126) 2025-04-08 16:08:41 -04:00
yhirose 9e4aed482e Fix style error 2025-04-06 09:02:25 -04:00
yhirose 65d6316d65 Fix #2113 2025-04-05 22:40:08 -04:00
yhirose 3e3a8cc02f Made the max timeout threshold for SSL longer. 2025-04-05 22:38:50 -04:00
KTGH b7e33b08f1 Add missing component comment (#2124)
Fix #2123
2025-03-31 20:34:28 -04:00
Alexey Sokolov 0dbe8ba144 Support zstd also via pkg-config (#2121)
* Support zstd also via pkg-config

It doesn't always provide cmake config

* Find zstd with pkg-config also in non-required case

Code by @sum01, slightly modified
2025-03-29 11:46:22 -04:00
Piotr dbc4af819a Fix compilation error on windows (#2118)
afunix.h uses types declared in winsock2.h, and so has to be included
after it. Including afunix.h first will result in a somewhat unhelpful
compilation error:

error C3646: 'sun_family': unknown override specifier

Signed-off-by: Piotr Stankiewicz <piotr.stankiewicz@docker.com>
2025-03-25 08:36:20 -04:00
yhirose 7dbf5471ce Fix the style error and comment 2025-03-24 19:16:48 -04:00
Piotr 72b35befb2 Add AF_UNIX support on windows (#2115)
Signed-off-by: Piotr Stankiewicz <piotr.stankiewicz@docker.com>
2025-03-24 19:14:24 -04:00
Jean-Francois Simoneau 65ce51aed7 Fix start_time shadow variable (#2114) 2025-03-18 19:17:47 -04:00
55 changed files with 19268 additions and 24451 deletions
+1
View File
@@ -29,6 +29,7 @@ jobs:
git
libbrotli-dev
libssl-dev
libzstd-dev
meson
pkg-config
python3
+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 }}
+59 -10
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,6 @@ 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
@@ -68,10 +64,45 @@ jobs:
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 EXTRA_CXXFLAGS="${{ matrix.config.arch_flags }}"
- 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')
strategy:
matrix:
tls_backend: [openssl, mbedtls]
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: build and run tests (OpenSSL)
if: matrix.tls_backend == 'openssl'
run: cd test && make
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 && ./test_mbedtls
- name: run fuzz test target
run: cd test && make EXTRA_CXXFLAGS="${{ matrix.config.arch_flags }}" fuzz_test
if: matrix.tls_backend == 'openssl'
run: cd test && make fuzz_test
macos:
runs-on: macos-latest
@@ -80,12 +111,26 @@ jobs:
(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]
name: macos (${{ matrix.tls_backend }})
steps:
- name: checkout
uses: actions/checkout@v4
- name: build and run tests
- name: install Mbed TLS
if: matrix.tls_backend == 'mbedtls'
run: brew install mbedtls@3
- name: build and run tests (OpenSSL)
if: matrix.tls_backend == 'openssl'
run: cd test && make
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 && ./test_mbedtls
- name: run fuzz test target
if: matrix.tls_backend == 'openssl'
run: cd test && make fuzz_test
windows:
@@ -138,6 +183,7 @@ jobs:
-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
@@ -146,7 +192,10 @@ jobs:
run: cmake --build build --config Release -- /v:m /clp:ShowCommandLine
- name: Run tests ${{ matrix.config.name }}
if: ${{ matrix.config.run_tests }}
run: ctest --output-on-failure --test-dir build -C Release
run: ctest --output-on-failure --test-dir build -C Release -E "BenchmarkTest"
- name: Run benchmark tests with retry ${{ matrix.config.name }}
if: ${{ matrix.config.run_tests }}
run: ctest --output-on-failure --test-dir build -C Release -R "BenchmarkTest" --repeat until-pass:5
env:
VCPKG_ROOT: "C:/vcpkg"
+20
View File
@@ -0,0 +1,20 @@
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 EXTRA_CXXFLAGS="-fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS"
+33
View File
@@ -0,0 +1,33 @@
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
- name: Run proxy tests (Mbed TLS)
if: matrix.tls_backend == 'mbedtls'
run: cd test && make proxy_mbedtls
+25 -2
View File
@@ -1,23 +1,46 @@
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/ssecli.*
example/ssecli-stream
!example/ssecli-stream.*
example/ssesvr
!example/ssesvr.*
example/upload
!example/upload.*
example/one_time_request
!example/one_time_request.*
example/server_and_client
!example/server_and_client.*
example/accept_header
!example/accept_header.*
example/*.pem
test/httplib.cc
test/httplib.h
test/test
test/test_mbedtls
test/test_no_tls
test/server_fuzzer
test/test_proxy
test/test_proxy_mbedtls
test/test_split
test/test_split_mbedtls
test/test_split_no_tls
test/test.xcodeproj/xcuser*
test/test.xcodeproj/*/xcuser*
test/*.o
@@ -42,4 +65,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] # インプレースで修正
+115 -22
View File
@@ -1,24 +1,27 @@
#[[
Build options:
* BUILD_SHARED_LIBS (default off) builds as a shared library (if HTTPLIB_COMPILE is ON)
* Standard BUILD_SHARED_LIBS is supported and sets HTTPLIB_SHARED default value.
* HTTPLIB_USE_OPENSSL_IF_AVAILABLE (default on)
* 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_ZLIB (default off)
* 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_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 ZLIB Brotli) is available.
After installation with Cmake, a find_package(httplib COMPONENTS OpenSSL 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:
@@ -49,6 +52,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 +108,52 @@ 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)
# Necessary for Windows if building shared libs
# See https://stackoverflow.com/a/40743080
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 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()
# 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(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)
@@ -158,11 +195,30 @@ elseif(HTTPLIB_USE_BROTLI_IF_AVAILABLE)
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 REQUIRED)
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 QUIET)
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()
@@ -194,15 +250,37 @@ if(HTTPLIB_COMPILE)
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")
# This will automatically be either static or shared based on the value of BUILD_SHARED_LIBS
add_library(${PROJECT_NAME} "${_httplib_build_includedir}/httplib.cc")
add_library(${PROJECT_NAME} ${HTTPLIB_LIB_TYPE} "${_httplib_build_includedir}/httplib.cc")
target_sources(${PROJECT_NAME}
PUBLIC
$<BUILD_INTERFACE:${_httplib_build_includedir}/httplib.h>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/httplib.h>
)
# 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}
@@ -219,22 +297,28 @@ endif()
# Only useful if building in-tree, versus using it from an installation.
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
# Require C++11
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_11)
# 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}
$<BUILD_INTERFACE:${_httplib_build_includedir}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
$<BUILD_INTERFACE:${_httplib_build_includedir}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
# Always require threads
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.
$<$<PLATFORM_ID:Windows>:ws2_32>
$<$<PLATFORM_ID:Windows>:crypt32>
# Needed for API from MacOS Security framework
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>,$<BOOL:${HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN}>>:-framework CoreFoundation -framework Security>"
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>,$<BOOL:${HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN}>>:-framework CFNetwork -framework CoreFoundation -framework Security>"
# Needed for non-blocking getaddrinfo on MacOS
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO}>>:-framework CFNetwork -framework CoreFoundation>"
# Can't put multiple targets in a single generator expression or it bugs out.
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::common>
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::encoder>
@@ -253,6 +337,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
@@ -289,7 +374,11 @@ if(HTTPLIB_INSTALL)
# Creates the export httplibTargets.cmake
# This is strictly what holds compilation requirements
# and linkage information (doesn't find deps though).
install(TARGETS ${PROJECT_NAME} EXPORT httplibTargets)
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)
@@ -318,7 +407,11 @@ if(HTTPLIB_INSTALL)
include(CPack)
endif()
if(HTTPLIB_TEST)
include(CTest)
add_subdirectory(test)
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()
+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"]
+182
View File
@@ -0,0 +1,182 @@
# 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)
```
+317
View File
@@ -0,0 +1,317 @@
# 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 (planned) │ ← 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 (planned) or `ssecli-stream.cc` example |
| 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<Response>` | 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 <iostream>
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 <iostream>
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 <fstream>
#include <iostream>
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<size_t>(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
+509 -37
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.
@@ -44,24 +44,34 @@ httplib::Client cli("http://yhirose.github.io");
// HTTPS
httplib::Client cli("https://yhirose.github.io");
auto res = cli.Get("/hi");
res->status;
res->body;
if (auto res = cli.Get("/hi")) {
res->status;
res->body;
}
```
SSL Support
-----------
SSL/TLS Support
---------------
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
cpp-httplib supports multiple TLS backends through an abstraction layer:
| Backend | Define | Libraries |
| :------ | :----- | :-------- |
| OpenSSL | `CPPHTTPLIB_OPENSSL_SUPPORT` | `libssl`, `libcrypto` |
| Mbed TLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | `libmbedtls`, `libmbedx509`, `libmbedcrypto` |
> [!NOTE]
> cpp-httplib currently supports only version 3.0 or later. Please see [this page](https://www.openssl.org/policies/releasestrat.html) to get more information.
> OpenSSL 3.0 or later is required. Please see [this page](https://www.openssl.org/policies/releasestrat.html) for more information.
> [!NOTE]
> Mbed TLS 2.x and 3.x are supported. The library automatically detects the version and uses the appropriate API.
> [!TIP]
> For macOS: cpp-httplib now can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`.
> For macOS: cpp-httplib can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`.
```c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
// Use either OpenSSL or Mbed TLS
#define CPPHTTPLIB_OPENSSL_SUPPORT // or CPPHTTPLIB_MBEDTLS_SUPPORT
#include "path/to/httplib.h"
// Server
@@ -82,9 +92,119 @@ cli.enable_server_certificate_verification(false);
cli.enable_server_hostname_verification(false);
```
### Windows Certificate Verification
On Windows, cpp-httplib automatically performs additional certificate verification using the Windows certificate store via CryptoAPI (`CertGetCertificateChain` / `CertVerifyCertificateChainPolicy`). This works with both OpenSSL and Mbed TLS backends, providing:
- Real-time certificate validation integrated with Windows Update
- Certificate revocation checking
- SSL/TLS policy verification using the system certificate store (ROOT and CA)
This feature is enabled by default and can be controlled at runtime:
```c++
// Disable Windows certificate verification (use only OpenSSL/Mbed TLS verification)
cli.enable_windows_certificate_verification(false);
```
To disable this feature at compile time, define:
```c++
#define CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
```
> [!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 `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, return value for Mbed TLS)
```c++
#define CPPHTTPLIB_OPENSSL_SUPPORT // or CPPHTTPLIB_MBEDTLS_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;
});
```
Server
------
@@ -148,7 +268,7 @@ int main(void)
}
```
`Post`, `Put`, `Delete` and `Options` methods are also supported.
`Post`, `Put`, `Patch`, `Delete` and `Options` methods are also supported.
### Bind a socket to multiple interfaces and any available port
@@ -187,7 +307,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 +321,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 +346,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 +441,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 +541,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 +684,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
```
@@ -550,7 +799,7 @@ httplib::SSLClient cli("localhost");
Here is the list of errors from `Result::error()`.
```c++
enum Error {
enum class Error {
Success = 0,
Unknown,
Connection,
@@ -562,12 +811,77 @@ enum Error {
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::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 +932,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" },
@@ -635,6 +949,12 @@ auto res = cli.Post("/multipart", items);
res = cli.Put("/resource/foo", "text", "text/plain");
```
### PATCH
```c++
res = cli.Patch("/resource/foo", "text", "text/plain");
```
### DELETE
```c++
@@ -655,8 +975,14 @@ 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
```
### 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
@@ -721,7 +1047,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));
@@ -769,7 +1095,7 @@ cli.set_proxy_bearer_token_auth("pass");
### Range
```cpp
httplib::Client cli("httpbin.org");
httplib::Client cli("httpcan.org");
auto res = cli.Get("/range/32", {
httplib::make_range_header({{1, 10}}) // 'Range: bytes=1-10'
@@ -820,6 +1146,50 @@ 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);
```
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
-----------
@@ -830,6 +1200,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 +1212,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,8 +1263,86 @@ 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.
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 <httplib.h>
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.
Split httplib.h into .h and .cc
-------------------------------
@@ -938,6 +1392,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).
@@ -966,7 +1438,7 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
License
-------
MIT license (© 2025 Yuji Hirose)
MIT license (© 2026 Yuji Hirose)
Special Thanks To
-----------------
+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}"
+20 -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)
@@ -39,7 +40,25 @@ if(@HTTPLIB_IS_USING_BROTLI@)
endif()
if(@HTTPLIB_IS_USING_ZSTD@)
find_dependency(zstd)
set(httplib_fd_zstd_quiet_arg)
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY)
set(httplib_fd_zstd_quiet_arg QUIET)
endif()
set(httplib_fd_zstd_required_arg)
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED)
set(httplib_fd_zstd_required_arg REQUIRED)
endif()
find_package(zstd QUIET)
if(NOT zstd_FOUND)
find_package(PkgConfig ${httplib_fd_zstd_quiet_arg} ${httplib_fd_zstd_required_arg})
if(PKG_CONFIG_FOUND)
pkg_check_modules(zstd ${httplib_fd_zstd_quiet_arg} ${httplib_fd_zstd_required_arg} IMPORTED_TARGET libzstd)
if(TARGET PkgConfig::zstd)
add_library(zstd::libzstd ALIAS PkgConfig::zstd)
endif()
endif()
endif()
set(httplib_zstd_FOUND ${zstd_FOUND})
endif()
+16
View File
@@ -0,0 +1,16 @@
# This file contains C++20 module support requiring CMake 3.28+
# Included conditionally to prevent parse errors on older CMake versions
if(HTTPLIB_BUILD_MODULES)
if(POLICY CMP0155)
cmake_policy(SET CMP0155 NEW)
endif()
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
target_sources(${PROJECT_NAME}
PUBLIC
FILE_SET CXX_MODULES FILES
"${_httplib_build_includedir}/httplib.cppm"
)
endif()
+265 -43
View File
@@ -1,81 +1,303 @@
//
// main.cc
//
// Copyright (c) 2025 Yuji Hirose. All rights reserved.
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
// 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;
}
+1 -1
View File
@@ -21,7 +21,7 @@ struct StopWatch {
int main(void) {
string body(1024 * 5, 'a');
httplib::Client cli("httpbin.org", 80);
httplib::Client cli("httpcan.org", 80);
for (int i = 0; i < 3; i++) {
StopWatch sw(to_string(i).c_str());
+2 -2
View File
@@ -1,7 +1,7 @@
//
// client.cc
//
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
// MIT License
//
@@ -30,7 +30,7 @@ int main(void) {
} else {
cout << "error code: " << res.error() << std::endl;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto result = cli.get_openssl_verify_result();
auto result = cli.get_verify_result();
if (result) {
cout << "verify error: " << X509_verify_cert_error_string(result) << endl;
}
+1 -1
View File
@@ -1,7 +1,7 @@
//
// hello.cc
//
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
// MIT License
//
+1 -1
View File
@@ -1,7 +1,7 @@
//
// redirect.cc
//
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
// MIT License
//
+1 -1
View File
@@ -1,7 +1,7 @@
//
// sample.cc
//
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
// MIT License
//
+1 -1
View File
@@ -1,7 +1,7 @@
//
// server_and_client.cc
//
// Copyright (c) 2025 Yuji Hirose. All rights reserved.
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
// MIT License
//
+1 -1
View File
@@ -1,7 +1,7 @@
//
// simplecli.cc
//
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
// MIT License
//
+18 -5
View File
@@ -1,7 +1,7 @@
//
// simplesvr.cc
//
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
// MIT License
//
@@ -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");
});
+234
View File
@@ -0,0 +1,234 @@
//
// ssecli-stream.cc
//
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
// MIT License
//
// SSE (Server-Sent Events) client example using Streaming API
// with automatic reconnection support (similar to JavaScript's EventSource)
//
#include <httplib.h>
#include <chrono>
#include <iostream>
#include <string>
#include <thread>
//------------------------------------------------------------------------------
// SSE Event Parser
//------------------------------------------------------------------------------
// Parses SSE events from the stream according to the SSE specification.
// SSE format:
// event: <event-type> (optional, defaults to "message")
// data: <payload> (can have multiple lines)
// id: <event-id> (optional, used for reconnection)
// retry: <milliseconds> (optional, reconnection interval)
// <blank line> (signals end of event)
//
struct SSEEvent {
std::string event = "message"; // Event type (default: "message")
std::string data; // Event payload
std::string id; // Event ID for Last-Event-ID header
void clear() {
event = "message";
data.clear();
id.clear();
}
};
// Parse a single SSE field line (e.g., "data: hello")
// Returns true if this line ends an event (blank line)
bool parse_sse_line(const std::string &line, SSEEvent &event, int &retry_ms) {
// Blank line signals end of event
if (line.empty() || line == "\r") { return true; }
// Find the colon separator
auto colon_pos = line.find(':');
if (colon_pos == std::string::npos) {
// Line with no colon is treated as field name with empty value
return false;
}
std::string field = line.substr(0, colon_pos);
std::string value;
// Value starts after colon, skip optional single space
if (colon_pos + 1 < line.size()) {
size_t value_start = colon_pos + 1;
if (line[value_start] == ' ') { value_start++; }
value = line.substr(value_start);
// Remove trailing \r if present
if (!value.empty() && value.back() == '\r') { value.pop_back(); }
}
// Handle known fields
if (field == "event") {
event.event = value;
} else if (field == "data") {
// Multiple data lines are concatenated with newlines
if (!event.data.empty()) { event.data += "\n"; }
event.data += value;
} else if (field == "id") {
// Empty id is valid (clears the last event ID)
event.id = value;
} else if (field == "retry") {
// Parse retry interval in milliseconds
try {
retry_ms = std::stoi(value);
} catch (...) {
// Invalid retry value, ignore
}
}
// Unknown fields are ignored per SSE spec
return false;
}
//------------------------------------------------------------------------------
// Main - SSE Client with Auto-Reconnection
//------------------------------------------------------------------------------
int main(void) {
// Configuration
const std::string host = "http://localhost:1234";
const std::string path = "/event1";
httplib::Client cli(host);
// State for reconnection (persists across connections)
std::string last_event_id; // Sent as Last-Event-ID header on reconnect
int retry_ms = 3000; // Reconnection delay (server can override via retry:)
int connection_count = 0;
std::cout << "SSE Client starting...\n";
std::cout << "Target: " << host << path << "\n";
std::cout << "Press Ctrl+C to exit\n\n";
//----------------------------------------------------------------------------
// Main reconnection loop
// This mimics JavaScript's EventSource behavior:
// - Automatically reconnects on connection failure
// - Sends Last-Event-ID header to resume from last received event
// - Respects server's retry interval
//----------------------------------------------------------------------------
while (true) {
connection_count++;
std::cout << "[Connection #" << connection_count << "] Connecting...\n";
// Build headers, including Last-Event-ID if we have one
httplib::Headers headers;
if (!last_event_id.empty()) {
headers.emplace("Last-Event-ID", last_event_id);
std::cout << "[Connection #" << connection_count
<< "] Resuming from event ID: " << last_event_id << "\n";
}
// Open streaming connection
auto result = httplib::stream::Get(cli, path, headers);
//--------------------------------------------------------------------------
// Connection error handling
//--------------------------------------------------------------------------
if (!result) {
std::cerr << "[Connection #" << connection_count
<< "] Failed: " << httplib::to_string(result.error()) << "\n";
std::cerr << "Reconnecting in " << retry_ms << "ms...\n\n";
std::this_thread::sleep_for(std::chrono::milliseconds(retry_ms));
continue;
}
if (result.status() != 200) {
std::cerr << "[Connection #" << connection_count
<< "] HTTP error: " << result.status() << "\n";
// For certain errors, don't reconnect
if (result.status() == 204 || // No Content - server wants us to stop
result.status() == 404 || // Not Found
result.status() == 401 || // Unauthorized
result.status() == 403) { // Forbidden
std::cerr << "Permanent error, not reconnecting.\n";
return 1;
}
std::cerr << "Reconnecting in " << retry_ms << "ms...\n\n";
std::this_thread::sleep_for(std::chrono::milliseconds(retry_ms));
continue;
}
// Verify Content-Type (optional but recommended)
auto content_type = result.get_header_value("Content-Type");
if (content_type.find("text/event-stream") == std::string::npos) {
std::cerr << "[Warning] Content-Type is not text/event-stream: "
<< content_type << "\n";
}
std::cout << "[Connection #" << connection_count << "] Connected!\n\n";
//--------------------------------------------------------------------------
// Event receiving loop
// Reads chunks from the stream and parses SSE events
//--------------------------------------------------------------------------
std::string buffer;
SSEEvent current_event;
int event_count = 0;
// Read data from stream using httplib::stream API
while (result.next()) {
buffer.append(result.data(), result.size());
// Process complete lines in the buffer
size_t line_start = 0;
size_t newline_pos;
while ((newline_pos = buffer.find('\n', line_start)) !=
std::string::npos) {
std::string line = buffer.substr(line_start, newline_pos - line_start);
line_start = newline_pos + 1;
// Parse the line and check if event is complete
bool event_complete = parse_sse_line(line, current_event, retry_ms);
if (event_complete && !current_event.data.empty()) {
// Event received - process it
event_count++;
std::cout << "--- Event #" << event_count << " ---\n";
std::cout << "Type: " << current_event.event << "\n";
std::cout << "Data: " << current_event.data << "\n";
if (!current_event.id.empty()) {
std::cout << "ID: " << current_event.id << "\n";
}
std::cout << "\n";
// Update last_event_id for reconnection
// Note: Empty id clears the last event ID per SSE spec
if (!current_event.id.empty()) { last_event_id = current_event.id; }
current_event.clear();
}
}
// Keep unprocessed data in buffer
buffer.erase(0, line_start);
}
//--------------------------------------------------------------------------
// Connection ended - check why
//--------------------------------------------------------------------------
if (result.read_error() != httplib::Error::Success) {
std::cerr << "\n[Connection #" << connection_count
<< "] Error: " << httplib::to_string(result.read_error())
<< "\n";
} else {
std::cout << "\n[Connection #" << connection_count
<< "] Stream ended normally\n";
}
std::cout << "Received " << event_count << " events in this connection\n";
std::cout << "Reconnecting in " << retry_ms << "ms...\n\n";
std::this_thread::sleep_for(std::chrono::milliseconds(retry_ms));
}
return 0;
}
+43 -7
View File
@@ -1,21 +1,57 @@
//
// ssecli.cc
//
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
// MIT License
//
#include <httplib.h>
#include <csignal>
#include <iostream>
using namespace std;
int main(void) {
httplib::Client("http://localhost:1234")
.Get("/event1", [&](const char *data, size_t data_length) {
std::cout << string(data, data_length);
return true;
});
// Global SSEClient pointer for signal handling
httplib::sse::SSEClient *g_sse = nullptr;
void signal_handler(int) {
if (g_sse) { g_sse->stop(); }
}
int main(void) {
// Configuration
const string host = "http://localhost:1234";
const string path = "/event1";
cout << "SSE Client using httplib::sse::SSEClient\n";
cout << "Connecting to: " << host << path << "\n";
cout << "Press Ctrl+C to exit\n\n";
httplib::Client cli(host);
httplib::sse::SSEClient sse(cli, path);
// Set up signal handler for graceful shutdown
g_sse = &sse;
signal(SIGINT, signal_handler);
// Event handlers
sse.on_open([]() { cout << "[Connected]\n\n"; });
sse.on_message([](const httplib::sse::SSEMessage &msg) {
cout << "Event: " << msg.event << "\n";
cout << "Data: " << msg.data << "\n";
if (!msg.id.empty()) { cout << "ID: " << msg.id << "\n"; }
cout << "\n";
});
sse.on_error([](httplib::Error err) {
cerr << "[Error] " << httplib::to_string(err) << "\n";
});
// Start with auto-reconnect (blocking)
sse.start();
cout << "\n[Disconnected]\n";
return 0;
}
+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);
});
});
+3 -3
View File
@@ -1,7 +1,7 @@
//
// upload.cc
//
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
// MIT License
//
@@ -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
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""This script generates httplib.cppm module file from httplib.h."""
import os
import sys
from argparse import ArgumentParser, Namespace
from typing import List
def main() -> None:
"""Main entry point for the script."""
args_parser: ArgumentParser = ArgumentParser(description=__doc__)
args_parser.add_argument(
"-o", "--out", help="where to write the files (default: out)", default="out"
)
args: Namespace = args_parser.parse_args()
cur_dir: str = os.path.dirname(sys.argv[0])
if not cur_dir:
cur_dir = '.'
lib_name: str = "httplib"
header_name: str = f"/{lib_name}.h"
# get the input file
in_file: str = f"{cur_dir}{header_name}"
# get the output file
cppm_out: str = f"{args.out}/{lib_name}.cppm"
# if the modification time of the out file is after the in file,
# don't generate (as it is already finished)
do_generate: bool = True
if os.path.exists(cppm_out):
in_time: float = os.path.getmtime(in_file)
out_time: float = os.path.getmtime(cppm_out)
do_generate: bool = in_time > out_time
if do_generate:
with open(in_file) as f:
lines: List[str] = f.readlines()
os.makedirs(args.out, exist_ok=True)
# Find the Headers and Declaration comment markers
headers_start: int = -1
declaration_start: int = -1
for i, line in enumerate(lines):
if ' * Headers' in line:
headers_start = i - 1 # Include the /* line
elif ' * Declaration' in line:
declaration_start = i - 1 # Stop before the /* line
break
with open(cppm_out, 'w') as fm:
# Write module file
fm.write("module;\n\n")
# Write global module fragment (from Headers to Declaration comment)
# Filter out 'using' declarations to avoid conflicts
if headers_start >= 0 and declaration_start >= 0:
for i in range(headers_start, declaration_start):
line: str = lines[i]
if 'using' not in line:
fm.write(line)
fm.write("\nexport module httplib;\n\n")
fm.write("export extern \"C++\" {\n")
fm.write(f"{' ' * 4}#include \"httplib.h\"\n")
fm.write("}\n")
print(f"Wrote {cppm_out}")
else:
print(f"{cppm_out} is up to date")
if __name__ == "__main__":
main()
+9620 -2906
View File
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
set shell := ["bash", "-c"]
default: list
list:
@just --list --unsorted
openssl:
@(cd test && make test && ./test)
@(cd test && make proxy)
mbedtls:
@(cd test && make test_mbedtls && ./test_mbedtls)
@(cd test && make proxy_mbedtls)
fuzz:
@(cd test && make fuzz_test)
build:
@(cd test && make test_split)
@(cd test && make test_split_mbedtls)
+42 -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: ['CFNetwork', '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,30 @@ if brotli_found_all
args += '-DCPPHTTPLIB_BROTLI_SUPPORT'
endif
zstd_dep = dependency('libzstd', required: get_option('zstd'))
if zstd_dep.found()
deps += zstd_dep
args += '-DCPPHTTPLIB_ZSTD_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', 'CoreFoundation'], 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 +117,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 +128,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 +141,6 @@ endif
meson.override_dependency('cpp-httplib', cpp_httplib_dep)
if get_option('cpp-httplib_test')
if get_option('test')
subdir('test')
endif
+17 -6
View File
@@ -2,9 +2,20 @@
#
# 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('zstd', type: 'feature', value: 'auto', description: 'Enable zstd 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')
+61 -51
View File
@@ -2,66 +2,76 @@
"""This script splits httplib.h into .h and .cc parts."""
import argparse
import os
import sys
from argparse import ArgumentParser, Namespace
from typing import List
border = '// ----------------------------------------------------------------------------'
BORDER: str = '// ----------------------------------------------------------------------------'
args_parser = argparse.ArgumentParser(description=__doc__)
args_parser.add_argument(
"-e", "--extension", help="extension of the implementation file (default: cc)",
default="cc"
)
args_parser.add_argument(
"-o", "--out", help="where to write the files (default: out)", default="out"
)
args = args_parser.parse_args()
def main() -> None:
"""Main entry point for the script."""
cur_dir = os.path.dirname(sys.argv[0])
lib_name = 'httplib'
header_name = '/' + lib_name + '.h'
source_name = '/' + lib_name + '.' + args.extension
# get the input file
in_file = cur_dir + header_name
# get the output file
h_out = args.out + header_name
cc_out = args.out + source_name
args_parser: ArgumentParser = ArgumentParser(description=__doc__)
args_parser.add_argument(
"-e", "--extension", help="extension of the implementation file (default: cc)", default="cc"
)
args_parser.add_argument(
"-o", "--out", help="where to write the files (default: out)", default="out"
)
args: Namespace = args_parser.parse_args()
# if the modification time of the out file is after the in file,
# don't split (as it is already finished)
do_split = True
cur_dir: str = os.path.dirname(sys.argv[0])
if not cur_dir:
cur_dir = '.'
lib_name: str = "httplib"
header_name: str = f"/{lib_name}.h"
source_name: str = f"/{lib_name}.{args.extension}"
# get the input file
in_file: str = f"{cur_dir}{header_name}"
# get the output file
h_out: str = f"{args.out}{header_name}"
cc_out: str = f"{args.out}{source_name}"
if os.path.exists(h_out):
in_time = os.path.getmtime(in_file)
out_time = os.path.getmtime(h_out)
do_split = in_time > out_time
# if the modification time of the out file is after the in file,
# don't split (as it is already finished)
do_split: bool = True
if do_split:
with open(in_file) as f:
lines = f.readlines()
if os.path.exists(h_out):
in_time: float = os.path.getmtime(in_file)
out_time: float = os.path.getmtime(h_out)
do_split: bool = in_time > out_time
if do_split:
with open(in_file) as f:
lines: List[str] = f.readlines()
python_version = sys.version_info[0]
if python_version < 3:
os.makedirs(args.out)
else:
os.makedirs(args.out, exist_ok=True)
in_implementation = False
cc_out = args.out + source_name
with open(h_out, 'w') as fh, open(cc_out, 'w') as fc:
fc.write('#include "httplib.h"\n')
fc.write('namespace httplib {\n')
for line in lines:
is_border_line = border in line
if is_border_line:
in_implementation = not in_implementation
elif in_implementation:
fc.write(line.replace('inline ', ''))
else:
fh.write(line)
fc.write('} // namespace httplib\n')
in_implementation: bool = False
cc_out: str = args.out + source_name
with open(h_out, 'w') as fh, open(cc_out, 'w') as fc:
# Write source file
fc.write("#include \"httplib.h\"\n")
fc.write("namespace httplib {\n")
# Process lines for header and source split
for line in lines:
is_border_line: bool = BORDER in line
if is_border_line:
in_implementation: bool = not in_implementation
elif in_implementation:
fc.write(line.replace("inline ", ""))
else:
fh.write(line)
fc.write("} // namespace httplib\n")
print("Wrote {} and {}".format(h_out, cc_out))
else:
print("{} and {} are up to date".format(h_out, cc_out))
print(f"Wrote {h_out} and {cc_out}")
else:
print(f"{h_out} and {cc_out} are up to date")
if __name__ == "__main__":
main()
+88 -17
View File
@@ -1,27 +1,44 @@
CXX = clang++
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow $(EXTRA_CXXFLAGS) # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS -fsanitize=address
PREFIX ?= $(shell brew --prefix)
OPENSSL_DIR = $(PREFIX)/opt/openssl@3
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow $(EXTRA_CXXFLAGS) -DCPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO -fsanitize=address # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS
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
PREFIX ?= $(shell brew --prefix)
OPENSSL_DIR = $(PREFIX)/opt/openssl@3
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
OPENSSL_SUPPORT += -DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN -framework Security
MBEDTLS_DIR ?= $(shell brew --prefix mbedtls@3)
MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -I$(MBEDTLS_DIR)/include -L$(MBEDTLS_DIR)/lib -lmbedtls -lmbedx509 -lmbedcrypto
MBEDTLS_SUPPORT += -DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN -framework Security
else
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -lssl -lcrypto
MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -lmbedtls -lmbedx509 -lmbedcrypto
endif
endif
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
ifneq ($(OS), Windows_NT)
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S), Darwin)
# macOS: use Homebrew paths for brotli and zstd
BROTLI_DIR = $(PREFIX)/opt/brotli
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
ZSTD_DIR = $(PREFIX)/opt/zstd
ZSTD_SUPPORT = -DCPPHTTPLIB_ZSTD_SUPPORT -I$(ZSTD_DIR)/include -L$(ZSTD_DIR)/lib -lzstd
LIBS = -lpthread -lcurl -framework CoreFoundation -framework CFNetwork
else
# Linux: use system paths
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -lbrotlicommon -lbrotlienc -lbrotlidec
ZSTD_SUPPORT = -DCPPHTTPLIB_ZSTD_SUPPORT -lzstd
LIBS = -lpthread -lcurl -lanl
endif
endif
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
TEST_ARGS = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(ZSTD_SUPPORT) $(LIBS)
TEST_ARGS_MBEDTLS = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(MBEDTLS_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(ZSTD_SUPPORT) $(LIBS)
TEST_ARGS_NO_TLS = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(ZSTD_SUPPORT) $(LIBS)
# By default, use standalone_fuzz_target_runner.
# This runner does no fuzzing, but simply executes the inputs
@@ -37,10 +54,45 @@ STYLE_CHECK_FILES = $(filter-out httplib.h httplib.cc, \
$(wildcard example/*.h example/*.cc fuzzing/*.h fuzzing/*.cc *.h *.cc ../httplib.h))
all : test test_split
./test
LSAN_OPTIONS=suppressions=lsan_suppressions.txt ./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
proxy_mbedtls : test_proxy_mbedtls
@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 (Mbed TLS)..."
./test_proxy_mbedtls; \
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)
@@ -51,6 +103,22 @@ test : test.cc include_httplib.cc ../httplib.h Makefile cert.pem
test_split : test.cc ../httplib.h httplib.cc Makefile cert.pem
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS)
# Mbed TLS backend targets
test_mbedtls : test.cc include_httplib.cc ../httplib.h Makefile cert.pem
$(CXX) -o $@ -I.. $(CXXFLAGS) test.cc include_httplib.cc $(TEST_ARGS_MBEDTLS)
@file $@
test_split_mbedtls : test.cc ../httplib.h httplib.cc Makefile cert.pem
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS_MBEDTLS)
# No TLS
test_no_tls : test.cc include_httplib.cc ../httplib.h Makefile
$(CXX) -o $@ -I.. $(CXXFLAGS) test.cc include_httplib.cc $(TEST_ARGS_NO_TLS)
@file $@
test_split_no_tls : test.cc ../httplib.h httplib.cc Makefile
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS_NO_TLS)
check_abi:
@./check-shared-library-abi-compatibility.sh
@@ -79,6 +147,9 @@ style_check: $(STYLE_CHECK_FILES)
test_proxy : test_proxy.cc ../httplib.h Makefile cert.pem
$(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS)
test_proxy_mbedtls : test_proxy.cc ../httplib.h Makefile cert.pem
$(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS_MBEDTLS)
# Runs server_fuzzer.cc based on value of $(LIB_FUZZING_ENGINE).
# Usage: make fuzz_test LIB_FUZZING_ENGINE=/path/to/libFuzzer
fuzz_test: server_fuzzer
@@ -86,7 +157,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
@@ -101,5 +172,5 @@ cert.pem:
./gen-certs.sh
clean:
rm -rf test test_split test_proxy server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc _build* *.dSYM
rm -rf test test_split test_mbedtls test_split_mbedtls test_no_tls, test_split_no_tls test_proxy test_proxy_mbedtls server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc _build* *.dSYM
+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:
+1 -1
View File
@@ -14,5 +14,5 @@ openssl genrsa 2048 > client.key.pem
openssl req -new -batch -config test.conf -key client.key.pem | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client.cert.pem
openssl genrsa -passout pass:test123! 2048 > key_encrypted.pem
openssl req -new -batch -config test.conf -key key_encrypted.pem | openssl x509 -days 3650 -req -signkey key_encrypted.pem > cert_encrypted.pem
openssl genrsa -aes256 -passout pass:test012! 2048 > client_encrypted.key.pem
openssl genrsa 2048 | openssl pkcs8 -topk8 -v1 PBE-SHA1-3DES -passout pass:test012! -out client_encrypted.key.pem
openssl req -new -batch -config test.conf -key client_encrypted.key.pem -passin pass:test012! | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client_encrypted.cert.pem
+1 -1
View File
@@ -993,7 +993,7 @@ void UniversalTersePrint(const T& value, ::std::ostream* os) {
// NUL-terminated string.
template <typename T>
void UniversalPrint(const T& value, ::std::ostream* os) {
// A workarond for the bug in VC++ 7.1 that prevents us from instantiating
// A workaround for the bug in VC++ 7.1 that prevents us from instantiating
// UniversalPrinter with T directly.
typedef T T1;
UniversalPrinter<T1>::Print(value, os);
@@ -753,7 +753,7 @@ class ParameterizedTestSuiteRegistry {
};
// Keep track of what type-parameterized test suite are defined and
// where as well as which are intatiated. This allows susequently
// where as well as which are intatiated. This allows subsequently
// identifying suits that are defined but never used.
class TypeParameterizedTestSuiteRegistry {
public:
@@ -764,7 +764,7 @@ class TypeParameterizedTestSuiteRegistry {
// Add an instantiation of a suit.
void RegisterInstantiation(const char* test_suite_name);
// For each suit repored as defined but not reported as instantiation,
// For each suit reported as defined but not reported as instantiation,
// emit a test that reports that fact (configurably, as an error).
void CheckForInstantiations();
+3
View File
@@ -0,0 +1,3 @@
# OpenSSL 3.x internal caches (provider, cipher, keymgmt) are allocated
# lazily and intentionally kept until process exit. These are not real leaks.
leak:libcrypto
+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
+7197 -579
View File
File diff suppressed because it is too large Load Diff
+41 -31
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");
@@ -17,7 +26,7 @@ TEST(ProxyTest, NoSSLBasic) {
ProxyTest(cli, true);
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#ifdef CPPHTTPLIB_SSL_ENABLED
TEST(ProxyTest, SSLBasic) {
SSLClient cli("nghttp2.org");
ProxyTest(cli, true);
@@ -42,7 +51,7 @@ void RedirectProxyText(T &cli, const char *path, bool basic) {
if (basic) {
cli.set_proxy_basic_auth("hello", "world");
} else {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#ifdef CPPHTTPLIB_SSL_ENABLED
cli.set_proxy_digest_auth("hello", "world");
#endif
}
@@ -58,7 +67,7 @@ TEST(RedirectTest, HTTPBinNoSSLBasic) {
RedirectProxyText(cli, "/httpbin/redirect/2", true);
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#ifdef CPPHTTPLIB_SSL_ENABLED
TEST(RedirectTest, HTTPBinNoSSLDigest) {
Client cli("nghttp2.org");
RedirectProxyText(cli, "/httpbin/redirect/2", false);
@@ -75,13 +84,13 @@ TEST(RedirectTest, HTTPBinSSLDigest) {
}
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#ifdef CPPHTTPLIB_SSL_ENABLED
TEST(RedirectTest, YouTubeNoSSLBasic) {
Client cli("youtube.com");
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);
}
@@ -143,20 +153,20 @@ template <typename T> void BaseAuthTestFromHTTPWatch(T &cli) {
}
TEST(BaseAuthTest, NoSSL) {
Client cli("httpbin.org");
Client cli("httpcan.org");
BaseAuthTestFromHTTPWatch(cli);
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#ifdef CPPHTTPLIB_SSL_ENABLED
TEST(BaseAuthTest, SSL) {
SSLClient cli("httpbin.org");
SSLClient cli("httpcan.org");
BaseAuthTestFromHTTPWatch(cli);
}
#endif
// ----------------------------------------------------------------------------
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#ifdef CPPHTTPLIB_SSL_ENABLED
template <typename T> void DigestAuthTestFromHTTPWatch(T &cli) {
cli.set_proxy("localhost", 3129);
cli.set_proxy_digest_auth("hello", "world");
@@ -172,15 +182,17 @@ template <typename T> void DigestAuthTestFromHTTPWatch(T &cli) {
"/digest-auth/auth/hello/world/MD5",
"/digest-auth/auth/hello/world/SHA-256",
"/digest-auth/auth/hello/world/SHA-512",
"/digest-auth/auth-int/hello/world/MD5",
};
cli.set_digest_auth("hello", "world");
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);
std::string algo(path.substr(path.rfind('/') + 1));
EXPECT_EQ(
normalizeJson("{\"algorithm\":\"" + algo +
"\",\"authenticated\":true,\"user\":\"hello\"}\n"),
normalizeJson(res->body));
EXPECT_EQ(StatusCode::OK_200, res->status);
}
@@ -191,24 +203,22 @@ template <typename T> void DigestAuthTestFromHTTPWatch(T &cli) {
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
}
// NOTE: Until httpbin.org fixes issue #46, the following test is commented
// out. Please see https://httpbin.org/digest-auth/auth/hello/world
// cli.set_digest_auth("bad", "world");
// for (auto path : paths) {
// auto res = cli.Get(path.c_str());
// ASSERT_TRUE(res != nullptr);
// EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
// }
cli.set_digest_auth("bad", "world");
for (auto path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
}
}
}
TEST(DigestAuthTest, SSL) {
SSLClient cli("httpbin.org");
SSLClient cli("httpcan.org");
DigestAuthTestFromHTTPWatch(cli);
}
TEST(DigestAuthTest, NoSSL) {
Client cli("httpbin.org");
Client cli("httpcan.org");
DigestAuthTestFromHTTPWatch(cli);
}
#endif
@@ -220,13 +230,13 @@ template <typename T> void KeepAliveTest(T &cli, bool basic) {
if (basic) {
cli.set_proxy_basic_auth("hello", "world");
} else {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#ifdef CPPHTTPLIB_SSL_ENABLED
cli.set_proxy_digest_auth("hello", "world");
#endif
}
cli.set_follow_location(true);
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#ifdef CPPHTTPLIB_SSL_ENABLED
cli.set_digest_auth("hello", "world");
#endif
@@ -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);
}
}
@@ -264,7 +274,7 @@ template <typename T> void KeepAliveTest(T &cli, bool basic) {
}
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#ifdef CPPHTTPLIB_SSL_ENABLED
TEST(KeepAliveTest, NoSSLWithBasic) {
Client cli("nghttp2.org");
KeepAliveTest(cli, true);