Compare commits

...

1374 Commits

Author SHA1 Message Date
yhirose ff04679538 Release v0.42.0 2026-04-11 18:53:36 -04:00
yhirose d97749a315 Update README 2026-04-11 17:15:37 -04:00
yhirose 994d76ab39 Fix #2422 2026-04-11 15:38:35 -04:00
yhirose 529dafdee3 Add Cookbook other topics (draft) 2026-04-10 19:02:44 -04:00
yhirose 361b753f19 Add Cookbook S01-S22 (draft) 2026-04-10 18:47:42 -04:00
yhirose 61e533ddc5 Add Cookbook C01-C19 (draft) 2026-04-10 18:16:02 -04:00
yhirose 783de4ec4e Update README 2026-04-08 18:35:56 -04:00
yhirose 7a7f9b30e7 Update README 2026-04-08 18:22:25 -04:00
yhirose 834a444435 Fixed warnings 2026-04-08 18:10:34 -04:00
yhirose 4f589c1ffb Fix #2421 2026-04-08 18:09:22 -04:00
Jiri Slaby fc885cc62d test: WebSocketIntegrationTest.SocketSettings: do not set AF_INET (#2420)
The server listens on AF_INET6 only (::1), so the test fails:
 [ RUN      ] WebSocketIntegrationTest.SocketSettings
 test/test.cc:17160: Failure
 Value of: client.connect()
   Actual: false
 Expected: true

Fixes #2419.

Co-authored-by: Jiri Slaby <jslaby@suse.cz>
2026-04-08 07:48:13 -04:00
yhirose ca82c93772 Refactor SSLVerifierResponse to enum class and add get_param_values method to Request 2026-04-04 00:02:26 -04:00
yhirose 073b587962 Release v0.41.0 2026-04-03 21:50:24 -04:00
yhirose 96785eea21 Add parse_url 2026-04-03 20:54:17 -04:00
yhirose 3093bdd9ab Fix #2416 2026-04-03 18:27:12 -04:00
crueter 6607a6a592 [cmake] Allow using pre-existing zstd target if it exists (#2390)
adds support for pre-existing `zstd::libzstd` which is useful for
projects that bundle their own zstd in a way that doesn't get caught by
`CONFIG`

Signed-off-by: crueter <crueter@eden-emu.dev>
2026-03-30 21:26:20 -04:00
DavidKorczynski 831b64bdeb Add two new fuzzers (#2412)
The goal is to increase code coverage by way of OSS-Fuzz. A recent code
coverage report is available at
https://storage.googleapis.com/oss-fuzz-coverage/cpp-httplib/reports/20260326/linux/report.html

Signed-off-by: David Korczynski <david@adalogics.com>
2026-03-28 15:00:10 -04:00
yhirose 32c82492de Add workflow_dispatch trigger to docs deployment workflow 2026-03-28 11:08:25 -04:00
yhirose b7e02de4a7 Release v0.40.0 2026-03-28 00:57:24 -04:00
yhirose a9359df42e Optimize multipart content provider to coalesce small writes and reduce TCP packet fragmentation (Fix #2410) 2026-03-28 00:23:59 -04:00
yhirose 9a97e948f0 Add set_socket_opt function and corresponding test for TCP_NODELAY option (Resolve #2411) 2026-03-28 00:23:59 -04:00
yhirose 6fd97aeca0 Implement request body consumption and reject invalid Content-Length with Transfer-Encoding to prevent request smuggling 2026-03-27 23:16:08 -04:00
yhirose 05540e4d50 Fixed warnings 2026-03-27 22:35:22 -04:00
yhirose ceefc14e7d Use go-httplibbin 2026-03-27 22:26:14 -04:00
yhirose a77284a634 Release v0.39.0 2026-03-23 23:14:05 -04:00
yhirose 315a87520d Add release script and update .gitignore for work directory 2026-03-23 23:06:37 -04:00
yhirose 703abbb53b Prevent forwarding of authentication credentials during cross-host redirects as per RFC 9110. Add tests for basic auth and bearer token scenarios. 2026-03-23 22:32:53 -04:00
yhirose cb8365349f Fix #2404 (Refactor make_file_body to improve file handling and scope management) 2026-03-22 22:40:01 -04:00
yhirose 3792ce0da7 Add socket configuration options and corresponding test case for WebSocketClient. Fix #2401 2026-03-22 22:31:59 -04:00
yhirose 7178f451a4 "Building a Desktop LLM App with cpp-httplib" (#2403) 2026-03-21 23:31:55 -04:00
yhirose c2bdb1c5c1 SSE Client: Update Authorization Header
Fixes #2402
2026-03-21 13:17:28 -04:00
yhirose 45820de332 Enhance stream handling in LongPollingTest and add new test for client close detection 2026-03-18 18:29:19 -04:00
yhirose dd8071a7d4 Fix #2397 2026-03-17 17:39:57 -04:00
v-nam c59ef98b3b [cmake] Update modules.cmake to fix cmake error (#2393)
* Update modules.cmake

* tst1
2026-03-17 12:09:51 -04:00
yhirose 1c6b3ea5a0 Add status: "draft" to multiple documentation pages and enhance navigation sections 2026-03-14 23:46:14 -04:00
yhirose 4a1e9443ee Update deprecation messages to indicate removal in v1.0.0 2026-03-14 23:32:07 -04:00
yhirose 6f2717e623 Release v0.38.0 2026-03-14 23:17:13 -04:00
yhirose 257b266190 Add runtime configuration for WebSocket ping interval and related tests 2026-03-14 22:44:17 -04:00
yhirose ba0d0b82db Add benchmark tests and related configurations for performance evaluation 2026-03-14 22:16:53 -04:00
yhirose 5ecba74a99 Remove large data tests for GzipDecompressor and SSLClientServerTest due to memory issues 2026-03-14 21:50:55 -04:00
yhirose ec1ffbc27d Add Brotli compression support and corresponding tests 2026-03-14 21:42:48 -04:00
yhirose 4978f26f86 Fix port number in OpenStreamMalformedContentLength test to avoid conflicts 2026-03-14 20:59:15 -04:00
yhirose bb7c7ab075 Add quality parameter parsing for Accept-Encoding header and enhance encoding type selection logic 2026-03-14 20:51:25 -04:00
yhirose 1c3d35f83c Update comment to clarify requirements for safe handling in ClientImpl::handle_request 2026-03-14 19:27:51 -04:00
yhirose b1bb2b7ecc Implement setup_proxy_connection method for SSLClient and refactor proxy handling in open_stream 2026-03-14 19:26:31 -04:00
yhirose f6ed5fc60f Add SSL support for proxy connections in open_stream and corresponding test 2026-03-14 18:38:34 -04:00
yhirose 69d468f4d9 Enable BindDualStack test and remove disabled large content test due to memory issues 2026-03-14 18:22:27 -04:00
yhirose 2e61fd3e6e Update documentation to clarify progress callback usage and user data handling in examples 2026-03-13 22:54:29 -04:00
yhirose 3ad4a4243a Update .gitignore to include AGENTS.md and related documentation files 2026-03-13 22:31:44 -04:00
yhirose 511e3ef9e5 Update README.md to enhance TLS backend documentation and clarify platform-specific certificate handling 2026-03-13 22:30:05 -04:00
yhirose f787f31b87 Implement symlink protection in static file server and add corresponding tests 2026-03-13 16:22:16 -04:00
yhirose 43a54a3e3d Add tests for Unicode path component decoding in decode_path_component function 2026-03-13 00:32:41 -04:00
yhirose 83e98a28dd Add filename sanitization function and tests to prevent path traversal vulnerabilities 2026-03-13 00:29:13 -04:00
yhirose 4d7c9a788d Release v0.37.2 2026-03-13 00:11:00 -04:00
yhirose 1cd0347ace Refactor parse_port function to accept char pointer and length, improving flexibility and validation 2026-03-13 00:05:13 -04:00
yhirose b3a8af80b9 Add port validation and corresponding tests to prevent overflow and out-of-range values 2026-03-13 00:05:13 -04:00
yhirose 1e97c28e36 Implement request smuggling protection for duplicate Content-Length headers and add corresponding tests 2026-03-13 00:05:13 -04:00
yhirose d279eff4db Fix the proxy test error 2026-03-12 23:15:10 -04:00
yhirose 188035fb6d Add a test for the previous change 2026-03-12 22:57:11 -04:00
yhirose 125272f34b Fix TLS cert verification bypass on proxy redirect introduced in #2165 (#2396) 2026-03-12 21:54:51 -04:00
yhirose 9ced2f614d Fix #2395 2026-03-12 19:18:09 -04:00
yhirose 68fa9bce0f Release v0.37.1 2026-03-09 20:51:37 -04:00
yhirose 1f34c541b0 Add more books 2026-03-09 19:30:18 -04:00
yhirose e41ec36274 Fix handling of malformed Content-Length in open_stream and add tests 2026-03-08 22:30:20 -04:00
yhirose 7489fd3a8b Remove 32-bit limitation (#2388)
* Remove 32-bit limitation

* Fix build problems

* Add 32-bit disclaimer and fix MSVC x86 warnings

- Move 32-bit warning to top of README with strong disclaimer
- Add static_cast<size_t> to fix truncation warnings on 32-bit MSVC

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:07:21 -05:00
yhirose ab3098f18b Update README 2026-03-06 18:50:23 -05:00
yhirose 0d3a3d805c Release v0.37.0 2026-03-06 16:57:33 -05:00
yhirose a058e31a3a Use doc.yml to generate document site 2026-03-05 20:50:22 -05:00
yhirose c87d442aac Remove docs-gen 2026-03-03 23:58:03 -05:00
yhirose de296af3eb Fix problem with alternate file name check 2026-03-03 13:37:28 -05:00
yhirose 41899acece Release v0.36.0 2026-03-02 21:56:38 -05:00
yhirose bb22553087 Fix code format 2026-03-02 21:49:10 -05:00
yhirose 8bffbe3ff2 Merge commit from fork 2026-03-02 21:46:31 -05:00
yhirose 0e4f104141 Fix #2383 (#2385) 2026-03-02 21:09:31 -05:00
yhirose 74807d24a7 Update pages-data.json with new content and structure for cpp-httplib documentation 2026-03-02 17:22:52 -05:00
yhirose 2e124cde02 Enhance documentation and configuration for static site generator
- Update README for clarity and quick start instructions
- Refine default config.toml with hostname and base path
- Adjust index.md files for consistent heading levels
- Simplify CSS for code block styling and remove unnecessary theme switching
- Refactor SiteConfig to derive full base URL from hostname and base path
- Update MarkdownRenderer to remove light theme handling
2026-03-02 00:48:14 -05:00
yhirose fdb589d97e Update dependency 2026-03-01 23:55:52 -05:00
yhirose 843ad379c7 Add search functionality across documentation pages
- Implemented a search button in the header of each documentation page.
- Added a search modal that allows users to input search queries.
- Integrated a JavaScript search feature that fetches and displays results from a new `pages-data.json` file.
- Each documentation page now includes a search overlay for improved navigation.
- Updated the main JavaScript file to handle search logic, including result highlighting and navigation.
- Created a `pages-data.json` file containing metadata for all documentation pages to facilitate search functionality.
2026-03-01 23:37:51 -05:00
yhirose cda9680cdc Update document site 2026-03-01 23:12:45 -05:00
yhirose e906c31a79 Add favicon and update navigation icons across documentation
- Added a favicon link to all tour pages in the Japanese documentation.
- Updated navigation links to include SVG icons for Home and GitHub.
- Changed language button to include an SVG icon for better visual representation.
- Improved theme toggle button to use SVG icons for light and dark modes.
- Refactored the documentation build commands in the justfile for clarity and consistency.
2026-03-01 23:04:38 -05:00
yhirose 2d669c3636 Refactor main function to display help message when no command is provided 2026-03-01 23:04:38 -05:00
yhirose b2d76658fc Add default templates, styles, and scripts for documentation site 2026-03-01 23:04:38 -05:00
Charles von Kalm 7444646627 Removed unused local variable (#2382) 2026-03-01 18:02:57 -05:00
Matheus Gabriel Werny 82a61a6b60 [CMake] New compoments MbedTLS and wolfSSL (#2360)
* [CMake] New component MbedTLS

New component MbedTLS.

* Fix case

Fix case: HTTPLIB_REQUIRE_OPENSSL=OFF; HTTPLIB_REQUIRE_MBEDTLS=ON

* [CMake] Test target MbedTLS::tfpsacrypto

[CMake] Test target MbedTLS::tfpsacrypto.

* [CMake] Test MbedTLS::mbedx509

[CMake] Test MbedTLS::mbedx509.

* Revert "[CMake] Test MbedTLS::mbedx509"

This reverts commit 1d0b91f59a.

* Revert "[CMake] Test target MbedTLS::tfpsacrypto"

This reverts commit bf099f6264.

* Fix problem caused by the recent performance improvement

* wolfSSL support

wolfSSL support.
Partly solve https://github.com/yhirose/cpp-httplib/issues/2371. Only
meson is missing.

* Solve https://github.com/yhirose/cpp-httplib/issues/2361

Solve https://github.com/yhirose/cpp-httplib/issues/2361.
Apply `WARNING`.

* Fix variable

Fix variable.

* [CMake] Solve incompatibilities with loop

Solve incompatibilities with loop.

* Fix

Fix.

* Remove debug prints

Remove debug prints.

* [CMake] Fix bug

Prevent a bug aus the required and if available libraries are checked
independently from each other. A could be chosen in required but B could
be chosen in if available and everything would pass.

* Remove debug print

Remove debug print.

* Restore change

Restore change.

---------

Co-authored-by: yhirose <yuji.hirose.bug@gmail.com>
2026-03-01 18:00:36 -05:00
yhirose 63ede29db1 Update README 2026-03-01 17:34:03 -05:00
yhirose ae64a5ee90 Fix #2381 2026-02-28 22:07:44 -05:00
yhirose f441fc33fb Update README with documentation link and formatting
Added a line break and a link to the official documentation.
2026-02-28 21:58:58 -05:00
yhirose a9fc935919 Add a link to GitHub 2026-02-28 21:25:01 -05:00
yhirose b6cd71d4ff Update docs/ 2026-02-28 20:58:42 -05:00
yhirose bda599bfb4 Fix base_dir for GitHub PageS 2026-02-28 20:56:46 -05:00
yhirose 797758a742 Documentation Site on GitHub Pages (#2376)
* Add initial documentations

* Update documentation for Basic Client and add WebSocket section

* feat: add a static site generator with multi-language support

- Introduced a new Rust-based static site generator in the `docs-gen` directory.
- Implemented core functionality for building sites from markdown files, including:
  - Configuration loading from `config.toml`.
  - Markdown rendering with frontmatter support.
  - Navigation generation based on page structure.
  - Static file copying and output directory management.
- Added templates for base layout, pages, and portal.
- Created a CSS file for styling and a JavaScript file for interactive features like language selection and theme toggling.
- Updated documentation source with new configuration and example pages in English and Japanese.
- Added a `justfile` target for building the documentation site.

* Add language/theme toggle functionality

- Created a new Japanese tour index page at docs/ja/tour/index.html
- Implemented navigation links for various sections of the cpp-httplib tutorial
- Added a language selector to switch between English and Japanese
- Introduced theme toggle functionality to switch between light and dark modes
- Added mobile sidebar toggle for better navigation on smaller screens
2026-02-28 14:45:40 -05:00
yhirose 85b18a9c64 Release v0.35.0 2026-02-27 22:14:27 -05:00
yhirose 0853ce7753 Fix #2379 2026-02-27 22:08:27 -05:00
yhirose c99d7472b5 Merge commit from fork 2026-02-27 21:36:04 -05:00
yhirose defd907c74 Merge commit from fork 2026-02-27 21:35:32 -05:00
yhirose c6c75e4c69 Remove tag-latest.yml 2026-02-23 00:01:58 -05:00
yhirose c2002f6e06 Make loading system certificates from the Keychain on macOS an opt-out feature (#2377) 2026-02-22 19:18:40 -05:00
yhirose 7c33fd47bf Release v0.34.0 2026-02-22 17:27:02 -05:00
yhirose 21243b3c9e Fix problem caused by the recent performance improvement 2026-02-22 13:02:40 -05:00
yhirose e068da4f6b Fix race condittion with logging 2026-02-22 08:46:30 -05:00
yhirose f6aec98145 Fix SocketStream.wait_writable_INET test 2026-02-22 08:39:08 -05:00
yhirose f29bb15f9d Performance improvement! 2026-02-22 07:53:23 -05:00
yhirose c53d93d145 Add make_file_body 2026-02-22 07:53:23 -05:00
yhirose b4d16a582d Update latest tag when new release 2026-02-21 23:27:16 -05:00
yhirose e61a8bcec7 Release v0.33.1 2026-02-21 08:55:06 -05:00
yhirose 33dbe00cce Fix compiple problem with C++11 compiler 2026-02-21 08:52:50 -05:00
yhirose f1f8ff53d5 Release v0.33.0 2026-02-21 08:43:49 -05:00
yhirose 9b9bda6b6e Fix CI build errors 2026-02-21 08:14:47 -05:00
yhirose 2280f1d191 Improvement for Multipart Form Data 2026-02-20 23:15:01 -05:00
yhirose 43cf1822c6 Resolve #2369 2026-02-20 16:22:01 -05:00
yhirose 17f301877f Add bench command in justfile 2026-02-20 15:45:04 -05:00
yhirose 4f17fbaa03 Missing change for wolfSSL support 2026-02-20 15:44:31 -05:00
yhirose 0d5bf55c73 Add wolfSSL support (#2370)
* Add wolfSSL support

* Update CI

* Fix build error

* Revert "Fix build error"

This reverts commit d48096277fd53777988d23dfdc53d9ce6bbc334c.

* Fix build errors

* Build errors on ubuntu

* Update README

* Refactoring

* Fix wolfSSL issues
2026-02-20 15:42:45 -05:00
yhirose 718d7d92b9 Fix problems in Unit tests 2026-02-16 06:22:18 -05:00
yhirose c41c5fb8a9 Revise README features section and header
Updated section headers and improved feature list formatting.
2026-02-14 23:22:42 -05:00
yhirose ab96f72b96 Update README 2026-02-14 20:06:01 -05:00
yhirose b2430249d2 Update README-websocket.md and justfile 2026-02-14 17:59:15 -05:00
yhirose 464867a9ce WebSocket and Dynamic Thread Pool support (#2368)
* WebSocket support

* Validate selected subprotocol in WebSocket handshake

* Fix problem with a Unit test

* Dynamic Thread Pool support

* Fix race condition in new Dynamic ThreadPool
2026-02-14 17:44:49 -05:00
Kostia Sokolovskyi d4180e923f Fix comparison of integers of different signs warning when compiling with BoringSSL. (#2367) 2026-02-13 17:25:59 -05:00
Adrien Gallouët 8d225afc8c Remove macOS select() fallback (#2365)
* Remove macOS select() fallback

macOS has supported `poll` for a long time now, so there's no need for
the specific `select` code paths.

With this commit, we can successfully build on visionOS.

Signed-off-by: Adrien Gallouët <adrien@gallouet.fr>

* Fix coding style

Signed-off-by: Adrien Gallouët <adrien@gallouet.fr>

---------

Signed-off-by: Adrien Gallouët <adrien@gallouet.fr>
2026-02-13 13:44:06 -05:00
yhirose ed5c5d325b Parallel test on CI (#2364)
* Parallel test on CI

* Fix problem with Windows

* Use cache for vcpkg

* Parallel 'No Exception' test

* Use one job to run all shards
2026-02-13 01:55:30 -05:00
yhirose c1ee85d89e Use iptables to disable network (#2363)
* Use iptables to disable network

* Fix race condition problem

* Enable network after test finishes
2026-02-12 22:46:26 -05:00
yhirose 14e37bd75b Offline test (Resolve #2356) (#2358)
* Offline test

* Disabled network

* Removed MbedTLS
2026-02-12 16:31:23 -05:00
yhirose c0adbb4b20 Release v0.32.0 2026-02-12 15:24:12 -05:00
yhirose f80864ca03 Resolve #2359 2026-02-11 14:25:27 -10:00
Adrien Gallouët 4e75a84b39 Fix compilation on BoringSSL by replacing ASN1_TIME_to_tm (#2354)
* Fix compilation on BoringSSL by replacing ASN1_TIME_to_tm

BoringSSL doesn't expose `ASN1_TIME_to_tm`.
This patch switches to using `ASN1_TIME_diff` to calculate `time_t`.
This is supported by OpenSSL, LibreSSL, and BoringSSL, and also avoids
the platform-specific `timegm` vs `_mkgmtime` logic.

Signed-off-by: Adrien Gallouët <adrien@gallouet.fr>

* Format code

Signed-off-by: Adrien Gallouët <adrien@gallouet.fr>

* Use detail::scope_exit

Signed-off-by: Adrien Gallouët <adrien@gallouet.fr>

---------

Signed-off-by: Adrien Gallouët <adrien@gallouet.fr>
2026-02-11 07:55:07 -10:00
Justin 77d945d3b6 Correct sign comparison error with sk_X509_OBJECT_num (#2355)
* Correct sign comparison error with sk_X509_OBJECT_num

In some build configurations, sk_X509_OBJECT_num (from BoringSSL) returns a size_t. Comparing this directly against a signed int loop counter triggers -Werror,-Wsign-compare.

Instead, move the count into a local int variable so the compiler uses implicit conversion to ensure type consistency during the loop comparison.

* Update httplib.h

* Update httplib.h

Missed a s/int/decltype(count)
2026-02-11 07:54:35 -10:00
yhirose f69737a838 Fix problem with PayloadMaxLengthTest.NoContentLengthPayloadLimit 2026-02-10 23:38:57 -10:00
yhirose a188913b02 Merge branch 'master' of github.com:yhirose/cpp-httplib 2026-02-10 23:23:13 -10:00
yhirose 02e4c53685 Fix 'no TLS' problem with RequestWithoutContentLengthOrTransferEncoding 2026-02-10 23:21:43 -10:00
Alexey Sokolov 8c4370247a Add support for mbedtls to meson (#2350)
Related: #2345
2026-02-10 09:51:37 -10:00
yhirose 1f1a799d13 Fix #2351 2026-02-09 16:43:02 -10:00
yhirose a875292153 Move stream and sse implementations from the decl area to the impl area. (#2352) 2026-02-09 16:41:49 -10:00
yhirose f0b7d4161d Update justfile 2026-02-09 15:12:12 -10:00
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
yhirose 787a34ad7f Release v0.20.0 2025-03-16 21:24:53 -04:00
yhirose 7a212cfe40 clang-format 2025-03-16 21:22:53 -04:00
Alexandre Bouvier 0be0526085 cmake: only validate component when the required library is found (#2112) 2025-03-16 21:05:55 -04:00
yhirose 87a5ae64a4 Fix #2097 2025-03-16 20:57:17 -04:00
yhirose 33acccb346 Fix #2109 2025-03-16 20:36:15 -04:00
davidalo c765584e6b Add zstd support (#2088)
* Add zstd support

* Add zstd to CI tests

* Use use zstd cmake target instead of ZSTD. Use cmake variable for found packages

* Add missing comment for HTTPLIB_REQUIRE_ZSTD

* Fix test.yaml rebase error

* Use zstd::libzstd target

* Add include and library paths to ZSTD args

* Run clang-format

* Add zstd to httplibConfig.cmake.in
2025-03-16 15:51:53 -04:00
yhirose 0bda3a7d1a Update benchmark 2025-03-13 21:54:05 -04:00
Florian Albrechtskirchinger 2eaa2ea64f Make random_string() thread-safe (#2110)
By making the random engine thread_local, each thread now has its own
independent random sequence, ensuring safe concurrent access.
Additionally, using an immediately invoked lambda expression to
initialize the engine eliminates the need for separate static seed
variables.
2025-03-13 12:44:43 -04:00
Florian Albrechtskirchinger 94a4028821 Update vendored gtest to 1.12.1 (#2100)
Update googletest to the last version supporting C++11.
2025-03-12 12:16:27 -04:00
Florian Albrechtskirchinger a8d6172250 Avoid static std::string (#2103)
Replace static std::string objects with constexpr character arrays and
use compile-time string length calculations.
2025-03-12 12:12:54 -04:00
Florian Albrechtskirchinger 2f39723d08 Wrap poll()/WSAPoll() in a function and build compiled library on Windows (#2107)
* Wrap poll()/WSAPoll() in a function

Instead of using a macro for poll() on Windows, which breaks when the
implementation is compiled separately, add a detail::poll_wrapper()
function that dispatches to either ::poll() or ::WSAPoll().

* Build compiled library on Windows
2025-03-12 12:12:03 -04:00
Florian Albrechtskirchinger a9ba0a4dff Remove SSLInit (#2102)
Quote: "As of version 1.1.0 OpenSSL will automatically allocate all
resources that it needs so no explicit initialisation is required."
2025-03-12 12:10:02 -04:00
Andrea Pappacoda 37399af996 build(meson): copy MountTest.MultibytesPathName files (#2098)
Fixes the test suite in Meson.
2025-03-11 13:10:30 -04:00
yhirose 48084d55f2 Fix #2096 2025-03-10 23:31:51 -04:00
Florian Albrechtskirchinger 5a1ecc3958 Run 32-bit compiled unit tests on Ubuntu (#2095) 2025-03-06 21:17:41 -05:00
Florian Albrechtskirchinger 85b5cdd78d Move detail::read_file() to test/test.cc (#2092)
The unit test code is the only user of the function.

read_file() now throws an exception if the file isn't found.
2025-03-06 11:58:55 -05:00
Florian Albrechtskirchinger f2928d7152 Switch redirect tests to httpbingo.org (#2090)
Redirect tests fail using httpbin.org or nghttp2.org/httpbin. The
location header value contains a string representation of a Python byte
string (e.g., b'http://www.google.com/'), which results in a 404 error.
2025-03-06 11:55:11 -05:00
Florian Albrechtskirchinger ee0bee3907 Fix HttpWatch tests (#2089)
HttpWatch changed the formatting of the returned JSON. Normalize it by
removing all whitespace.
2025-03-06 07:17:05 -05:00
yhirose 71ba7e7b1b Fix #2068 (#2080)
* Fix #2068

* Add unit test
2025-02-20 23:45:21 -05:00
Florian Albrechtskirchinger ebe7efa1cc Parallelize testing with/without SSL on Windows & set concurrency group (#2079)
* Parallelize testing with/without SSL on Windows

* Set concurrency group in workflows
2025-02-20 20:57:18 -05:00
Florian Albrechtskirchinger 22d90c29b4 Remove select() and use poll() (#2078)
* Revert "Fix typo in meson.build (#2070)"

This reverts commit 5c0135fa5d.

* Revert "build(meson): automatically use poll or select as needed (#2067)"

This reverts commit 2b5d1eea8d.

* Revert "Make poll() the default (#2065)"

This reverts commit 6e73a63153.

* Remove select() and use poll()
2025-02-20 18:51:35 -05:00
Florian Albrechtskirchinger b944f942ee Correct default thread pool size in README.md (#2077) 2025-02-20 12:59:38 -05:00
Florian Albrechtskirchinger 550f728165 Refactor streams: rename is_* to wait_* for clarity (#2069)
- Replace is_readable() with wait_readable() and is_writable() with
  wait_writable() in the Stream interface.
- Implement a new is_readable() function with semantics that more
  closely reflect its name. It returns immediately whether data is
  available for reading, without waiting.
- Update call sites of is_writable(), removing redundant checks.
2025-02-20 12:56:39 -05:00
yhirose a4b2c61a65 Max timeout test refactoring (#2071)
* Simplify code

* Adjust threshold
2025-02-19 22:19:02 -05:00
Florian Albrechtskirchinger 5c0135fa5d Fix typo in meson.build (#2070) 2025-02-19 16:20:44 -05:00
Andrea Pappacoda 2b5d1eea8d build(meson): automatically use poll or select as needed (#2067)
Follow-up to 6e73a63153
2025-02-19 12:47:56 -05:00
yhirose d274c0abe5 Fix typo 2025-02-18 21:33:32 -05:00
yhirose dda2e007a0 Fixed documentation about Unix Domain Sockt (#2066) 2025-02-18 11:40:50 -05:00
yhirose 321a86d9f2 Add *.dSYM to Makefile clean 2025-02-18 05:56:22 -05:00
yhirose ada97046a2 Fix misspelled words 2025-02-18 05:54:22 -05:00
Florian Albrechtskirchinger 6e73a63153 Make poll() the default (#2065)
* Make poll() the default

select() can still be enabled by defining CPPHTTPLIB_USE_SELECT.

* Run tests with select() and poll()
2025-02-18 05:23:23 -05:00
Uros Gaber cdc223019a server_certificate_verifier extended to reuse built-in verifier (#2064)
* server_certificate_verifier extended to reuse built-in verifier

* code cleanup and SSLVerifierResponse enum clarification as per @falbrechtskirchinger comment

* cleanup

* clang-format

* change local var verification_status_ declaration to auto

* change local var verification_status_ to verification_status

* clang-format

* clang-format

---------

Co-authored-by: UrosG <uros@ub330.net>
2025-02-17 17:24:41 -05:00
Florian Albrechtskirchinger 574f5ce93e Add style check to workflow (#2062)
* Add style check to workflow

* Add example files to style check
2025-02-17 12:14:53 -05:00
Florian Albrechtskirchinger 2996cecee0 Fix code inconsistently formatted and re-format (#2063)
* Fix code inconsistently formatted by clang-format

* Run clang-format
2025-02-17 12:14:02 -05:00
Florian Albrechtskirchinger 32bf5c9c09 Simplify SSL shutdown (#2059) 2025-02-16 17:38:41 -05:00
Florian Albrechtskirchinger 735e5930eb Detect additional CMake build failures (#2058)
Add include_httplib.cc to the main test executable (already done in
Makefile), and add include_windows_h.cc to the main test executable on
Windows to test if including windows.h conflicts with httplib.h.
2025-02-16 15:45:28 -05:00
Florian Albrechtskirchinger 748f47b377 Add workflow_dispatch with Google Test filter and OS selection (#2056)
* Add workflow_dispatch with Google Test filter

Add the workflow_dispatch trigger to the test.yaml workflow. Includes an
input for an optional Google Test filter pattern.

* Add OS selection to workflow_dispatch

* Fix wording
2025-02-16 12:34:28 -05:00
Florian Albrechtskirchinger 4cb8ff9f90 Print timeout exceedance in MaxTimeoutTest (#2060) 2025-02-16 08:43:54 -05:00
Florian Albrechtskirchinger 985cd9f6a2 Fix compilation failures with include <windows.h> (#2057) 2025-02-16 08:39:29 -05:00
Florian Albrechtskirchinger 233f0fb1b8 Refactor setting socket options (#2053)
Add detail::set_socket_opt() and detail::set_socket_opt_time() to avoid
repetition of platform-specific code.
2025-02-14 22:40:24 -05:00
yhirose 03cf43ebaa Release v0.19.0 2025-02-14 14:42:29 -05:00
yhirose 3c4b96024f Don't run CI twice (on push AND pull request) 2025-02-14 14:19:54 -05:00
yhirose d74e4a7c9c Removed incomplete API compatibility check scripts. 2025-02-14 14:10:06 -05:00
Andrea Pappacoda bfa2f735f2 ci: add abidiff workflow (#2054)
This CI workflow checks ABI compatibility between the pushed commit and
the latest tagged release, helping preventing accidental ABI breaks.

Helps with https://github.com/yhirose/cpp-httplib/issues/2043
2025-02-14 14:06:35 -05:00
yhirose b6ab8435d7 Improve ABI check tool on macOS 2025-02-12 12:49:20 -05:00
yhirose 39a64fb4e7 Fix ABI compatibility tool on macOS 2025-02-11 18:40:39 -05:00
yhirose d7c14b6f3a Add API compatibility check tool 2025-02-11 17:49:33 -05:00
yhirose 1880693aef Dropped Visual Studio 2015 support 2025-02-11 11:22:46 -05:00
Florian Albrechtskirchinger dd20342825 Don't run CI twice (on push AND pull request) (#2049) 2025-02-11 06:55:13 -05:00
Brett Profitt a268d65c4f Fix check for URI length to prevent incorrect HTTP 414 errors (#2046) 2025-02-10 21:46:38 -05:00
Florian Albrechtskirchinger b397c768e4 Unify select_read() and select_write() (#2047) 2025-02-10 18:15:19 -05:00
yhirose 8e22a7676a Remome 'global timeout' to 'max timeout' 2025-02-10 18:07:30 -05:00
yhirose 8a7c536ad5 Fix #2034 (#2048)
* Fix #2034

* Fix build error

* Adjust threshold

* Add temporary debug prints

* Adjust threshhold

* Another threshold adjustment for macOS on GitHub Actions CI...

* Performance improvement by avoiding unnecessary chrono access

* More performance improvement to avoid unnecessary chrono access
2025-02-10 06:51:07 -05:00
yhirose 8aad481c69 Fix test.yaml problem 2025-02-08 23:37:41 -05:00
yhirose 5814e121df Release v0.18.7 2025-02-08 15:53:35 -05:00
Florian Albrechtskirchinger 7adbccbaf7 Refine when content is expected (#2044)
Consider Content-Length and Transfer-Encoding headers when determining
whether to expect content. Don't handle the HTTP/2 connection preface
pseudo-method PRI.

Fixes #2028.
2025-02-08 15:51:52 -05:00
yhirose eb10c22db1 Add unit test for #609 2025-02-08 10:17:09 -05:00
yhirose 708f860e3a Fix #2042 2025-02-06 05:56:31 -05:00
yhirose eb30f15363 Release v0.18.6 2025-02-05 19:14:20 -05:00
yhirose 4941d5b56b Fix #2033 (#2039) 2025-02-05 12:46:33 -05:00
Florian Albrechtskirchinger 9bbb4741b4 Run clang-format (#2037) 2025-02-02 22:32:33 -05:00
yhirose 282f2feb77 Add a unit test 2025-02-01 22:11:15 -05:00
alex-cornford 60a1f00618 Support building httplib.h on OpenVMS x86 systems (#2031)
Modify for OpenVMS x86 C++. Make tests on OpenVMS currently not supported due to no cmake support.
Changes tested on OpenVMS clang C++ and Fedora & GCC
2025-01-28 18:44:22 -05:00
yhirose 9104054ca5 Fix README example 2025-01-27 13:37:16 -05:00
Baiyies d69f144a99 Update httplib.h (#2030)
fix 'max'
2025-01-26 08:50:10 -05:00
yhirose 929dfbd348 Update copyright year 2025-01-20 00:32:10 -05:00
yhirose 3047183fd9 Update README 2025-01-20 00:02:02 -05:00
yhirose ef5e4044f1 Update README 2025-01-19 23:46:12 -05:00
yhirose 3779800322 Release v0.18.5 2025-01-17 17:38:03 -05:00
yhirose 986a20fb7d Resolve #2017 (#2022)
* Resolve #2017

* Fix warning

* Update README
2025-01-17 17:37:07 -05:00
yhirose 8311e1105f Fix Windows build problem 2025-01-16 23:26:04 -05:00
yhirose ba6845925d Fix #2014 2025-01-16 23:10:58 -05:00
yhirose 343a0fc073 Fix #2011 2025-01-16 21:38:45 -05:00
yhirose 54f8a4d0f3 Release v0.18.4 2025-01-16 01:00:25 -05:00
yhirose 9c36aae4b7 Fix HTTP Response Splitting Vulnerability 2025-01-16 00:04:33 -05:00
yhirose b766025a83 clangformat 2025-01-16 00:03:10 -05:00
yhirose 9b5f76f833 Fix #2012 2024-12-27 17:19:23 -05:00
sinnren d647f484a4 fix:set_file_content with range request return 416. (#2010)
Co-authored-by: fenlog <bakurise@qq.com>
2024-12-24 09:38:59 -05:00
Sergey Bobrenok 8794792baa Treat out-of-range last_pos as the end of the content (#2009)
RFC-9110 '14.1.2. Byte Ranges':
A client can limit the number of bytes requested without knowing the
size of the selected representation. If the last-pos value is absent,
or if the value is greater than or equal to the current length of the
representation data, the byte range is interpreted as the remainder of
the representation (i.e., the server replaces the value of last-pos
with a value that is one less than the current length of the selected
representation).

https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6
2024-12-23 13:14:36 -05:00
yhirose b85768c1f3 Fix #2005 2024-12-16 17:43:50 -05:00
yhirose e6d71bd702 Add a unit test for Issue #2004 2024-12-12 18:15:22 -05:00
yhirose 258992a160 Changed to use non-blocking socket in is_ssl_peer_could_be_closed 2024-12-03 19:26:08 -05:00
yhirose a7bc00e330 Release v0.18.3 2024-12-03 06:33:00 -05:00
yhirose 11a40584e9 Fix #1998 2024-12-03 00:38:20 -05:00
yhirose 3e86bdb4d8 Fix #1997 (#2001) 2024-12-03 00:11:29 -05:00
Pavel P c817d65695 Fix casting uint64_t to size_t for 32-bit builds (#1999) 2024-12-02 11:09:52 -05:00
yhirose 51dee793fe Release v0.18.2 2024-11-29 20:49:50 -05:00
yhirose 457fc4306e Fix #1993 2024-11-29 20:46:48 -05:00
yhirose 4f5b003e76 Fix #1992 2024-11-28 20:40:38 -05:00
yhirose 5421e27106 Fix a compiler warning 2024-11-28 20:39:26 -05:00
yhirose fe07660f40 Fix #1986 (#1988) 2024-11-27 12:18:35 -05:00
yhirose da2f9e476e Fix #1985 (#1989) 2024-11-27 12:18:23 -05:00
sebastianas 1a7a7ed1c3 test: Don't check for the exact size of compressed content. (#1984)
The testsuite checks for the exact size of the compressed content. The
exact size can change if the zlib library is using a different strategy.
In thise case using zlib-ng results in a slightly larger content leading
to a failure in the test.

Check that the compressed content is less than 10MiB which is a tenth of
the orignal content and proves that compression works.

Signed-off-by: Sebastian Andrzej Siewior <sebastian@breakpoint.cc>
2024-11-25 15:46:41 -05:00
yhirose 413994912d Update vcxproj files 2024-11-16 11:14:13 -05:00
yhirose 01dcf1d0ad Fix #1969 (without unnecessary sleep_for) (#1982) 2024-11-16 10:56:57 -05:00
yhirose 8e378779c2 Update README 2024-11-16 09:45:04 -05:00
yhirose 970b52897c Fix #1980
Fix #1980
2024-11-16 02:09:52 -05:00
yhirose 412ba04d19 Fix problem caused by #1975 2024-11-14 20:33:08 -05:00
yhirose bfef4b3e9b Fix #1975 2024-11-14 17:27:28 -05:00
yhirose 7bd316f3d0 Fix #1977 2024-11-14 16:46:27 -05:00
yhirose 26208363ee Fix warning 2024-11-14 16:46:09 -05:00
yhirose b1b4bb8850 clangformat 2024-11-13 22:50:03 -05:00
yhirose 9dd565b6e3 Resolve #1973 (#1976)
* Fix #1973

* Fixed problems with 'Language for non-Unicode programs' setting on Windows

* Fix problems on English locale
2024-11-13 22:47:09 -05:00
yhirose 924f214303 Added unit test for exception handler 2024-11-02 07:23:44 -04:00
yhirose 5c1a34e766 Release v0.18.1 2024-10-18 17:16:54 -04:00
yhirose fa90d06dd5 Merge branch 'master' of github.com:yhirose/cpp-httplib 2024-10-18 17:16:14 -04:00
Jiwoo Park d869054318 Allow empty header values (#1965) 2024-10-18 10:16:48 -04:00
Peter Boström 0cc1ca9a8d Remove extra semicolon (#1963)
This fixes a -Wc++98-compat-extra-semi instance.
2024-10-15 00:09:04 -04:00
yhirose 3701195033 Merge branch 'master' of github.com:yhirose/cpp-httplib 2024-10-14 21:32:28 -04:00
Peter Boström f884a56258 Remove space between operator"" and _t (#1962)
This should fix a -Wdeprecated-literal-operator instance since this is
deprecated as a result of CWG2521 (iiuc C++23).
2024-10-14 21:32:13 -04:00
yhirose d79633ff52 clangformat 2024-10-11 14:49:46 -04:00
yhirose e0ebc431dc Fix #1959 2024-10-11 13:43:01 -04:00
Andrew McDaniel 131bc6c674 Add documentation for using Unix domain sockets. (#1954)
* Add documentation for using unix domain sockets.

* Formatting
2024-10-03 10:53:25 -04:00
yhirose 10d68cff50 Added a unit test for #1946 2024-09-26 22:24:44 -04:00
zjyhjqs 996acc5253 Feat: add CPack support (#1950) 2024-09-25 00:19:41 -04:00
yhirose 7c4799d0cf Fix #1798 for CMake (#1944) 2024-09-19 18:33:32 -04:00
yhirose c239087332 Fix Mafile errors 2024-09-17 18:37:44 -04:00
Andrea Pappacoda 7018e9263d test(meson): copy files in www directory (#1941)
These files were added in commits
2d01e71286 and
b8315278cb
2024-09-17 18:06:01 -04:00
yhirose 4990b4b4b7 Fix problems with SSLSlientServerTest.* tests 2024-09-17 17:00:17 -04:00
Andrea Pappacoda 5064373c23 test(meson): fix SSLClientServerTest.* tests with OpenSSL 3.2.0 (#1940)
* build(meson): bump minimum version to 0.62.0

This allows making some minor cleanups

* test(meson): fix SSLClientServerTest.* tests with OpenSSL 3.2.0

Since OpenSSL commit
<https://github.com/openssl/openssl/commit/342e3652c791bdb06e08abcc169b4456c83ccd00>,
the default X.509 certificate format generated with the `openssl req`
command has been changed to X.509 v3 from X.509 v1.

For some reason, this change breaks cpp-httplib's SSLClientServerTest.*
tests.

To fix the test failures, this patch passes the '-x509v1' flag instead
of '-x509' when OpenSSL 3.2.0 or newer is detected. To detect the
version of a command line utility, Meson 0.62.0 or later is required.

Fixes <https://github.com/yhirose/cpp-httplib/issues/1798>, but only for
the Meson build system.
2024-09-17 16:58:09 -04:00
yhirose 6c93aea59a Revert "Enabled HostnameToIPConversionTest.YouTube_Online partially"
This reverts commit 6553cdedab.
2024-09-15 01:18:20 -04:00
yhirose 6553cdedab Enabled HostnameToIPConversionTest.YouTube_Online partially 2024-09-13 20:45:59 -04:00
yhirose a61b2427b0 Update benchmark base vertion to 0.18.0 2024-09-13 20:34:30 -04:00
yhirose af4ece3d5f Update benchmark/Makefile 2024-09-12 20:41:56 -04:00
yhirose e64379c3d7 Release v0.18.0 2024-09-12 12:28:35 -04:00
yhirose 5053912534 Updated actions/upload-artifact from v1 to v4 2024-09-12 12:04:25 -04:00
Jean-Francois Simoneau 932b1cbc32 Fix shadow parameter warning (#1936) 2024-09-12 12:02:25 -04:00
yhirose de36ea7755 Fix #1933 on Linux and macOS 2024-09-09 23:07:27 -04:00
yhirose 9f8db2c230 Fix #1933 2024-09-09 22:22:56 -04:00
yhirose 3f00e1b321 Revert "Changed set_file_content to accept only a regular file path."
This reverts commit 7ab9c119ef.
2024-09-09 20:03:47 -04:00
yhirose 7ab9c119ef Changed set_file_content to accept only a regular file path. 2024-09-09 19:59:18 -04:00
yhirose 3f2922b3fa Fix #1929 2024-09-09 19:25:52 -04:00
yhirose 509f583dca Fix problem caused by #1931. 2024-09-09 19:19:52 -04:00
Paul Harris 2d01e71286 Test reading empty zero-length file (#1931) 2024-09-09 17:54:11 -04:00
yhirose e612154694 Issue1431 (#1926)
* Renamed enable_server_host_verification to enable_server_hostname_verification and added Error::SSLServerHostnameVerification

* Add some Open SSL function calls

* Code cleanup

* Fix #1431
2024-09-08 22:45:47 -04:00
yhirose 82fcbe3901 Code cleanup 2024-09-08 20:10:35 -04:00
yhirose dbd2465b56 Add some Open SSL function calls 2024-09-08 20:10:17 -04:00
yhirose ea79494b29 Renamed enable_server_host_verification to enable_server_hostname_verification and added Error::SSLServerHostnameVerification 2024-09-08 20:08:52 -04:00
yhirose f35aff84c2 Fixed FuzzableServer build error 2024-09-08 19:00:23 -04:00
yhirose 7b18ae6f16 Update benchmark 2024-09-08 10:56:13 -04:00
yhirose a79c56d06b Fix #1796 2024-09-08 09:26:19 -04:00
yhirose 3d6e315a4c Fix #1923 2024-09-08 08:38:36 -04:00
yhirose 4c27f9c6ef Made default server and client read/write timeout settings separately 2024-09-07 21:06:23 -04:00
yhirose d173a37d17 Increased CPPHTTPLIB_READ_TIMEOUT_SECOND to 300 from 5 2024-09-07 16:10:54 -04:00
yhirose 7fd346a2ca Fix #1379 2024-09-07 16:07:45 -04:00
yhirose c673d502b9 Update server_and_client.cc 2024-09-07 11:11:57 -04:00
yhirose c43c51362a Add monitor tool 2024-09-07 10:16:15 -04:00
yhirose 3e86d93d13 clangformat 2024-09-07 10:16:03 -04:00
yhirose f6e4e2d0f3 Code cleanup 2024-09-07 10:15:22 -04:00
yhirose 01a52aa8bd Add example/server_and_client.cc 2024-09-07 10:05:53 -04:00
yhirose 8415bf0823 Resolve #1906 2024-09-07 00:05:41 -04:00
orbea 327ff263f5 httplib.h: support LibreSSL (#1922) 2024-09-06 22:19:53 -04:00
yhirose 61c418048d Release v0.17.3 2024-09-06 19:58:02 -04:00
yhirose 9720ef8c34 Code cleanup 2024-09-06 19:48:25 -04:00
yhirose 978a4f6345 Fix KeepAliveTest.SSLClientReconnectionPost problem (#1921) 2024-09-06 13:58:24 -04:00
bgs99 80fb03628b Only match path params that span full path segment (#1919)
* Only match path params that span full path segment

* Fix C++11 build
2024-09-06 08:48:51 -04:00
laowai9189 2480c0342c ‘constexpr’ error (#1918)
httplib.h: In member function ‘constexpr size_t httplib::detail::case_ignore_hash::operator()(const string&) const’:
httplib.h:359:30: error: call to non-‘constexpr’ function ‘const _CharT* std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::data() const [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’
  359 |     return hash_core(key.data(), key.size(), 0);
2024-09-06 07:23:29 -04:00
bgs99 eb6f610a45 Fix find_package for curl (#1920) 2024-09-06 07:22:03 -04:00
yhirose cb74e4191b Performance imporovement for Keep-Alive 2024-09-06 00:03:43 -04:00
yhirose dfa641ca41 Misc 2024-09-05 22:54:48 -04:00
yhirose 969a9f99d5 Adjust sleep 2024-09-05 22:54:28 -04:00
yhirose c099b42ba3 Removed write_format 2024-09-05 22:17:56 -04:00
yhirose b8315278cb Add a missing file 2024-09-05 19:35:43 -04:00
yhirose 485f8f2411 Added one more case to MountTest.Redicect unit test. 2024-09-05 17:49:12 -04:00
yhirose 953e4f3841 Adjust sleep duration 2024-09-05 17:45:09 -04:00
yhirose adf65cfe61 Target C++11 for benchmark 2024-09-05 17:44:51 -04:00
yhirose 12c829f6d3 Fix #1389 and #1907 2024-09-05 17:44:32 -04:00
yhirose 913314f1b1 Fix warning 2024-09-05 17:43:51 -04:00
yhirose ef63f97afe Release v0.17.2 2024-09-05 12:57:26 -04:00
yhirose bda74db01d Fix fuzzing test error 2024-09-05 12:50:05 -04:00
yhirose 9ff3ff9446 Fixed build error 2024-09-05 12:27:50 -04:00
yhirose c75d071615 Add benchmark tool 2024-09-05 12:22:46 -04:00
yhirose b4989130da Peformance improvement by removing tolower function call 2024-09-05 12:09:38 -04:00
yhirose 4fc0303bda clangformat 2024-09-05 12:07:35 -04:00
yhirose 3d9cc51851 Fixed build error on Windows due to max macro in windows.h 2024-09-05 12:02:40 -04:00
Andrea Pappacoda f69587656f build(meson): add libcurl test dependency (#1914)
Prompted by PR #1911
2024-09-04 18:05:03 -04:00
yhirose d5fc340c30 Update README 2024-09-04 12:23:48 -04:00
yhirose d79a547dc9 Merge branch 'solarispika-fix-100-continue' 2024-09-04 10:17:01 -04:00
yhirose bd1da4346a Disable Expect100ContinueTest test on Windows 2024-09-04 09:51:36 -04:00
yhirose 4c2a608a0c Fix GitHub Actions errors 2024-09-04 09:06:27 -04:00
yhirose ee4eb8deaa Merge branch 'fix-100-continue' of github.com:solarispika/cpp-httplib into solarispika-fix-100-continue 2024-09-04 08:45:48 -04:00
Sung, Po Han 7196ac8a07 Fix incorrect handling of Expect: 100-continue
Fix #1808
2024-09-04 17:50:42 +08:00
yhirose c88b09bc6b Release v0.17.1 2024-09-03 21:20:57 -04:00
yhirose 87fab847b8 Fix SIGINT problem in Docker image 2024-09-03 21:20:10 -04:00
yhirose 4e6055f084 Fix problem with Abstract Namespace Unix Domain 2024-09-03 20:56:16 -04:00
yhirose 975cf0dae5 Fix #1908 2024-09-03 18:00:12 -04:00
yhirose 4854a694cd Use IPPROTO_IP 2024-09-03 17:29:28 -04:00
yhirose b1f8e986bf Fix #1908 (#1910)
* Fix #1908

* Code format
2024-09-03 00:47:39 -04:00
yhirose c5ee208775 Fix build error on Mac and Linux 2024-09-02 23:04:38 -04:00
yhirose ddfdacfa49 Fix build error 2024-09-02 22:49:31 -04:00
yhirose 2514ebc20f Fix #1848 2024-09-02 20:38:01 -04:00
yhirose 4f9c6540b2 Fixed warning 2024-09-02 20:33:33 -04:00
mol123 21c9a6a1ff Windows: simplify conditional compilation and fix call to CreateFileMappingW. (#1909) 2024-09-02 18:01:05 -04:00
yhirose 7f6d413ddd Release v0.17.0 2024-09-01 07:53:56 -04:00
yhirose 88277139e7 Added set_ipv6_v6only method (#1905)
* Added `set_ipv6_v6only` method

* Adjust the place where socket_options is called
2024-09-01 07:52:24 -04:00
yhirose 6cdd3493a1 Fix #1788 2024-09-01 01:55:27 -04:00
yhirose 9c91b6f4a6 Fix #1645 2024-09-01 00:11:07 -04:00
yhirose cee838e335 Documentation 2024-08-31 17:42:43 -04:00
yhirose d82c82db2c Add sleep in handle_EINTR 2024-08-31 17:19:52 -04:00
yhirose ba638ff38e Update Docker support 2024-08-31 17:09:20 -04:00
yhirose da0c6579fa Breaking Change! get_header_ methods on Request and Response now take a default value. 2024-08-31 17:07:48 -04:00
yhirose 52a18c78a5 Add docker related files 2024-08-27 00:23:31 -04:00
yhirose 048edec9ed Changed CPPHTTPLIB_KEEPALIVE_MAX_COUNT to 100 2024-08-26 21:10:38 -04:00
yhirose af56b7ec0b Release v0.16.3 2024-08-17 09:53:26 -04:00
Jiwoo Park 6c3e8482f7 Fix KeepAliveTest.SSLClientReconnectionPost (#1895) 2024-08-10 07:19:59 -04:00
yhirose 390f2c41f6 Fix #1878 (#1893)
* Fix #1878
2024-08-08 22:07:46 -04:00
yhirose aa04feebb4 Fix warnings 2024-08-08 20:54:33 -04:00
yhirose 45f3694f82 Fix problem with clean command in Makefile 2024-08-08 19:30:46 -04:00
yhirose c5c54b31e2 Release v0.16.2 2024-08-08 11:48:50 -04:00
Mark Mentovai 69c84c9597 BoringSSL compatibility fixes (#1892)
This patch is necessary to build cpp-httplib in Crashpad, itself in
Chromium, using BoringSSL. Details at [1].

The fixes include:
 - Library version check: tolerate BoringSSL as an alternative to
   OpenSSL 3.
 - Don’t call `OPENSSL_thread_stop`, which is not in BoringSSL.
 - Use `SSL_get_peer_certificate` (deprecated in OpenSSL 3), the old
   name for `SSL_get1_peer_certificate`, because the new name is not in
   BoringSSL.
 - Call `SSL_set_tlsext_host_name` directly instead of making an
   `SSL_ctrl` call that BoringSSL does not support. The feared
   -Wold-style-cast warning that occurs when buidling with OpenSSL is
   not triggered in BoringSSL.

[1] https://chromium.googlesource.com/crashpad/crashpad/+/1a62a0182557c89494676c06611f1ca731bcb2db
2024-08-08 11:47:56 -04:00
yhirose ae63b89cbf Use SOCK_CLOEXEC instead of __linux__ 2024-08-06 17:31:55 -04:00
yhirose ff038f98b7 Merge branch 'thread-safe-cloexec' of github.com:kdombroski/cpp-httplib into kdombroski-thread-safe-cloexec 2024-08-06 17:22:43 -04:00
yhirose e00fd06355 Release v0.16.1 2024-08-06 17:04:22 -04:00
yhirose 521529d24d Fix #1481 (with content provider) (#1527)
* Fix #1481 (with content provider)

* Improve shutdown performance

* Make shutdown action more stable

* Move some tests up

* Simplified

* Simplified
2024-08-06 13:43:00 -04:00
yhirose ed0719f2bc Code format 2024-08-06 07:20:05 -04:00
hanslivingstone 6a848b1a16 Require a minimum of TLS 1.2 (#1889)
TLS 1. is deprecated: https://www.ietf.org/rfc/rfc8996.html
2024-07-30 17:18:33 -04:00
mol123 c8bcaf8a91 Fix build when targeting Windows 7 as platform. (#1869)
* Fix build when targeting Windows 7 as platform.

This change makes more of the code introduced in
https://github.com/yhirose/cpp-httplib/pull/1775
conditional on feature macros.

`CreateFile2`, `CreateFileMappingFromApp` and `MapViewOfFileFromApp` are
available only starting from Windows 8.

 * https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2
 * https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingfromapp
 * https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffilefromapp

* Update feature macros used and use `GetFileSizeEx` conditionally.
2024-07-02 10:58:40 -04:00
Hlado 8cd0ed0509 Added move assignment operator to Client class. (#1873) 2024-06-30 11:17:00 -04:00
Hlado 177d8420a1 Added .gitattributes file to prevent git from changing line endings (#1872)
of text files using as data for tests.
2024-06-30 11:16:48 -04:00
Daniel Ludwig 388a8c007c Fix build on Windows with no WINAPI_PARTITION_APP support (#1865) 2024-06-24 15:13:37 -04:00
Andrea Pappacoda bdefdce1ae test: fix GetRangeWithMaxLongLength on 32 bit machines (#1867)
The test used the hardcoded long value for 64 bit machines even on 32
bit ones, leading to test failures. With this patch the max long length
is obtained using std::numeric_limits<long>::max(). Thanks to q2a3z for
the hint!

Fixes: https://github.com/yhirose/cpp-httplib/issues/1795
2024-06-23 17:49:00 -04:00
Zhenlin Huang 9e4f93d87e Allow hex for ipv6 literal addr in redirect (#1859)
Co-authored-by: jaredhuang <jaredhuang@tencent.com>
2024-06-17 11:44:51 -04:00
yhirose 0b657d28cf Added example/one_time_request.cc. 2024-06-14 18:29:34 -04:00
Rainer Schielke c1a09daf15 avoid memory leaks if linked with static openssl libs (#1857)
* New function SSLServer::update_certs. Allows to update certificates while server is running

* New function SSLServer::update_certs. Added unit test

* avoid memory leaks if linked with static openssl libs

---------

Co-authored-by: CEU\schielke <Rainer.Schielke@heidelberg.com>
2024-06-14 15:40:03 -04:00
yhirose 8438df4a95 Release v0.16.0 2024-06-11 18:20:47 -04:00
Jiwoo Park 67fd7e3d09 Change library name to cpp-httplib (#1854) 2024-06-10 13:18:19 -04:00
Rainer Schielke d44031615d New function SSLServer::update_certs. Allows to update certificates while server is running (#1827)
* New function SSLServer::update_certs. Allows to update certificates while server is running

* New function SSLServer::update_certs. Added unit test

---------

Co-authored-by: CEU\schielke <Rainer.Schielke@heidelberg.com>
2024-06-03 09:37:40 -04:00
Sean Quinn 98cc1ec344 Allow hex for ip6 literal addr, fix #1800 (#1830)
* Allow hex for ip6 literal addr, fix #1800

* Add UT for ipv6 + Universal client implementation

* add /n at EOF
2024-05-26 08:57:07 -04:00
Vladimír Chlup 25b1e0d906 Tweak CI & fix macOS prefix (#1843)
* Use brew prefix or given one

* Polish CI workflow file
2024-05-26 08:24:29 -04:00
Pavel P 05f9f83240 Avoid unreferenced formal parameter warning in get_range_offset_and_length (#1838)
Release builds result in the following warning because `content_length` param was used only inside asserts:

1> cpp-httplib\httplib.h(4933,45): warning C4100: 'content_length': unreferenced formal parameter
2024-05-17 09:56:06 +09:00
Karen Dombroski fb739dbaec threadsafe accept on windows, linux
* Windows has WSAAccept() which will create sockets inheriting flags from
  the server socket

* Linux has accept4() which has a flags argument supporting SOCK_CLOEXEC
2024-05-01 21:58:58 +12:00
Karen Dombroski 50fce538c6 threadsafe CLOEXEC on platforms that support it
SOCK_CLOEXEC is a flag available on some platforms to enable creation of
sockets with CLOEXEC already set
2024-05-01 21:46:50 +12:00
Jiwoo Park 3b6597bba9 Fix query parsing when value has = characters (#1822)
* Implement string divider to replace splitter

* Divide query string in half

* Add a test case for query values containing the '=' character

* Add test cases for string divider

* Fix warnings
2024-04-21 19:17:14 -04:00
KTGH f10720ed69 Move httplibConf.cmake.in & install readme/license (#1826)
* Move httplibConfig.cmake.in to cmake dir

Just makes more sense to put it there I suppose.

* Cmake install README & License

Seems to make sense since you might already do this as a package
manager, or an end user might want them anyways.

The locations are just based on standard Linux locations using
GNUInstallDirs, so it should be sane on other machines too.
2024-04-21 19:14:12 -04:00
rndm13 2bc550b2f0 Added progress to POST, PUT, PATCH and DELETE requests (#1821)
* Added progress to POST, PUT, PATCH, DELETE requests

* Added tests for post, put, patch, delete progress/cancellation

* fix accidental infinite recursion in delete

---------

Co-authored-by: rndm <rndm@localhost.localdomain>
2024-04-21 19:13:41 -04:00
Jiwoo Park ce36b8a6e5 Highlight notes using markdown features (#1820) 2024-04-18 10:19:54 -04:00
Kent 560854a961 Apply range header base on response status code (#1806)
* Enable ignoring range header to generate customized response

* Apply range header base on response status code
2024-04-11 23:28:21 -04:00
yhirose 2064462c35 Merge branch 'HerrCai0907-fix' 2024-04-11 22:30:00 -04:00
yhirose 07288888ad Code cleanup 2024-04-11 22:26:30 -04:00
yhirose 34d392cf3d Merge branch 'fix' of github.com:HerrCai0907/cpp-httplib into HerrCai0907-fix 2024-04-11 22:22:58 -04:00
yhirose 825c3fbbb1 Removed excess usage of std::move 2024-04-11 21:21:27 -04:00
Rusty Conover 00bdf73ec6 fix: increase default receive buffer to 16kb (#1814)
Since TLS packets have a maximum size of 16kb it makes
sense to fully accommodate them on reads.

Co-authored-by: Rusty Conover <rusty@Juno.local>
2024-04-09 16:59:07 -04:00
Jiwoo Park f44ab9b3da Fix range parser when parsing too many ranges (#1812)
* Implement range parser without std::regex

* Add test cases for invalid ranges
2024-04-07 10:06:16 -04:00
Andrea Pappacoda a61f2b89be build(meson): generate new test PEMs (#1813)
Follow-up to commits 548dfff0ae and
b8bafbc291
2024-04-07 10:05:07 -04:00
Jiwoo Park b8bafbc291 Generate missing PEMs for CTest (#1811)
* Generate missing PEMs

* Fix typo

* Copy files using simpler command
2024-04-05 13:50:21 -04:00
yhirose 548dfff0ae Fix #1793 2024-03-09 22:26:17 -05:00
Congcong Cai 4dd2f3d03d fix ambiguous for HandlerWithResponse and Handler in set_error_handler
using lambda expression as Handler in set_error_handler will cause ambiguous.
Template forwarding can forward HandlerWithResponse to the correct overloading function
2024-03-07 19:07:39 +08:00
KTGH 6791a8364d FindBrotli cleanup & fixes (#1786)
* Unimportant cleanup of FindBrotli

Some whitespace, removal of an unused var, and add a comment about
the missing version support

* Fix incorrect var in FindBrotli

Not much to say, it was just the wrong one. Not sure how that happened,
maybe a holdover from when I did an overhaul at some point..

* Tiny useless edit to FindBrotli

Just a little tweak to how I was postfixing those -static strings to the
lib names. Mostly pointless..

* Simplify some FindBrotli code

Nothing much, just reducing redundant stuff & a cleanup of a comment.

* Ignore PkgConf in FindBrotli if looking for static

As per the issue mentioned in this comment, static PkgConf support
is broken upstream. After testing, I found it'll just accept shared
even if you try to find static, so I'm merely disabling that feature
for this FindBrotli module.

That said, you can still get the static libs if you have them installed,
but PkgConf will be ignored even if found in favor of a regular
find_library call. Otherwise you'd end up with shared libs even if you
ask for static (with PkgConf installed).

TLDR: actually fail correctly when static libs aren't found when PkgConf
thought they were.
2024-02-27 19:25:02 -05:00
KTGH d1a1c8a158 Add description & URL to Cmake (#1785)
Doesn't do much, but some packages/builders might find a use for these
vars it provides...
2024-02-27 19:22:44 -05:00
yhirose b4d26badf2 Update github actions 2024-02-17 09:17:51 -05:00
Jiwoo Park c5a0673c93 Use final keyword for devirtualization (#1779) 2024-02-17 09:17:15 -05:00
Sergey ad40bd6a00 Implement file mapping for UWP apps (#1775)
Fixes #1773.
2024-02-08 22:59:34 -05:00
yhirose 5c00bbf36b Release v0.15.3 2024-02-05 22:12:43 -05:00
yhirose 9d6f5372a3 Fix #1772 2024-02-05 22:11:53 -05:00
Ikko Eltociear Ashimine f06fd934f6 Fix typo in gtest-all.cc (#1770)
synthetic -> synthetic
2024-02-05 15:35:33 -05:00
yhirose 80c0cc445e Release v0.15.2 2024-02-02 23:29:30 -05:00
yhirose 762024b890 Fix #1768 2024-02-02 23:17:32 -05:00
yhirose 82a90a2325 Update year 2024-01-29 08:53:01 -05:00
yhirose b7cac4f4b8 Release v0.15.1 2024-01-29 07:40:56 -05:00
yhirose e323374d2a Fix #1766 2024-01-28 17:43:51 -05:00
Jiwoo Park ffc294d37e Reduce object copy (#1767) 2024-01-28 08:18:29 -05:00
yhirose fceada9ef4 Changed to return 416 for a request with an invalid range 2024-01-28 08:13:19 -05:00
yhirose 5f0f73fad9 Reduce duplicate computation for ranges 2024-01-27 19:07:52 -05:00
yhirose 530d6ee098 Release v0.15.0 2024-01-27 17:39:58 -05:00
yhirose 420c9759c6 Fix #1694 2024-01-27 16:13:54 -05:00
yhirose 2ce7c22218 Fix #1747 2024-01-27 12:56:39 -05:00
Wander Nauta 4ef9ed80cd Treat paths with embedded NUL bytes as invalid (#1765)
Fixes #1763.
2024-01-27 08:22:00 -05:00
Jiwoo Park 44b3fe6277 Support move semantics for Response::set_content() (#1764) 2024-01-27 07:53:19 -05:00
Ilya Andreev 449801990f Add a getter for a bearer token from a request (#1755)
* Add a getter for a bearer token from a request

* Replace a method for bearer token getter with a free function
2024-01-15 08:57:22 -05:00
Jean-Francois Simoneau af2928d316 Fix select() return code for fd >= 1024 (#1757) 2024-01-15 08:27:31 -05:00
KTGH d948e38820 Minor cmake fix & cleanup (#1754)
* Reorder cmake docs a bit

Just wanted to group the more related build options together.

Also removed a pointless reference to the old reasoning for the required
min ver since it's 3.14 now anyways.

* Fix outdated cmake comment

We don't use Git to find the version string anymore.
Just updated to match what it's actually used for now.

* Group options and build-tree vars in Cmake

Doesn't really change anything, I just wanted to clean these up a bit.

* Fix how we set HTTPLIB_IS_USING_XXX vars in Cmake

Prevents us acidentally using libs when the user didn't want them
actually used. This could happen if they set the option to OFF but their
own project itself is using the lib, thus we'd find and use it anyways.

Ref #1602 to see an example of this already happening before.
This is merely apply that kind of fix to all 3 of our deps, instead of
just OpenSSL.

* Minor formatting/comment change to Cmake

Pointless, but these things were bothering me..
2024-01-04 18:20:37 -05:00
Matthias Bilger 65218ce222 added missing include of exception (#1752) 2023-12-31 18:59:43 -05:00
Adam Gajda 55e99c4030 Fix -Wold-style-cast warning (#1751) 2023-12-31 18:43:31 -05:00
TheOnlyJoey b63d50671d Fixes Windows std::max macro problems (#1750) 2023-12-30 11:37:58 -05:00
yhirose eba980846b Fix #1628 (OpenSSL 1.1.1 End of Life on September 11, 2023) (#1745) 2023-12-24 08:20:58 -05:00
vmaffione 374d058de7 ThreadPool: optional limit for jobs queue (#1741)
For very busy servers, the internal jobs queue where accepted
sockets are enqueued can grow without limit.
This is a problem for two reasons:
 - queueing too much work causes the server to respond with huge latency,
   resulting in repetead timeouts on the clients; it is definitely
   better to reject the connection early, so that the client
   receives the backpressure signal as soon as the queue is
   becoming too large
 - the jobs list can eventually cause an out of memory condition
2023-12-24 08:20:22 -05:00
yhirose 31cdcc3c3a Update README about MSYS2 and MinGW 2023-12-23 21:37:34 -05:00
yhirose ad9f6423e2 Fix #1744 2023-12-23 11:45:08 -05:00
yhirose cbca63f091 Release v0.14.3 2023-12-21 19:55:25 -05:00
yhirose b4748a226c Fix #1738 2023-12-21 13:33:52 -05:00
Ilya Andreev 5b943d9bb8 Use StatusCode in tests and examples (#1743)
* Use StatusCode in tests and examples

* Use StatusCode in README
2023-12-20 17:28:57 -05:00
Ilya Andreev c86f69a105 Use StatusCode in httplib code (#1742) 2023-12-19 22:17:24 -05:00
Ilya Andreev d39fda0657 Add StatusCode enum (#1739)
* Add StatusCode enum

* Remove changes on RFC 9110

* Add number suffixes to StatusCode constants

* Remove docs for StatusCode constants
2023-12-19 17:57:30 -05:00
Ilya Andreev 37f8dc4382 Change some of status messages based on RFC 9110 (#1740) 2023-12-19 09:22:58 -05:00
yhirose 3a8adda381 Fix #1737 2023-12-17 22:04:36 -05:00
yhirose 8aa38aecaf Fix #1665 2023-12-17 22:03:02 -05:00
yhirose f1dec77f46 Code format 2023-12-17 22:00:33 -05:00
yhirose cddaedaff8 Fix #1736 2023-12-15 19:29:54 -05:00
Renato Foot Guimarães Costallat cefb5a8822 Update README.md (#1731)
Fix "With Progress Callback" code example
2023-12-07 23:21:59 -05:00
davidalo e426a38c3e Fix: Query parameter including query delimiter ('?') not being parsed properly (#1713)
* Fix: Query parameter including query delimiter ('?') not being parsed properly

* Add details::split function with and without m argument to allow split parameters with/without counter

* Revert changes in SplitTest.ParseQueryString
2023-12-07 14:28:41 -05:00
yhirose f14accb7b6 Release v0.14.2 2023-12-04 22:31:12 -05:00
yhirose c5c704cb3b Fix #1724 2023-12-04 21:34:55 -05:00
Jean-Francois Simoneau 115a786581 Fix readability warnings (#1722)
* Fix readability warnings

Did not fix readbility-qualified-auto, will do a separate pull request

* Revert changes where meaning is lost

* Revert some style changes
2023-11-24 09:55:04 -05:00
Jean-Francois Simoneau 5ef4cfd263 Fix bugprone warnings (#1721) 2023-11-20 22:14:00 -05:00
Jean-Francois Simoneau 03fecb2f78 Fix modernize warnings (#1720) 2023-11-20 22:10:04 -05:00
Jean-Francois Simoneau 7fc8682a0a Fix performance-noexcept-move-constructor (#1715) 2023-11-20 13:13:59 -05:00
Jiwoo Park f1431311a4 Minor fixes on test cases (#1709)
* Fix data race

* Replace sleep_for() to wait_until_ready()
2023-11-11 21:28:50 -05:00
Jiwoo Park 1d14e051a5 Remove cryptui on Windows (#1710) 2023-11-11 21:26:57 -05:00
Jiwoo Park 97ae6733ed Run fuzz test in CTest (#1707) 2023-11-09 19:35:15 -05:00
Jiwoo Park 1d6b22b5f0 Fix C6001 (#1701) 2023-10-30 07:13:40 -04:00
yhirose 1a49076b5b Removed unnecessary exception 2023-10-29 19:36:40 -04:00
Jiwoo Park d0e4cb3f07 Include missing stdint.h on fuzz test (#1700)
* Include missing stdint.h

* Remove std:: from uint8_t
2023-10-29 19:26:06 -04:00
yhirose e2813d9d4d Code cleanup. (Removed unnecessary .c_str() calls) 2023-10-23 16:43:12 -04:00
Andrea Pappacoda 20a7f088ce build(meson): copy 1MB.txt test file (#1695)
Since tests are run in the build directory, the 1MB.txt file has to be
copied there.
2023-10-20 17:58:06 -04:00
yhirose f63ba7d013 Fix #1685 2023-10-03 09:59:27 -04:00
yhirose 0a629d7391 Release v0.14.1 2023-09-30 22:26:23 -04:00
PabloMK7 a609330e4c Add optional user defined header writer (#1683)
* Add optional user defined header writer

* Fix errors and add test
2023-09-30 22:13:14 -04:00
Jiwoo Park c029597a5a Update the remote address of www.httpwatch.com (#1664) 2023-09-13 10:33:33 -04:00
yhirose 30b7732565 Release v0.14.0 2023-08-22 20:19:07 -04:00
yhirose 6650632e7f Fix #1638 2023-08-22 19:36:10 -04:00
Sven Panne afe627e7af Avoid a -Warray-bounds false positive in GCC 13. (#1639)
The exact circumstances when this false positive is triggered are quite
tricky to reproduce, but it happened reproducibly with g++ 13.1 and 13.2 in
a close-source SW I'm working on.  The fix even improves performance by a
very tiny bit: There is no need to copy the std::smatch, having a const
reference is enough.

Just as a side note: -Warray-bounds seems to cause trouble in other
projects, too, so e.g. the Linux kernel has disabled since June 2022.
2023-08-14 10:26:54 -04:00
Duncan Ogilvie 67f6ff7fa9 Fix CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR (#1634) 2023-08-03 17:01:40 -04:00
yhirose c7ed1796a7 Release v0.13.3 2023-07-31 21:28:33 -04:00
yhirose 44c62d838e Use memory mapped file for static file server (#1632)
* Use memory mapped file for static file server

* Fix build error
2023-07-31 07:43:50 -04:00
yhirose 6bb580cda8 Fix #1559 2023-07-31 00:27:26 -04:00
yhirose 00a8cb8e5d Code cleanup 2023-07-31 00:22:22 -04:00
yhirose 2e34a39673 Added StaticFileRanges test 2023-07-31 00:22:22 -04:00
yhirose 01b90829bc Removed unnecessary CRLF at the end of multipart ranges data 2023-07-31 00:22:22 -04:00
yhirose e699bd0730 Release v0.13.2 2023-07-29 12:26:19 -04:00
Akın ELDEN 961a9379d5 Fix #1590 (#1630)
* ClientImpl: Connection=close header control moved from process_request to send_

* Connection=close header control moved from send_ to handle_request

* SSLClient::connect_with_proxy error handling improved

* to_string definition added for Error::ProxyConnection

* Comment improvement

---------

Co-authored-by: akinelden <akin.elden@gmail.com>
2023-07-29 12:09:25 -04:00
yhirose ec87b04aff Fix #1619 2023-07-29 00:53:57 -04:00
yhirose aabf752a51 Fix #1519 2023-07-28 23:37:45 -04:00
yhirose afb0674ccb Fix #1624 2023-07-21 20:36:01 -04:00
bdenhollander ee625232a4 Fix successful decompress reported as Error::Read (#1612)
* Fix successful decompress reported as Error::Read

Streams less than 4096 bytes are sometimes reported as failed reads because stream_.avail_in is not reduced to 0. The next iteration of the loop finds `prev_avail_in == strm_.avail_in` and return false. `ret = inflate(...)` returns Z_STREAM_END on the first iteration of the loop indicating that inflate is finished. This fix prevents the second iteration of the loop from failing.

* Fix successful decompress reported as Error::Read

- Add unit tests for raw deflate that illustrates the decompression failure when there are extra trailing bytes
2023-07-11 18:35:27 -04:00
Andrea Pappacoda 52d8dd41f1 build(meson): use C++14 with GTest >= 1.13.0 (#1618)
GoogleTest, starting with vesion 1.13.0, requires C++14 to build. This
patch enables C++14 if GoogleTest 1.13.0 or newer is detected when
compiling the tests with Meson, making it possible to use new GTest
versions.

You can find GoogleTest's release notes at
<https://github.com/google/googletest/releases/tag/v1.13.0>.
2023-07-11 18:32:41 -04:00
Alexandre Bouvier be07d2d7a9 cmake: fix comment (#1616) 2023-07-08 13:24:36 -04:00
yhirose 0f1b62c2b3 Release v0.13.1 2023-07-08 07:41:24 -04:00
yhirose c30906a541 Code cleanup 2023-07-07 19:43:37 -04:00
bgs99 3533503323 Fix explicit constructor warning (#1614) 2023-07-07 13:17:19 -04:00
yhirose 82acdca638 Release v0.13.0 2023-07-06 18:32:42 -04:00
vmaffione a1e56a567b Result: allow default constructor (#1609) 2023-07-05 12:05:29 -04:00
yhirose 0c17d172a2 Code cleanup 2023-07-05 09:06:21 -04:00
yhirose 5d8e7c761f Updated README 2023-07-05 08:45:05 -04:00
bgs99 17fc522b75 Add named path parameters parsing (Implements #1587) (#1608)
* Add named path parameters parsing

* Select match mode based on pattern

* Add examples and comments to README

* Add documentation to matchers
2023-07-05 07:44:19 -04:00
yhirose f1daa5b88b Fix #1607 2023-07-04 20:27:11 -04:00
yhirose fe9a1949a6 Fixed an example in README 2023-06-28 11:28:08 -04:00
yhirose 50cba6db9f Fix #1603 2023-06-27 21:23:23 -04:00
Nathan Moinvaziri 18592e7f98 Ability to turn off linking against OpenSSL if already detected. (#1602)
We already detect OpenSSL in our tree for another project, but don't want to
link httplib against OpenSSL or with SSL support.

Allow us to `set(HTTPLIB_IS_USING_OPENSSL FALSE)`.
2023-06-27 20:09:42 -04:00
yhirose bd9612b81e Changed to use auto (#1594) 2023-06-17 02:18:34 -04:00
yhirose 7ab5fb65b2 Code cleanup (#1593) 2023-06-16 18:08:28 -04:00
Andre Eisenbach 8df5fedc35 Fix -Wold-style-cast warnings (#1591)
Removed multiple old-style (C) casts and replaced them with the
appropriate _cast's. This makes httplib a better citizen inside projects
that are compiled with all warnings enabled.

Unfortunately one warning remains as a result of invoking an
OpenSSL macro (at least on Linux), that would have to be fixed
upstream.

Also fixed a few casts for invocations of setsockopt.
setsockopt takes a const void* for the value pointer, not a char*.
Well, except on Windows ...

Co-authored-by: Andre Eisenbach <git@4ae.us>
2023-06-16 17:30:24 -04:00
Jiwoo Park 4a61f68fa4 Don't overwrite the last redirected location (#1589)
* Don't overwrite the last redirected location

* Check the last redirected location
2023-06-16 14:56:16 -04:00
Jiwoo Park 067890133c Use nghttp2-hosted httpbin.org (#1586)
* Use nghttp2-hosted httpbin.org

* Add CPPHTTPLIB_DEFAULT_HTTPBIN macro to choose the default httpbin.org
2023-06-15 11:12:41 -04:00
yhirose d3076f5a70 v0.12.6 2023-06-10 07:02:38 +09:00
yhirose ed129f057f Fixed C++11 warnings and code format 2023-06-09 20:49:46 +09:00
Jiwoo Park eab5ea01d7 Load in-memory CA certificates (#1579)
* Load in-memory CA certs

* Add test cases for in-memory cert loading

* Don't use the IIFE style
2023-06-09 16:34:51 +09:00
Petr Hosek 3e287b3a26 Provide a CMake option to disable C++ exceptions (#1580)
This allows disabling the use of C++ exceptions at CMake configure time.
The value is encoded in the generated httplibTargets.cmake file and will
be used by CMake projects that import it.
2023-06-06 16:56:26 +09:00
v1gnesh 4f33637b43 Add support for zOS (#1581)
Signed-off-by: v1gnesh <v1gnesh@users.noreply.github.com>
2023-06-06 14:14:06 +09:00
db-src 698a1e51ec Move, not copy, Logger and Handler functors (#1576)
* Explicitly #include <utility> for use of std::move

* Move not copy Logger arg from Client to ClientImpl

* Move not copy, set_error_handler Handler to lambda

* Remove null statement in non-empty if/else block

I guess it was a relic from a time before the other statement was added.

---------

Co-authored-by: Daniel Boles <daniel.boles@voltalis.com>
2023-06-02 15:40:00 +09:00
yhirose 27c0e1186c Release v12.0.5 2023-05-30 16:09:42 +09:00
Niccolò Iardella c54c71a3e5 Add HTTPLIB_INSTALL CMake option (#1575)
* Add HTTPLIB_INSTALL CMake option

* Proper formatting of HTTPLIB_INSTALL block

Co-authored-by: Jiwoo Park <jiwoo_90@naver.com>

---------

Co-authored-by: Niccolò Iardella <niccolo.iardella@doriansrl.it>
Co-authored-by: Jiwoo Park <jiwoo_90@naver.com>
2023-05-30 16:08:58 +09:00
yhirose 3409c00e6f Fixed warnings 2023-05-27 01:10:21 +09:00
yhirose f8ef5fab64 Release v0.12.4 2023-05-27 00:57:43 +09:00
yhirose 5b397d455d Fix more CRLF injection problems. 2023-05-22 22:56:16 +09:00
yhirose f977558a28 Release v0.12.3 2023-04-30 10:44:36 +09:00
Oleg Shparber c2e156e0e0 Fix leaked handle in create_socket (#1554)
Fixes resource leak problem detected by Coverity Scan.
2023-04-09 12:18:44 -04:00
yhirose 7aba2938d3 Fix #1548 2023-04-08 15:36:13 -04:00
yhirose d587548250 Fix #1545 2023-04-08 14:53:55 -04:00
Jiwoo Park 21f9c51556 Remove whitespace in CMake generator expression (#1552) 2023-04-07 15:40:52 -04:00
yhirose 985ceba525 Updated README 2023-04-04 07:17:59 -07:00
Sergey Kazmin e62a4b02e5 fix (#1525)
Co-authored-by: Sergey Kazmin <sergey.kazmin@kaspersky.com>
2023-04-04 07:12:15 -07:00
Octavio Valle ff34749572 Initialize sockaddr_un to fix valgrind uninitialised byte message. (#1547) 2023-04-03 08:28:01 -07:00
Jiwoo Park e5804d4a50 Don't loading system certs from Keychain on iOS (#1546) 2023-04-01 06:26:30 -07:00
Jiwoo Park 3956a2b790 Fix ServerTest.ClientStop test case (#1542) 2023-03-30 09:50:51 -04:00
Jiwoo Park b33aa52dc2 Fix lifetime issues in test using detail::scope_exit() (#1535)
* Fix lifetime issues in test using detail::scope_exit()

* Remove checking joinable threads
2023-03-28 00:01:34 -04:00
yhirose 76230db97f Simplified scope_exit 2023-03-25 21:52:39 -04:00
yhirose a66a013ed7 Release v0.12.2 2023-03-25 21:47:14 -04:00
yhirose f4b02dfdc1 Fix #1533 2023-03-25 21:13:07 -04:00
yhirose 4cf218643e Code format 2023-03-25 21:12:40 -04:00
yhirose 8f96b69a25 Update test.yaml to use microsoft/setup-msbuild@v1.1 2023-03-23 18:53:27 -04:00
Johannes Flügel d262033ded Prevent overflow in hash function str2tag_core() (#1529)
* str2tag_core(): prevent overflow

* Update httplib.h

works for all sizes of unsigned int and if there exists a #define for max
2023-03-22 14:16:32 -04:00
yhirose 5745eabe69 Updated test.yaml to use ctions/checkout@v3 2023-03-21 18:54:54 -04:00
yhirose 5f18642271 Close #1531 2023-03-20 11:47:06 -04:00
yhirose 88a9278872 Fix #1486 2023-03-11 17:04:08 -05:00
yhirose 9bb3ca8169 Fix #1459 (#1523) 2023-03-10 22:21:42 -05:00
yhirose f2f4728489 Release v0.12.1 2023-03-10 17:53:19 -05:00
yhirose d1b616286f Add note for macOS regarding system certs 2023-03-10 17:48:19 -05:00
Mathieu Gaillard df74526f91 Fix multipart Content-Type headers with both boundary and charset parameters (#1516)
* Fix multipart Content-Type headers with both boundary and charset parameters

* Improve code readability

* Add missing forward declaration

---------

Co-authored-by: Mathieu Gaillard <gaillard@adobe.com>
2023-03-08 23:57:17 -05:00
Joel Rosdahl 9f7ae0737a Fix typos (#1517) 2023-03-08 17:03:20 -05:00
yhirose 1ebb8412c5 Use SSL_ERROR_ZERO_RETURN to check if the SSL peer is closed. 2023-03-07 08:15:16 -05:00
Petr Menšík 7b69999c37 Reuse gtest on system (#1493)
* Reuse gtest on the system

Try to use gtest on the system if found. Avoid using recent CMake
features.

* Set CMP0135 only when using FetchContent

* Add /bigobj option for MSVC

* Support also cmake between 3.14 and 3.20

Older versions provided only GTest::Main target, not currently used
gtest_main. Provide backward compatibility also to old cmake versions.

* Remove redundant variable checking

---------

Co-authored-by: Jiwoo Park <jiwoo_90@naver.com>
2023-03-05 21:35:35 -05:00
yhirose c7e959a948 Fix #1481 2023-03-04 18:14:00 -05:00
yhirose ba5884e779 Fix #1481 (#1513) 2023-03-03 23:45:19 -05:00
yhirose cdaa5c48db Code cleanup 2023-03-03 22:41:57 -05:00
Alexandre Bouvier bab5c0e907 cmake: fix find_dependency (#1509) 2023-03-01 08:38:58 -05:00
Alexandre Bouvier 016838fd10 cmake: support components (#1504) 2023-02-25 09:56:56 -05:00
yhirose 75053bf855 Fix #1498 (#1501)
* Fix #1498

* Fixed build error
2023-02-22 13:19:36 -05:00
yhirose ae3a6dd2a9 Fixed an issue with example/Makefile on MacOS 2023-02-21 22:00:10 -05:00
Sergey Kazmin 6d963fbe8d Support loading system certs from Keychein on MacOS (#1474)
* Support loading system certs from Keychein on MacOS

* review improvements: add deps to meson.build and improve conditional expressions in cmake

* fix tabs

* fix tabs

* review improvements

* fix after review

* additionally load root certs from the system root keychain

* cmake fix

* fix

* small refactoring

* small refactoring

---------

Co-authored-by: Sergey Kazmin <sergey.kazmin@kaspersky.com>
2023-02-17 12:06:55 -05:00
jingTian-z 88f6245c84 feat: Add Request::get_file_multi_value func. (#1495)
Support to get multiple values of a key.

perf: Rename function names, variable names etc.
2023-02-16 21:51:06 -05:00
yhirose 0e7d2f9f93 Resolve #1482 2023-02-14 11:40:47 -05:00
yhirose 4e6ded1f36 Release v0.12.0 2023-02-07 10:27:40 -05:00
yhirose d663588491 Removed is_writable() from DataSink (Resolve #1478, too) (#1483) 2023-02-04 13:53:42 -05:00
yhirose 439caf5b79 Fix #1479 2023-02-01 19:11:11 -05:00
yhirose c4ba43ca6f Removed incorrect comment 2023-01-24 09:07:42 -05:00
Jiwoo Park 20cba2ecd9 Support CTest (#1468)
* Add test/CMakeLists.txt to enable testing with CTest

Add HTTPLIB_TEST option
Downlaod GoogleTest source using FetchContent module
Generate cert files to test httplib with OpenSSL

* Generate cert2.pem with a new certificate request

* Use the latest GoogleTest library
2023-01-21 03:43:22 -05:00
yhirose 0ff2e16d69 Issue 52666: cpp-httplib:server_fuzzer: Timeout in server_fuzzer 2023-01-21 01:09:19 -05:00
Ray Beck 51607ec752 add to_human_string (#1467)
* add to_human_string

* replace to_string with to_human_string

* fix test
2023-01-19 07:01:34 -05:00
yhirose 7992b14896 Release v0.11.4 2023-01-09 17:08:11 -05:00
Ray Beck 7e420aeed3 add support for requests with both MultipartFormDataItems and Content Providers (#1454)
* add support for requests with both MultipartFormDataItems and ContentProviders

* rework implementation

* use const auto & and fix offset calculation

* fix zero items

* snake case variables

* clang-format

* commonize get_multipart_content_provider, add Put() with MultipartFormDataProviderItems

* fix linker multiple definition error

* add test MultipartFormDataTest.DataProviderItems
2023-01-08 18:38:14 -05:00
yukun 227d2c2050 Add EINTR and EAGAIN judge for accept (#1438)
* Add EINTR and EAGAIN judge for accept

* Add EINTR signal tests

* Cancel win32 and win64 compile on signal unittest

Co-authored-by: yukun.yu <yukun.yu@alibaba-inc.com>
2022-12-20 19:34:51 -05:00
yhirose 93e53c91f7 Updated unit test 2022-12-10 11:45:56 -05:00
Jiwoo Park 58cffd3223 std::condition_variable::notify_one/all() should be called after unlocking mutex (#1448)
* Move next job in task queue rather than copy

* Notify waiting thread after unlocking mutex

* Add unit test for TaskQueue

* Don't use C++14 feature in test code
2022-12-09 17:37:48 -05:00
Ingo Bauersachs 8f32271e8c Support LOCAL_ADDR and LOCAL_PORT header in client Request (#1450)
Having the local address/port is useful if the server is bound to
all interfaces, e.g. to serve different content for developers
on localhost only.
2022-12-06 08:23:09 -05:00
yhirose c8c1c3d376 Fix #1442 2022-12-01 17:21:40 -05:00
yhirose 9f512acb42 Removed code for upsupported OpenSSL 2022-12-01 13:41:18 -05:00
yhirose c0b461a3b7 Release v0.11.3 2022-11-29 19:26:10 -05:00
yhirose 74fe5a5029 Fix #1426 2022-11-27 10:53:11 -05:00
yhirose 9d0a9d4e23 Fix #1437 2022-11-27 10:21:24 -05:00
TheMarpe 5758769ad3 Windows CMake directory install fix (#1434)
* Fixed install convention pre 3.25

* Simplified CMAKEDIR installation directory
2022-11-24 22:51:55 -05:00
Ray Beck e7eadc3605 add SYSTEM to include to prevent warnings (#1428) 2022-11-24 22:23:11 -05:00
yhirose 07c6e58951 Fix #1421 2022-11-15 11:57:14 -05:00
Pavel Artemkin 87994811a1 undef poll at the end if CPPHTTPLIB_USE_POLL (#1427)
* undef poll at the end if CPPHTTPLIB_USE_POLL

* win32 only
2022-11-13 21:49:49 -05:00
yhirose 42feb7e8be Updated README 2022-11-10 08:27:30 -05:00
yhirose 26196b70af Add test case for 'Issue 52666 in oss-fuzz: cpp-httplib:server_fuzzer: Timeout in server_fuzzer' 2022-11-03 13:07:04 -04:00
Changbin Park 93a51979c4 Get client process id over ip/port when server runs on UNIX socket. (#1418)
* handle socket options for UNIX socket same as others

 * set FD_CLOEXEC by default
 * invoke `socket_options` callback if set

* Offer Client info even on UNIX socket based Server

HTTP Request header "REMOTE_PORT" contains client process id if possible
when Server works on UNIX socket.

* retrigger checks

* retrigger checks

* add support macOS

Co-authored-by: Changbin Park <changbin.park@ahnlab.com>
2022-11-03 10:25:18 -04:00
Changbin Park ad7edc7b27 avoid lockup in ThreadPool::shutdown() on legacy host (#1417)
It is found on CentOS 7.0.1406 which is very early version of CentOS 7.

Co-authored-by: Changbin Park <changbin.park@ahnlab.com>
2022-11-03 10:23:45 -04:00
Andreas Kempf 27cd4e6ffe Support compilation on AIX (#1402)
Disable the ifaddrs.h include statement and the USE_IF2IP flag on AIX.
AIX is a Unix OS but does not offer the ifaddrs.h header.
2022-10-14 10:49:48 -04:00
yhirose cae5a8be1c Added more fuzzing corpus 2022-09-24 08:31:21 -04:00
yhirose 8e10d4e8e7 Release v0.11.2 2022-09-12 11:36:14 -04:00
Changbin Park b57f79f438 Detecting client disconnection (#1373)
* SocketStream need to check connectivity for writability.

When the stream is used for SSE server which works via chunked content
provider it's not possible to check connectivity over writability
because it's wrapped by DataSink. It could make the server stalled, when
the provider wants to only keep connection without send data and certain
amount of clients consumes entire threadpool.

* add unittest for SocketStream::is_writable()

SocketStream::is_writable() should return false if peer is disconnected.

* revise broken unittest ServerTest.ClientStop

DataSink could be unwritable if it's backed by connection based. Because
it could be disconnected by client right after enter centent provider.

Co-authored-by: Changbin Park <changbin.park@ahnlab.com>
2022-08-30 21:11:19 -04:00
Andrea Pappacoda a9cf097951 build: set soversion to major.minor (#1357)
Release 0.11 broke backwards compatibility, meaning that different
cpp-httplib versions are compatible with each other only if the major
and minor version numbers are the same.

This patch reflects this in the build systems.

See #1209 for some more context.
2022-08-12 13:48:40 -04:00
yhirose 5c3624e1af Updated example/uploader.sh 2022-08-06 14:43:56 -04:00
yhirose cba9ef8c0b Issue 49740 in oss-fuzz: cpp-httplib:server_fuzzer: Timeout in server_fuzzer 2022-08-06 08:08:08 -04:00
yhirose 4f8407a3a7 Refactoring the previous commit 2022-08-04 20:56:02 -04:00
Gopinath K 656e936f49 add multipart formdata for PUT requests. (#1351)
* httplib.h

  add multipart formdata for PUT in addition to POST as some REST
  APIs use that.

  Factor the boundary checking code into a helper and use it from
  both Post() and Put().

* test/test.cc

  add test cases for the above.
2022-08-04 20:42:13 -04:00
yhirose d92c314466 Release v0.11.1 2022-08-02 19:44:25 -04:00
yhirose b747fb111d Updated README 2022-08-02 19:42:11 -04:00
yhirose 7e0a0b2d0c Updated README 2022-08-02 19:30:15 -04:00
Changbin Park 362d064afa UNIX domain socket support (#1346)
* Add support UNIX domain socket

* `set_address_family(AF_UNIX)` is required

* add unittest for UNIX domain socket

* add support UNIX domain socket with abstract address

Abstract address of AF_UNIX begins with null(0x00) which can't be
delivered via .c_str() method.

* add unittest for UNIX domain socket with abstract address

Co-authored-by: Changbin Park <changbin.park@ahnlab.com>
2022-08-01 06:57:25 -04:00
Ata Yardımcı 1bd88de2e5 Fix test build warning (#1344)
Co-authored-by: ata.yardimci <ata.yardimci@erstream.com>
2022-07-31 16:51:06 -04:00
Rockybilly 0b541ffebc Add get_socket_fd method to Client and ClientImpl, add according unit… (#1341)
* Add get_socket_fd method to Client and ClientImpl, add according unit test

* Change name get_socket_fd to get_socket

* Change name get_socket to socket

Co-authored-by: ata.yardimci <ata.yardimci@erstream.com>
2022-07-31 08:27:38 -04:00
yhirose 106be19c3e Issue 49512: cpp-httplib:server_fuzzer: Timeout in server_fuzzer 2022-07-30 23:27:29 -04:00
yhirose 25d72bf881 Release v0.11.0 2022-07-29 20:45:55 -04:00
Mehmet İbrahimoğlu 9d5b5297cc ssize_t redefinition on Windows - int/int64 vs long/long long (#1337)
* ssize_t redefinition on Windows - int/int64 vs long/long long

* Define ssize_t as __int64 for _WIN64, not long long

Co-authored-by: iamttaM <9880090+oculusbytes@users.noreply.github.com>
2022-07-29 20:42:31 -04:00
Mehmet İbrahimoğlu 462884bebb With SSL enabled and NOMINMAX not defined, there is a conflict with 'max', which this fixes (#1334)
Co-authored-by: iamttaM <9880090+oculusbytes@users.noreply.github.com>
2022-07-27 08:16:06 -04:00
ZHANG Xiang b1cc24b795 Update README.md (#1332)
Update error list. Introduce `httplib::to_string` in the example code.
2022-07-25 06:48:52 -04:00
yhirose f0eb55b327 Changed to use const std::string & as much as possible instead of const char * (#1331)
* Changed to use `const std::string &` as much as possible instead of `const char *`

* Fix problems on Windows
2022-07-22 22:44:33 -04:00
yhirose 6dc285b5ca Merge branch 'kuguma-use_exception_ptr' 2022-07-15 17:33:53 -04:00
yhirose 07e614eef7 clangformat and README update 2022-07-15 17:32:38 -04:00
yhirose 916b2a8fd3 Merge branch 'use_exception_ptr' of https://github.com/kuguma/cpp-httplib into kuguma-use_exception_ptr 2022-07-15 17:19:39 -04:00
Kai Aoki 869f5bb279 fix ExceptionHandlerTest.ContentLength 2022-07-15 11:50:26 +09:00
yhirose 3e21338f82 Update README 2022-07-14 20:59:48 -04:00
yhirose 37bb3c6a77 No longer support VS 2013 and older #1325 (#1326)
* Fixed a warning

* No longer support VS 2013 and older (Fix #1325)
2022-07-14 20:57:41 -04:00
Kai Aoki d4ab2fa0e6 fix double ref and case of exceptions that are not std::exception 2022-07-15 01:45:10 +09:00
Kai Aoki 72d3f4896a Update httplib.h
use std::exception_ptr
2022-07-12 00:10:57 +09:00
yhirose 5e6f973b99 Release v0.10.9 2022-07-08 17:40:05 -04:00
yhirose 7ed77b02ad Disable YouTubeNoSSLDigest 2022-07-08 17:40:05 -04:00
yhirose 127a64d5a0 Skip preamble and epilogue in multipart/form-data (Fix #1317) (#1320)
* fix: skip MIME preamble (#1317)

* Skip epilogue in multipart/form-data

Co-authored-by: Gavin1937 <71205842+Gavin1937@users.noreply.github.com>
2022-07-08 17:26:50 -04:00
yhirose caa31aafda Accept large data transfer over SSL (Fix #1261, Close #1312) 2022-07-02 07:50:33 -04:00
yhirose dae318495f Revert "Accept large data transfer over SSL (#1261)"
This reverts commit 307b729549.
2022-07-02 07:18:59 -04:00
Andrea Pappacoda 305a7abcb9 fix: update CPPHTTPLIB_VERSION to 0.10.8 (#1305) 2022-06-08 16:44:10 -04:00
yhirose 219d13b718 Fix #1303 2022-06-07 09:52:08 -04:00
conghuawang df20c27696 resolve http server can't send file large than 2GB (Fix #1290) (#1294)
* resolve problem: http server can't send file large than 2GB.
add unit test for http server send large file.
add /bigobj compile option to msvc x64.

* disable unit test "ServerLargeContentTest" due to out-of-memory on GitHub Actions.
2022-05-27 11:56:20 -04:00
yhirose a5a62768c0 Fix #1292 (#1296) 2022-05-27 11:54:43 -04:00
yhirose 4001637beb Added CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 2022-05-26 10:16:32 -04:00
Martín Córdova 47044c05a8 Fix compile error with MINGW-64 GCC-12.1.0 (#1283) 2022-05-24 07:16:54 -04:00
Andrea Pappacoda a449d82723 build(cmake): minor tweaks (#1274)
- Enable THREADS_PREFER_PTHREAD_FLAG to use -pthread where supported
- Remove low-level compile features (closes #1272)
- Remove unneeded DESTINATION options where possible
2022-05-17 07:02:44 -04:00
yhirose fee8e97b4e Rename fuzzing test corpus for #1264 2022-05-03 14:53:59 -04:00
yhirose 72d9ed4056 Added fuzzing corpus for #1264 2022-05-02 18:06:14 -04:00
Andrea Pappacoda 1be1b3a86d build(meson): don't require python3 (#1267)
Thanks to abf3a67dd0 the use of python3
isn't required anymore to configure the build, so I moved the
find_program('python3') inside the "if compile" block.

This makes it possible to configure cpp-httplib on systems where python
isn't available with tools like muon: https://sr.ht/~lattis/muon/
2022-04-30 17:40:47 -04:00
yhirose 9452c0a4b6 Release v0.10.7 2022-04-28 10:21:14 -04:00
Yoshiki Matsuda 307b729549 Accept large data transfer over SSL (#1261)
* Add large data transfer test

* Replace `SSL_read` and `SSL_write` with `ex` functions

* Reflect review comment

* Fix return value of `SSLSocketStream::read/write`

* Fix return value in the case of `SSL_ERROR_ZERO_RETURN`

* Disable `LargeDataTransfer` test due to OoM in CI
2022-04-27 21:08:39 -04:00
mylogin 696239d6e1 Link Windows crypto libs only when CPPHTTPLIB_OPENSSL_SUPPORT is set (#1254) 2022-04-20 22:04:55 -04:00
Andrea Pappacoda 6929d90353 build(meson): allow using OpenSSL 3.0 (#1256)
Following 0857eba17b cpp-httplib is fully compatible with OpenSSL versions newer than 1.1.1
2022-04-20 21:39:52 -04:00
yhirose 348d032029 Updated README 2022-04-19 23:02:30 -04:00
Andrea Pappacoda d1d3fcdfd5 build(meson): mark *_encrypted_pem as test deps (#1255)
Meson only runs required targets. The key_encrypted_pem and
cert_encrypted_pem targets added in 020b0db090
and 8191fd8e6c weren't added to the list
of targets required by the test target, so the generation of the
encrypted certs was skipped, resulting in the failure of
BindServerTest.BindAndListenSeparatelySSLEncryptedKey.
2022-04-19 07:12:00 -04:00
Eli Schwartz abf3a67dd0 meson: fix regression that broke extracting version (#1253)
* meson: fix regression that broke extracting version

In commit 33f67386fe the code that
heuristically parsed the version broke due to the version being moved
around into a more easily accessible define.

While we are at it, pass the exact path of httplib.h to un-break usage
as a meson subproject. This was broken in commit
8ecdb11979 which checked the return code
of trying to get the version; it was always broken, but formerly failed
in silence and resulted in no version number.

* meson: use the compiler builtins to extract the version from the header

As a convenient string define, it is now possible to ask the
preprocessor what the version of cpp-httplib is. This can be used from
meson too, in order to avoid encoding C++ file structure into python
regexes.
2022-04-19 07:11:51 -04:00
yhirose d87abeecf0 Release v0.10.6 2022-04-17 17:34:48 -04:00
yhirose 4e28e4f741 Fix #1251 2022-04-17 11:53:41 -04:00
yhirose 80a55cedeb Removed Repl.it examples 2022-04-16 21:11:17 -04:00
yhirose d05c343602 Release v0.10.5 2022-04-16 21:02:30 -04:00
yhirose 33f67386fe Fix #1249 2022-04-16 08:54:56 -04:00
yhirose 56d8168dc4 clangformat 2022-04-16 08:52:55 -04:00
greenfish 5d87cc0558 resolve compiler warnings (#1246)
* resolve compiler warnings

- check `WSAStartup` return.
- `const` is not suitable for `std::move`.

* resolve compiler warnings

- bool startup => bool is_valid_.
- remove `const` not removed.
2022-04-14 11:46:10 -04:00
yhirose cb41947eb4 Fix #1235 (#1243)
* Fix #1235

* fix BindIPAddress error (#1242)

* Code cleanup

* Added a unit test

* Commented out 'SSLClientTest.SetInterfaceWithINET6'

* Fixed incorrect return value from if2ip

* Removed if_nametoindex call

Co-authored-by: Kotarou <2918558+CyberKoo@users.noreply.github.com>
2022-04-13 21:32:46 -04:00
Kotarou 0857eba17b replace deprecated OpenSSL functions with evp functions (#1241) 2022-04-11 13:40:58 -04:00
Andrea Pappacoda 020b0db090 build(meson): generate key_encrypted.pem (#1221)
8191fd8e6c only added one of the two files
2022-03-20 12:21:45 -04:00
Andrea Pappacoda bf0760fde4 fix: update user agent (#1218) 2022-03-18 18:12:51 -04:00
yhirose bb8e45383e Update README 2022-03-17 08:38:15 -04:00
yhirose a1df576e4f Fix #1212 2022-03-16 22:00:40 -04:00
yhirose 7fb0254794 Fix #1215 2022-03-16 12:50:13 -04:00
yhirose c82d1e52cc Fix #1214 2022-03-16 10:56:33 -04:00
yhirose 846151b605 Added a unit test case for large multipart form data 2022-03-16 09:37:13 -04:00
Alexandre Bouvier e44e31dd5b Add soversion (#1209) 2022-03-03 21:06:08 -05:00
yhirose f7b9501662 clangformat 2022-02-27 14:31:22 -05:00
yhirose e12fe4cbbb Performance improvement 2022-02-27 14:30:49 -05:00
yhirose 49d2e1f135 Fix problem with InvalidPort test 2022-02-27 14:29:34 -05:00
Sebastien Blanchet 8191fd8e6c Add optional private key password to SSLServer ctor (#1205) 2022-02-27 14:16:15 -05:00
yhirose d73395e1dc Release v0.10.3 2022-02-22 10:21:27 -05:00
au-ee 64d001162b CPPHTTPLIB_NO_DEFAULT_USER_AGENT skips default user agent (#1201) 2022-02-22 09:39:26 -05:00
yhirose bb00a23116 Apply clangformat 2022-02-09 17:16:47 -05:00
yhirose 63d6e9b91b Removed up.sh and down.sh 2022-02-09 17:16:18 -05:00
Edwin Kofler 66eed5681a Fix typo in Dockerfile (#1187) 2022-02-06 09:15:15 -05:00
Andrea Pappacoda 8ecdb11979 build(meson): always install a pkg-config file (#1182)
A pkg-config file was previously installed only if cpp-httplib was being
built as a compiled library.

Since architecture-independent .pc files
can exist in /usr/share/pkgconfig, it can be useful to install one even
when installing the header-only version (for example, it could be used
by third party projects to easily find out if cpp-httplib is installed
and its version, using something like Meson's `dependency()` or CMake's
`pkg_check_modules()`).

The change makes the Meson build behave a bit more like the CMake one,
as it also always installs a CMake Config file, but here the pkg-config
file gets installed to the correct architecture-independent directory
(`datadir` represents /usr/share on Linux and simiar systems).

Lastly, I made some minor cleanups.
2022-02-03 19:50:49 -05:00
Andrea Pappacoda 894fcc8e02 test: add missing "_Online" suffix (#1183)
This test fails reproducibly in a Debian build chroot, and they generally don't have internet access
2022-01-30 12:34:52 -05:00
Rockybilly 7f43f0f3ff User-Agent update cpp-httplib/0.10.2 (#1181) 2022-01-28 12:27:27 -05:00
Gregor Jasny 87e03dd1ce Report connection timeout as separate event (#1171) 2022-01-26 17:32:40 -05:00
yhirose e5cacb465d Fix #1172 (#1173)
This change is based on RFC7230, § 3.5 'Message Parsing Robustness': "Although the line terminator for the start-line and header fields is the sequence CRLF, a recipient MAY recognize a single LF as a line terminator and ignore any preceding CR."
2022-01-26 13:34:23 -05:00
yhirose ee8371f753 Added 'PostLarge' unit test for #1169 2022-01-22 09:52:27 -05:00
yhirose 081723f983 Add another fuzz test corpus 2022-01-20 15:27:26 -05:00
yhirose b61f36579c Fix #1166 2022-01-20 15:24:09 -05:00
ArnaudBienner 33f53aa458 Fix set_content_provider example in README.md (#1163) 2022-01-13 12:26:34 -05:00
yhirose 412ab5f063 Added example/Dockerfile.hello 2022-01-11 00:18:20 -05:00
yhirose 11e02e901c Fixed unit test 2021-12-31 16:10:57 -05:00
yhirose 65a8f4cf44 Added hosted_at. (Resolve #1113) 2021-12-31 15:35:52 -05:00
yhirose 27d128bbb4 Fix problems in #1154 2021-12-31 14:55:40 -05:00
yhirose 070f9bec58 Code cleanup 2021-12-31 13:27:47 -05:00
c00c f817032513 fix socket option setting for windows (#1154)
* fix socket option setting for windows

* misc

Co-authored-by: zhangsen <zhangsen@cyberkl.com>
2021-12-31 10:07:59 -05:00
yhirose 17abe221c0 Fix is_file problem on Windows (#1153) 2021-12-30 14:54:57 -05:00
vitaly-ivanov 4a7a81e039 Work around silly Win defines to support BoringSSL (#1148)
* Work around silly Win defines to support BoringSSL

* changes wrapped into ifdef(_WIN32) just in case
2021-12-30 11:08:51 -05:00
yhirose 37fd4eb643 Code cleanup 2021-12-26 07:53:56 -05:00
yhirose 865b0e4c03 Resolve #1145 2021-12-26 07:53:06 -05:00
yhirose b324921c1a Release v0.10.1 2021-12-24 21:01:06 -05:00
yhirose 63f72caf30 Fix "Issue 42689 in oss-fuzz: cpp-httplib:server_fuzzer: Timeout in server_fuzzer" 2021-12-24 20:58:09 -05:00
yhirose 99ac17b90a Fix #1140 2021-12-23 23:19:14 -05:00
yhirose 4b0ed9ee88 Release v0.10.0 2021-12-21 18:15:41 -05:00
yhirose 20056f6cda Update test.cc 2021-12-19 14:19:10 -05:00
yhirose 3b35279b16 Added SSLServer::ssl_context() 2021-12-18 00:21:41 -05:00
yhirose 27deb44df5 Update SSL related code 2021-12-18 00:15:38 -05:00
yhirose 24a3ef949b Performance improvement for multipart form data file upload. 2021-12-18 00:15:10 -05:00
yhirose bc3e098964 Updated .gitignore 2021-12-17 22:36:02 -05:00
yhirose c247dcdd7b Added uploader.sh 2021-12-17 22:34:00 -05:00
yhirose 793ae9855e Fix #1041 (#1132)
* Fix #1041

* Fixed problem with is_socket_alive

* Adjust the way to check if the sockt is still alive.

* Revert "Adjust the way to check if the sockt is still alive."

This reverts commit 6c673b21e5.

* Adjust is_socket_alive according to the code review
2021-12-16 21:06:17 -05:00
yhirose 9fa426d51b Added more fuzzing corpus 2021-12-14 18:35:20 -05:00
yhirose cec6288a99 Resolve #1131 2021-12-14 07:58:21 -05:00
yhirose 9639578c2a Release v0.9.10 2021-12-11 19:26:22 -05:00
yhirose 743ecbd365 Issue1121 (#1122)
* Fixed test/Makefile problem when cleaning *.pem files

* Fix #1121
2021-12-11 19:07:12 -05:00
yhirose 084c643973 Fixed README 2021-12-10 22:33:59 -05:00
Andrea Pappacoda 824e7682e4 test: add missing _Online suffixes (#1110) 2021-11-27 09:54:05 -05:00
Andrea Pappacoda f9074684dd build(meson): drop Git-based version detection (#1109)
See 3051152103
2021-11-27 09:47:09 -05:00
yhirose ddff782133 Release v0.9.9 2021-11-26 20:46:38 -05:00
yhirose 3051152103 Fix #1102 (#1108) 2021-11-26 20:44:58 -05:00
yhirose 06026bb47d Code formating 2021-11-23 10:53:05 -05:00
yhirose 226388ae27 Resolve #1100 2021-11-23 10:47:30 -05:00
Scott Graham ea7548b4cc Remove stray ; causing warning with -Wextra-semi (#1099) 2021-11-22 07:16:07 -05:00
Rodolphe c7486ead96 accept protobuf encoding (#1096)
Co-authored-by: rodolphe <rodolphe@zen.ly>
2021-11-17 13:14:31 -05:00
yhirose 90a291214c Update Makefile 2021-11-15 23:08:49 -05:00
Andrea Pappacoda c111c42a86 build(meson): feature args in pkg-config file (#1092)
Follow-up for #1090. The args are now also added to the pkg-config file.
2021-11-15 22:50:33 -05:00
yhirose 6fb5b63018 Fix #1093. Remove meson-build tests from GitHubActions (#1094) 2021-11-15 22:49:40 -05:00
yhirose ec56dfa35e Fix #1085 (#1091) 2021-11-15 14:37:10 -05:00
Andrea Pappacoda 943cd51b67 build(meson): pass feature args to dependency consumers (#1090)
Fixes #1087
2021-11-15 14:03:25 -05:00
Yuji Hirose 301faa074c Added test case for #1065 2021-10-29 07:29:23 -04:00
DavidKorczynski dc0481e832 Add CIFuzz integration (#1079)
Signed-off-by: David Korczynski <david@adalogics.com>
2021-10-27 12:19:21 -04:00
zhenyolka 4f8fcdbaf7 Allow to specify server IP address (#1067)
* Allow to specify server IP address

* Reimplement in set_hostname_addr_map

* Add tests for set_hostname_addr_map

* Fix tests after implement set_hostname_addr_map

* SpecifyServerIPAddressTest.RealHostname typo
2021-10-16 15:05:55 -04:00
CarlosLeeGit b80aa7fee3 support custom ssl ctx configuration for SSLServer (#1073) 2021-10-15 07:13:16 -04:00
yhirose c384be02c9 Fixed GitHub Actions build error 2021-10-14 10:52:05 -04:00
yhirose d17ac3bb40 Fix "Issue 39922 in oss-fuzz: cpp-httplib:server_fuzzer: Timeout in server_fuzzer" 2021-10-14 08:55:29 -04:00
yhirose c7554ccac2 Fix #1069 (#1070) 2021-10-09 20:35:58 -04:00
yhirose 35ef1c7bae Fix #1063 2021-10-03 18:37:59 -04:00
David Pfahler d87d0672a8 Split the header only if needed (#1060)
* Update split.py

* Update split.py

* Update split.py

* Update split.py
2021-09-28 18:11:50 -04:00
estshorter 3da42fd1e8 Avoid min/max macro expansion on Windows (#1057) 2021-09-25 08:53:15 -04:00
yhirose 503aa61325 Fix problem with an empty parameter in set_base_dir 2021-09-20 17:40:05 -04:00
null e4c276d0c2 doc: fix typo in README (#1056)
fixed typo in README.md, replacing `Sutudio` with `Studio`.
2021-09-18 11:33:23 -04:00
yhirose e07f7691a8 Update README 2021-09-17 21:26:31 -04:00
yhirose 623ab4a96e Updated README regarding Visual Studio support 2021-09-17 11:36:08 -04:00
Zizheng Tai e1efa337a2 Make Client move-constructible (#1051) 2021-09-16 14:05:42 -04:00
Andrea Pappacoda 549cdf2f7d test: avoid infinite loop when IPV6 is unsupported (#1054) 2021-09-16 14:04:43 -04:00
yhirose 3c522386e9 Fix "Issue 38551 in oss-fuzz: cpp-httplib:server_fuzzer: Timeout in server_fuzze" 2021-09-12 19:24:48 -04:00
yhirose c202aa9ce9 Read buffer support. (Fix #1023) (#1046) 2021-09-12 00:26:02 -04:00
Andrea Pappacoda e3e28c6231 meson: add tests (#1044)
This integrates the "main" test suite (test/test.cc) in Meson.

This allows to run the tests in the CI with the Meson-built version of
the library to ensure that nothing breaks unexpectedly.

It also simplifies life of downstream packagers, that do not have to
write a custom build script to split the library and run tests but can
instead just let Meson do that for them.
2021-09-11 14:26:48 -04:00
yhirose 4e05368086 Fix #1054 2021-09-11 14:13:49 -04:00
yhirose e1afe74fe2 Fix #1037 2021-09-10 22:42:14 -04:00
yhirose 461acb02f5 Comment out SlowPostFail test for now 2021-09-10 22:37:31 -04:00
Gregor Jasny 415edc237c Set error variable for failed write_data (#1036) 2021-09-05 16:15:46 -04:00
Andrea Pappacoda e20ecd2574 Full Meson support (#1033)
* Full Meson support
cpp-httplib can be now built with Meson even in compiled library mode.

The library is built with LTO, supports OpenSSL, zlib and Brotli,
and the build system also generates a pkg-config file when needed.

Compared to the CMake file this one is quite small (more than five times
smaller!), and maintaining it won't be an issue :)

* meson: automatic versioning
2021-09-04 11:33:53 -04:00
yhirose ab477b5631 Fix "Issue 37742 in oss-fuzz: cpp-httplib:server_fuzzer: Timeout in server_fuzzer" 2021-09-02 22:57:57 -04:00
yhirose 0823d5c7f2 Fixed #1031 2021-08-30 17:16:31 -04:00
yhirose 1cc6930363 Append '_Online' suffix to Unit test names that access external servers 2021-08-23 13:02:19 -04:00
yhirose 4297500928 Fix #1024 2021-08-17 09:28:17 -04:00
Thomas Behn a58f042614 Don't define INVALID_SOCKET if it has been defined already (i.e. by libpcap) (#1021)
Co-authored-by: Thomas Behn <Thomas.Behn@meinberg.de>
2021-08-10 08:29:27 -04:00
yhirose 469c6bc2b6 Fix #1017 2021-08-02 15:44:50 -04:00
Joel Rosdahl 887074efd2 Add test of httplib.h split into .h + .cc (#1015)
In order to test the split version (.h + .cc via split.py):

- Added a test_split program in the test directory whose main purpose is
  to verify that it works to compile and link the test case code against
  the split httplib.h version.
- Moved types needed for test cases to the “header part” of httplib.h.
  Also added forward declarations of functions needed by test cases.
- Added an include_httplib.cc file which is linked together with test.cc
  to verify that inline keywords have not been forgotten.

The changes to httplib.h just move code around (or add forward
declarations), with one exception: detail::split and
detail::process_client_socket have been converted to non-template
functions (taking an std::function instead of using a type parameter for
the function) and forward-declared instead. This avoids having to move
the templates to the “header part”.
2021-07-31 09:53:30 -04:00
Joel Rosdahl 9c2c15ca45 Add missing template method implementations (#1013)
When using the split version of httplib.h the templated implementation
of e.g. Client::set_connection_timeout ends up in httplib.cc and
therefore results in a linker error since the needed template
specialization has not been instantiated. Fix this by moving the
implementation of template methods into the part that ends up in
httplib.h after the split.

Fixes #1008.
2021-07-30 10:05:49 -04:00
Joel Rosdahl 1b3b098329 Avoid hardcoded ports in RedirectToDifferentPort.Redirect test (#1012)
The RedirectToDifferentPort.Redirect test assumes that port 8080 and
8081 are available on localhost. They aren’t on my system so the test
fails. Improve this by binding to available ports instead of hardcoded
ones.
2021-07-30 10:04:02 -04:00
Joel Rosdahl 6f7075e3aa Improve split script (#1011)
- Added shebang and made the script executable.
- Added help text.
- Added -o/--out argument for specifying output directory.
- Added -e/--extension argument for specifying file extension of the
  implementation file.
- Made the script find httplib.h next to split.py instead of the current
  working directory. This makes it possible to call the script from
  another directory.
- Simplified code structure slightly.
- Improved variable naming to follow Python conventions.
2021-07-30 10:03:05 -04:00
yhirose ccbddd8842 Allow LargeRandomData test only on Windows 2021-07-22 22:17:31 -04:00
yosh-matsuda 879dd261c2 Fix gzip compression/decompression over 4 GiB data size (#1002)
* Fix gzip compression/decompression over 4 GiB data size

* Add gzip test for large random data
2021-07-22 22:07:40 -04:00
xxrl 52f5eb5980 [Fix] ca_cert_path/ce_cert_store lose (#1004)
When redirect from http to https, user setting for ca_cert will lose

issue: #1003
2021-07-22 21:41:41 -04:00
Gregor Jasny ea2f69a0d7 Add httplib::Error to std::string function (#999)
Fixes: #978
2021-07-19 21:17:44 -04:00
Gregor Jasny 9f2064a8ed Fix remaining test warnings (#1001)
* Use portable way to encode ESC

'\e' is a GNU extension

* Use length specifier for size_t
2021-07-19 21:17:18 -04:00
yhirose e3750d9ddf Simplefied server APIs 2021-07-17 18:09:30 -04:00
yhirose c1eee3012e Fix #998 2021-07-17 17:18:56 -04:00
Gregor Jasny 6b08babbd2 Use googletest 1.11.0 (#1000)
* Update googletest to version 1.11.0

* Fix test warnings
2021-07-17 13:21:03 -04:00
yhirose 215b81342e Added a test case for #996 2021-07-15 08:24:06 -04:00
yhirose 06bfa7e08b Fix #979 2021-07-14 22:49:49 -04:00
yhirose 3d83cbb872 Improve string compare performance 2021-07-12 23:51:56 -04:00
yhirose 8a803b30f6 Fix #990 2021-07-12 23:46:25 -04:00
yhirose 80be649de7 Fix #961 2021-06-26 18:26:33 -04:00
yhirose 9648f950f5 Updated README 2021-06-18 08:45:50 -04:00
Gregor Jasny 6b9ffc8bec Remove dead code (#965) 2021-06-18 07:20:34 -04:00
Simon Edlund d903053faf Update httplib.h (#964)
operator""_ replaced by operator""_t
2021-06-17 10:57:25 -04:00
yhirose 676f1b5a26 Updated the user agent string 2021-06-14 08:43:12 -04:00
Baruch Nissenbaum b8dec12f15 Limit SSL_ERROR_WANT_READ retries to 1 sec (#957)
retry with 1ms delays to prevent CPU hoggin
2021-06-14 08:41:20 -04:00
yhirose fc9b223acc Updated copyright year 2021-06-11 14:45:35 -04:00
CncGpp ba824089d7 Fix code err code 401 when the password is empty in base_auth. (#958) 2021-06-11 14:39:33 -04:00
Andrea Pappacoda 1a2faf09e0 Add header-only Meson support (#955)
* Add header-only Meson support
This allows users to call `dependency('httplib')` and have the include
directory automatically configured

* Rename `httplib` to `cpp-httplib`
2021-06-05 16:45:00 -04:00
yhirose 5a43bb8149 Implemented #946 in a different way 2021-06-02 13:45:47 -04:00
yhirose 0104614656 Code refactoring 2021-06-02 08:11:31 -04:00
yhirose 77a77f6d2d Added set_default_headers on Server 2021-05-23 19:06:28 -04:00
Mathias Laurin 089b9daa1c Fix virtual call in ClientImpl::~ClientImpl() (#942)
* Fix virtual call in ClientImpl::~ClientImpl()

This fixes a warning in clang tidy:

> Call to virtual method 'ClientImpl::shutdown_ssl' during
> destruction bypasses virtual dispatch

ClientImpl::~ClientImpl() calls lock_socket_and_shutdown_and_close()
that itself calls shutdown_ssl().  However, shutdown_ssl() is virtual
and C++ does not perform virtual dispatch in destructors, which results
in the wrong overload being called.

This change adds a non-virtual shutdown_ssl_impl() function that is
called from ~SSLClient().  We also inline sock_socket_and_shutdown_and_close()
and removes the virtual call in ~ClientImpl().

* Inline and remove lock_socket_and_shutdown_and_close()

The function only has one caller.
2021-05-22 20:15:20 -04:00
yhirose ba34ea4ee8 Fix #944 2021-05-22 19:24:50 -04:00
Baruch Nissenbaum 2917b8a005 Explicit cast from size_t to uInt (#941)
* Explicit cast from size_t to uInt

* static_cast<uInt> instead of C style cast
2021-05-19 18:03:59 -04:00
Joseph Huang dcf24d45a2 fix ssesvr use of deleted function (#938) 2021-05-18 23:19:15 -04:00
yhirose 75fdb06696 Added a missing member in copy_settings. 2021-05-15 09:14:44 -04:00
Alex Hornung e00ad37580 Add option to bypass URL encode of path (#934) 2021-05-15 08:48:25 -04:00
Vincent Stumpf 5cfb70c2b4 Fix some shadowed variable warnings (#935) 2021-05-15 08:46:16 -04:00
Alessio Pollero 2a70c45697 Fix client.cc code, since res.error() without operator overloading… (#921)
* Fix client.cc code, since res.error() without operator overloading causing error in Xcode

* Add unit test to check new error to string with operator overloading

* Add inline as requested in code review comment
2021-05-01 13:29:23 -04:00
Aswin Raj Kharel c58b00580e reserving before encoding (#912) 2021-04-24 16:19:14 -04:00
Ken Schalk 7c60e69c33 Remove redunant call to close_socket (#911) 2021-04-23 17:07:19 -04:00
yhirose 33e94891ee Updated test.cc 2021-04-22 08:04:46 -04:00
yhirose 73e0729f63 Change sink.write() to return boolean 2021-04-22 07:14:08 -04:00
yhirose 21c529229c Fixed timeout issues 2021-04-22 07:14:08 -04:00
yhirose 63643e6386 Code format 2021-04-13 20:52:49 -04:00
yhirose 6cc2edce99 Added set_address_family 2021-04-13 20:49:52 -04:00
yhirose d122ff3ca8 Code formatting 2021-04-13 12:38:45 -04:00
James Young 14c6d526b4 Use newer version-flexible TLS/SSL method (#904) 2021-04-13 09:11:38 -04:00
Philipp Hasper 28e07bca16 Fixed minor code smells (#901) 2021-04-09 14:55:21 -04:00
yhirose faa5f1d802 Additional changes for #889 2021-04-05 16:13:41 -04:00
yhirose 9d3365df54 Fix #889 2021-04-05 11:40:53 -04:00
yhirose 6ff84d34d1 Another simpler implementation of #890 (#891) 2021-04-02 18:25:04 -04:00
yhirose b845425cd0 Fix #878 2021-03-16 19:42:44 -04:00
yhirose 89519c88e2 Fix #874 2021-03-10 15:57:56 -05:00
yhirose ff813bf99d Fix #863 2021-02-17 15:36:56 -05:00
yhirose cf475bcb50 Fix #860 2021-02-12 12:21:43 -05:00
yhirose bc80d7c789 Fixed ClientStop test problem 2021-02-06 20:12:30 -05:00
yhirose b7566f6961 Resolve #852 2021-02-02 22:09:35 -05:00
Nikolas 0542fdb8e4 Add exception handler (#845)
* Add exception handler

* revert content reader changes

* Add test for and fix exception handler

* Fix warning in test

* Readd exception test, improve readme note, don't rethrow errors, remove exception handler response
2021-01-28 17:19:11 -05:00
yhirose 78c474c744 Update README 2021-01-27 11:59:42 -05:00
yhirose 88411a1f52 Fix #846 2021-01-27 14:35:32 +00:00
yhirose ae6cf70bc4 Updated README 2021-01-26 08:38:28 -05:00
yhirose 68d1281759 Resolve #839 2021-01-23 13:33:27 -05:00
yhirose 0308d60cb2 Resolve #831 (#835) 2021-01-23 12:23:06 -05:00
yhirose 59f5fdbb33 Resolve #840 2021-01-23 11:43:01 -05:00
Anonymous 13184f5f80 Return Server& from handler setters (#836)
* Update httplib.h

* Update httplib.h

* Update httplib.h

* Update httplib.h
2021-01-22 09:20:21 -05:00
Eric Lee 8d9a477edb No content check (#823)
* No content check

* unit test for no content

* fixing merge conflict break

* oops during manual merge conflict
2021-01-13 17:51:40 -05:00
yhirose 85b4abbf16 Updated the simple example 2021-01-07 19:56:33 -05:00
yhirose e42a358da8 Updated README 2021-01-07 19:00:24 -05:00
yhirose f008fe4539 Added middleware support (#816) 2021-01-07 18:40:52 -05:00
yhirose ddf41d29ef Added const *char and size_t interface 2021-01-06 22:39:58 -05:00
yhirose 3f88a46c4a Code format 2020-12-31 18:14:28 -05:00
yhirose 242706ea34 Fix #820 2020-12-31 18:14:05 -05:00
yhirose a9f5f8683f Fixed warnings on Visual C++ 2020-12-31 11:35:11 -05:00
yhirose 60c2213893 Fix #817 2020-12-31 10:58:44 -05:00
yhirose eb2d28bca2 Code cleanup 2020-12-31 10:37:06 -05:00
Yuri Santos 86f637a246 Added encode_uri_param tests with UTF-8 characters (#818)
Remove additional lines between tests
2020-12-30 23:06:36 -05:00
yhirose 2c07ec4600 Code cleanup 2020-12-29 09:39:19 -05:00
yhirose 871d8d67b0 Made Request paramater const in handle_file_request 2020-12-28 22:03:44 -05:00
Anonymous 7299713195 Fix readme Response::set_chunked_content_provider (#811)
`Response::set_chunked_content_provider` has formal parameters, not one.
2020-12-25 19:55:57 -05:00
yhirose 96afa7e108 Updated README 2020-12-21 13:40:32 -05:00
yhirose 55f57af0b9 Update README 2020-12-21 09:27:36 -05:00
yhirose 6b35cd0116 Updated README 2020-12-21 08:15:06 -05:00
yhirose 99f2229e48 Updated README 2020-12-19 22:43:31 -05:00
yhirose b9641048fc Switch to hghttp2.org/httpbin for redirect test. (#538) 2020-12-19 20:27:52 -05:00
yhirose e9c6c6e609 Code format 2020-12-19 20:14:53 -05:00
yhirose 40db42108f Fixed problem with invalid requests including spaces in URL path 2020-12-19 12:03:08 -05:00
Anonymous 24bb1387d6 Update README.md (#806) 2020-12-19 11:12:44 -05:00
Jeremie Rahm d0bd4afb0b Ensure socket is closed after processing in SSLServer (#804) 2020-12-18 19:29:36 -05:00
Yuri Santos 78ea786abd [PR] Special function to encode query params (#801)
* Special function to encode query params

* Fix #include <iomanip>

* Added unescaped charsets to encode_query_param

* Unit tests for encode_query_param
2020-12-18 17:51:11 -05:00
Miosame 9cac2c9ceb typo: specitic => specific (#802) 2020-12-18 15:12:21 -05:00
Anonymous 0cff3245df Extend built-in extension MIME mapping (#799)
* Update README.md

* Update httplib.h

* Update httplib.h

* Update httplib.h

* Update httplib.h

* Remove duplicate cases

Someone left a bunch of duplicate cases, idiot, couldn't have been me.

* Reformat

Modify spacing and whatnot

* Update README.md
2020-12-18 09:32:19 -05:00
yhirose 0e3925db3f Fixed build error 2020-12-18 00:07:48 +00:00
yhirose c9a13d214b Changed not to use string_view 2020-12-17 18:48:27 -05:00
yhirose 0954af2d4c Use user-defined literals for file extention match 2020-12-17 18:27:04 -05:00
yhirose 7c1c952f5a Don't allow invalid status code format (It sould be a three-digit code.) 2020-12-15 20:25:24 -05:00
yhirose a6edfc730a Added a unit test for static file with range 2020-12-15 18:47:51 -05:00
yhirose c1264bfedc Fix problem with mp4 w/ Range header 2020-12-14 22:41:05 -05:00
yhirose 90a5b6ceb0 Updated README 2020-12-04 19:39:39 -05:00
yhirose eb240ad2e5 Code cleanup 2020-12-03 16:03:12 -05:00
yhirose 88c961f37e Removed std::atomic<Error> error_ 2020-12-01 15:17:34 +00:00
yhirose b952376968 Fixed warning 2020-12-01 03:50:55 +00:00
yhirose 5dd605d3a2 Fix #762 2020-11-30 21:49:31 -05:00
Seunghwan Hong 9c0c98b1ed Add keep_alive_timeout guide on README.md (#778) 2020-11-30 07:11:22 -05:00
yhirose 615867322d Fixed build errors and apply clangformat 2020-11-29 12:29:18 -05:00
David Wu 02d3cd5909 Fix multiple threading bugs including #699 and #697 2020-11-29 12:29:15 -05:00
yhirose 47e5af15ea Updated README 2020-11-22 09:22:40 -05:00
yhirose a5c239c174 Fix #765 2020-11-21 16:35:31 -05:00
yhirose c2afc5ca44 Added chunked content provider support on client 2020-11-21 08:46:50 -05:00
yhirose cee062d4c9 Fixed unit tests due to the change in #763 2020-11-19 21:04:46 -05:00
Unkorunk b21dc8cbe0 Fix incorrect content_encoding for Brotli (#763) 2020-11-19 12:39:20 -05:00
372046933 e1133a2dcb std::tolower is undefined if the argument's value is neither representable as unsigned char nor equal to EOF (#761)
Co-authored-by: taoxu <taoxu@bilibili.com>
2020-11-19 09:21:40 -05:00
yhirose e273fec93c Fixed Visual Studio setup 2020-11-19 09:17:59 -05:00
yhirose 95d0b073bd Fix #754 2020-11-15 08:13:08 -05:00
vawen 9c7d841b37 Fix: #746 Compile error under VS2015 (#747)
Co-authored-by: jigarcia@vaxtor.es <jigarcia@vaxtor.es>
2020-11-10 08:41:53 -05:00
yhirose f086bf5310 Fix #738 2020-11-08 18:16:23 -05:00
yhirose 6613d7b7ad Fixed warnings 2020-11-08 17:59:11 -05:00
yhirose 6adf130bf3 Fix #739 2020-11-07 21:54:47 -05:00
miketsts b6b2eaf5bc Add unit test SSLConnectTimeout (#741)
Add unit test for issue #682 fixed in PR #728, which does not contain
the test of its own.

The test creates a fake SSL server, inherited from SSLServer, which
does not create an SSL context. When an SSL client attempts to send it
a request, it gets a timeout error. Prior to PR #728, the client would
wait indefinitely

Co-authored-by: Michael Tseitlin <michael.tseitlin@concertio.com>
2020-11-07 09:41:20 -05:00
yhirose eb4b7c70a9 Fix #737 2020-11-07 09:33:22 -05:00
yhirose 84661ea6ed Refactoring 2020-11-05 07:06:53 -05:00
yhirose 041122908c Fix problem with invalid range 2020-11-05 00:20:59 -05:00
yhirose 401de608df Fixed debug option problem. 2020-11-05 00:20:59 -05:00
yhirose 726c64cf10 Code format 2020-11-05 00:20:59 -05:00
Omkar Jadhav e1f781a21a [oss-fuzz] Enable msan for fuzz tests (#734)
* Disable openssl as we're not using it in fuzz tests

* Increase timeout in ClientStop test
2020-11-04 11:30:56 -05:00
Omkar Jadhav 72b81badad Fix issues reported by oss-fuzz (#729)
* Fix oss-fuzz issue #26529

* Add test for oss-fuzz issue  #26598

* Fix oss-fuzz issue  #26632

* Revert change and add new test cases
2020-11-03 11:36:02 -05:00
yhirose 17428a8fbf Fixed warning 2020-11-03 09:16:28 -05:00
Daniel Ottiger 6e1879dfae ssl-verify-host: fix verifying ip addresses containing zero's (#732)
* ssl-verify-host: fix verifying ip addresses containing zero's

If the subject alternate name contained an ip address with an zero
(like 10.42.0.1) it could not successfully verify.
It is because in c++ strings are null-terminated
and therefore strlen(name) would return a wrong result.
As I can not see why we can not trust the length returned by openssl,
lets drop this check.

* ssl-verify-host: add test case

lets try to validate against 127.0.0.1

Co-authored-by: Daniel Ottiger <daniel.ottiger@ch.schindler.com>
2020-11-02 20:27:34 -05:00
miketsts eb1d2e04bc SSL_connect and SSL_accept in non-blocking mode (#728)
SSL connection is performed in two steps:
First, a regular socket connection is established.
Then, SSL_connect/SSL_accept is called to establish SSL handshake.

If a network problem occurs during the second stage, SSL_connect on
the client may hang indefinitely.

The non-blocking mode solves this problem.

Co-authored-by: Michael Tseitlin <michael.tseitlin@concertio.com>
2020-11-02 17:05:08 -05:00
yhirose c909ffa758 Fix #731 2020-11-01 21:03:47 -05:00
yhirose ff5677ad19 Updated README 2020-10-27 20:35:56 -04:00
yhirose 8b1b31ac20 Fix #723 2020-10-27 20:32:19 -04:00
yhirose 953600c177 Fixed compiler error for old compiler. 2020-10-27 12:23:37 -04:00
yhirose 536e7eb7f2 Revert "Fix #697". (It broke unit test...)
This reverts commit 6d66721ba1.
2020-10-25 20:22:39 -04:00
yhirose 6d66721ba1 Fix #697 2020-10-25 16:55:54 -04:00
yhirose 3b29cd0bdc Fix #698 2020-10-25 12:14:54 -04:00
yhirose 109b624dfe Fix #708 (#713)
* Fix #708

* Rename ContentReceiver2 to ContentReceiverWithProgress
2020-10-22 11:48:43 -04:00
yhirose 0ed70c4d9f Fixed unit test errors 2020-10-21 14:34:27 -04:00
yhirose a50b7591ca Fix #714 2020-10-21 13:02:33 -04:00
yhirose bf8fc11b53 Code cleanup 2020-10-20 21:24:47 -04:00
Omkar Jadhav bc4a613b6d Fix suffix-byte-range issue (#711) 2020-10-20 11:11:27 -04:00
yhirose 4bb001351c Fix #705 2020-10-19 22:13:24 -04:00
yhirose e155ba44bb Fix #706 2020-10-19 15:23:35 -04:00
Muchamad Arifin Dwi P a4a9637738 Fix #700 null pointer exception (#702) 2020-10-16 20:44:14 -04:00
Omkar Jadhav 5292142046 Add cpp-httplib to oss-fuzz (#684)
* *Add server fuzzer target  and seed corpus
* Add fuzz_test option to Makefile

* Fix #685

* Try to fix Github actions on Ubuntu

* Added ReadTimeoutSSL test

* Comment out `-fsanitize=address`

* Rebase upstream changes

* remove address sanitizer temporarily

* Add separate Makefile for fuzzing

* 1. Remove special char from dictionary
2. Clean fuzzing/Makefile

* Use specific path to avoid accidently linking openssl version brought in by oss-fuzz

* remove addition of flags

* Refactor Makefile

* Add missing newline

* Add fuzztest to github workflow

* Fix

Co-authored-by: yhirose <yuji.hirose.bug@gmail.com>
2020-10-15 08:11:40 -04:00
Snape3058 cc5147ad72 Replace shared_ptr with unique_ptr for better performance (#695)
* Backport std::make_unique from C++14.

* Replace shared_ptr with unique_ptr for better performance.

Co-authored-by: Ella <maxutong16@otcaix.iscas.ac.cn>
2020-10-15 08:09:11 -04:00
Andrew Gasparovic fffbf1a669 Use move semantics instead of copy for functions (#692)
* Use move semantics instead of copy for functions

In some cases, a few more copies could be prevented by changing function definitions to accept parameters by const-ref, rather than by value, but I didn't want to change public signatures.

* Fix two use-after-move errors
2020-10-11 19:00:36 -04:00
lightvector d37bc0fb4d Allow client to specify boundary and use more entropy by default (#691) (#694) 2020-10-11 15:34:54 -04:00
Andrew Gasparovic 6d60dc8839 Add cache_control parameter to set_mount_point (#688)
* Add `cache_control` parameter to `set_mount_point`

Specifies the Cache-Control header value to return when specified. For example:

```
svr.set_mount_point("/assets", "public/assets", "public, max-age=604800, immutable");
```

* Add default for cache_control

Default to "no-cache", which is implicitly what is happening today.

* Change set_mount_point to accept Headers

* Don't use C++17 destructuring
2020-10-10 20:46:08 -04:00
Wang Gao b713a3a651 fix MSVC2015 error: std::tolower to ::lower (#689) 2020-10-10 12:02:50 -04:00
yhirose 7e8db1dc68 Comment out -fsanitize=address 2020-10-08 23:14:53 -04:00
yhirose 316add860b Added ReadTimeoutSSL test 2020-10-08 22:55:09 -04:00
yhirose 79ce6f1745 Try to fix Github actions on Ubuntu 2020-10-08 21:49:47 -04:00
yhirose 09fdf4eacd Fix #685 2020-10-08 21:37:42 -04:00
Omkar Jadhav 143b2dd15a Fix memory leak due caused due to X509_STORE (#671)
* Fix memory leak due caused due to X509_STORE

* Add test for repro and address sanitizer to compiler flags

* Add comment

* Sync

* Associate ca_store with ssl context within set_ca_cert_store()

* Split SlowPost test

* Fix #674

Co-authored-by: yhirose <yuji.hirose.bug@gmail.com>
2020-10-02 13:17:37 -04:00
yhirose e2c4e9d95c Fix #674 2020-09-29 19:22:28 -04:00
yhirose d87082f04b Split SlowPost test 2020-09-29 19:17:34 -04:00
yhirose cc14855ba0 Fix #661 2020-09-26 04:50:09 -04:00
yhirose 56c418745f Fixed conction close problem with HTTP 1.0 client 2020-09-25 20:58:49 -04:00
yhirose 4ce9911837 Add <sstream> 2020-09-25 18:17:32 -04:00
yhirose 559c407552 Adjusted SlowRequest test 2020-09-25 18:13:10 -04:00
yhirose a2f4e29a7b Add set_keep_alive_timeout 2020-09-25 17:57:33 -04:00
mi01 b8cf739d27 Add cctype header (#656) 2020-09-16 16:32:49 -04:00
yhirose aec2f9521d Updated documentation 2020-09-15 10:11:46 -04:00
yhirose 7b55ecdc59 Fixed #650 2020-09-12 16:11:14 -04:00
tmahring e9575bcb78 don't replace plus with space in headers (#649)
* don't replace plus with space in headers

* fixed forward handling with changed header parsing

* add test for boundaries containing plus chars
2020-09-10 20:27:01 -04:00
Jonas Minnberg 308aeb187b Undefined if2ip() also on Android since getifaddrs() does not exist. (#648)
Co-authored-by: Jonas Minnberg <jonas@minnberg.se>
2020-09-10 07:52:01 -04:00
Jodi the Tigger 05d18f2bc5 Fix -D build flags containing escaped quotes (#645)
Fixes #638

Done by removed unneeded quotes from cmake's add_compiler_definitions()
2020-09-08 21:35:50 -04:00
Ivan Fefer 3da4a0ac69 Add compression buffer size customization (#644)
* add compression buffer size customization and small brotli refactor

* allocat brotli buffer once

* add init to brotli decoder buffer
2020-09-08 12:18:14 -04:00
yhirose 9d12b3f20e Fixed warnings and refactoring 2020-09-03 20:33:30 -04:00
Omkar Jadhav 852a374748 Fix server crash caused due to regex complexity while matching headers. (#632)
* Fix parsing to parse query string with single space char.

When passed ' ' as a query string, the server crashes cause of illegal memory access done in httplib::detail::split. Have added checks to make sure the split function has a valid string with length > 0.

* Fix parsing to parse query string with single space char.

* Fix server crash caused due to regex complexity while matching headers.

While parsing content-type header in multipart form request the server crashes due to the exhaustion of max iterations performed while matching the input string with content-type regex.
Have removed the regex which might use backtracking while matching and replaced it with manual string processing. Have added tests as well.

* Remove magic number

Co-authored-by: Ivan Fefer <fefer.ivan@gmail.com>

Co-authored-by: yhirose <yhirose@users.noreply.github.com>
Co-authored-by: Ivan Fefer <fefer.ivan@gmail.com>
2020-09-03 13:17:52 -04:00
Ivan Fefer 3b5bab3308 Fix gzip_decompressor in case of one chunk being exactly equal to buffer size (#636)
* add larget chunks test

* revert test

* Fix gzip decoder in case of chunk being equal to buffer size

* add test
2020-09-03 12:20:02 -04:00
yhirose 69e75f4a67 Fix #635. HTTPS request stucked with proxy (#637) 2020-09-03 12:17:53 -04:00
Omkar Jadhav b0fd4befb1 Fix query parsing issues (#629)
* Fix parsing to parse query string with single space char.

When passed ' ' as a query string, the server crashes cause of illegal memory access done in httplib::detail::split. Have added checks to make sure the split function has a valid string with length > 0.

* Fix parsing to parse query string with single space char.
2020-08-28 09:43:28 -04:00
yhirose 3e80666a74 Fix #628 2020-08-27 19:45:28 -04:00
yhirose 3e4567bae8 Updated Repl.it examples 2020-08-27 14:16:35 -04:00
yhirose db075d8cf9 Added Repl.it examples 2020-08-27 14:06:57 -04:00
yhirose 16df0ef37e Code cleanup 2020-08-26 12:18:49 -04:00
Ivan Fefer f1a2ac5108 Avoid copying of content provider if possible (#627) 2020-08-26 08:56:51 -04:00
yhirose e5743b358d Updated README 2020-08-25 20:26:53 -04:00
Ivan Fefer 1184bbe4cb Fix typo in README (#625) 2020-08-25 06:38:16 -04:00
Felix Kästner 9dcffda7ae Fix cmake execute_process working directory (#622)
Correct the working directory of the execute_process call, to get the version from git tags.
The working directory should be the the directory of this libary. Otherwise, if you include httplib
as a git submodule and call add_subdirectory, the execute_process command will be run in a wrong
directory leading to error.
2020-08-24 17:23:01 -04:00
yhirose e5903635e2 Fix #619 2020-08-22 12:54:43 -04:00
yhirose 510b4eaaae Fix #613 2020-08-17 13:40:06 -04:00
yhirose d7e63b4316 Update README 2020-08-16 20:49:54 -04:00
yhirose e5dd410256 Added set_content_provider without content length 2020-08-15 05:53:49 -04:00
yhirose 951e46929e Fix #609 2020-08-14 15:01:09 -04:00
yhirose c8adac30f4 Fix #564 again 2020-08-13 08:07:25 -04:00
yhirose b2b4f7635f Fix #608 2020-08-11 12:11:05 -04:00
R Edgar 649b1d2172 Fix nullptr_t issue (#605)
Clang complains that `nullptr_t` should be `std::nullptr_t
2020-08-09 16:45:53 -04:00
yhirose dc5f9ba164 Better error handling on client (#601) 2020-08-08 20:50:24 -04:00
yhirose cf084e1db1 Fixed example build errors 2020-08-08 00:10:08 -04:00
KTGH e0e5898601 Overhaul FindBrotli to fix weird issues (#604)
Should get rid of the issue about not being able to create an ALIAS on
MinGW, as well as the "No REQUIRED_VARS" issue.

Fixes #603 (hopefully)
2020-08-06 07:08:29 -04:00
yhirose dfec2de5b5 Update README 2020-08-03 23:37:05 -04:00
yhirose 04002d57bd Added set_default_headers (Fix #600) 2020-08-03 22:05:37 -04:00
yhirose 38a7706c8b Removed old Keep-Alive functions 2020-08-03 22:04:40 -04:00
KTGH abaf875c42 Fix FindBrotli when no Brotli installed (#598)
Woops.

Ref https://github.com/yhirose/cpp-httplib/issues/582#issuecomment-667537002
2020-08-01 17:08:49 -04:00
PixlRainbow 5f76cb01c7 fix #592 -- add check for static-linked OpenSSL (#595) 2020-08-01 08:10:42 -04:00
yhirose ae54e833ea Code cleanup 2020-07-31 23:48:42 -04:00
yhirose 0dd3659de5 Updated README 2020-07-31 18:54:53 -04:00
KTGH 999f6abd2c Fix for Cmake on systems without Git (#594)
If you didn't have Git installed, execute_process never declared the
error variable, which led to it never parsing the header for a version.
So if Git wasn't installed, the version variable never got declared,
which caused errors.

This fixes that.
2020-07-31 13:46:12 -04:00
KTGH 48da75dd35 Fix FindBrotli for static libs (#593)
It wasn't linking them.
2020-07-31 13:45:21 -04:00
yhirose 4f84eeb298 Bearer Token auth support. Fix #484 2020-07-31 12:37:14 -04:00
yhirose a5b4cfadb9 Brotli suport on server. Fix #578 2020-07-31 10:23:57 -04:00
yhirose 3e906a9b8c Fix #591 2020-07-30 18:26:18 -04:00
yhirose 110393eadb Class name change 2020-07-30 17:27:07 -04:00
yhirose 98caa8c058 Updated README 2020-07-30 17:21:49 -04:00
yhirose ef65f09608 OpenSSL support on Visual Studio project 2020-07-30 17:19:12 -04:00
ThePiso e130cf3a3b The piso patch 1 (#590)
* Update httplib.h

When you disconnect and reconnect from the network, your network stack rewrites and updates /etc/resolv.conf accordingly. This configuration file is needed by the DNS resolver in the C library. The C library reads the DNS configuration from /etc/resolv.conf the first time, and caches it. It doesn't check, with every lookup, if the contents of /etc/resolv.conf have changed.
the solution is to add a call to res_init(), defined in resolv.h

* Update httplib.h
2020-07-30 10:11:02 -04:00
yhirose 8a348f17fd Resolved #192 2020-07-30 01:47:54 -04:00
yhirose 797d1f27e8 Fix #357 2020-07-29 23:12:05 -04:00
yhirose 6cde600922 Simplified simplecli.cc 2020-07-29 16:04:28 -04:00
KTGH 342c3ab293 Add Brotli Cmake support (#584)
Had to create a custom FindBrotli package, as not all users have
PkgConfig installed (which Brotli uses). This file gets installed
alongside httplibConfig.cmake for the end-users convenience.

Set BROTLI_USE_STATIC_LIBS to ON if you want to find the static libs
instead of default shared.

Adds the HTTPLIB_REQUIRE_BROTLI (default off) and HTTPLIB_USE_BROTLI_IF_AVAILABLE
(default on) options, which work in the same manner as the other optional/required
dependency options.

Moved the scattered linking and definitions to a single call.

Updated some documentation about the new options.

Improved the in-tree support by setting the HTTPLIB_IS_USING_XYZ
variables in the main CMakeLists (as well as having them in the
httplibConfig.cmake file).

Fixes #582
2020-07-28 17:04:29 -04:00
yhirose 6cce7951fc Fixed build error on non Windows environments with OpenSSL 2020-07-27 22:34:35 -04:00
yhirose e9058e5639 Fixed build error on Windows with OpenSSL 2020-07-27 22:32:31 -04:00
yhirose 2538a85486 Fix #581 2020-07-27 22:07:04 -04:00
KTGH 8c501022b3 Fix Cmake build for MinGW (#580)
Seems certain targets/hosts failed without these, as "_MSC_VER" is
undefined on MinGW, which caused the 'pragma comment(lib "libname")' to
fail.

Fixes #575
2020-07-26 12:27:03 -04:00
yhirose 9f5db2d1aa Updated README 2020-07-25 20:53:38 -04:00
yhirose 12540fe8d3 Brotli support on client 2020-07-25 20:44:02 -04:00
yhirose 29a06f852a Update README 2020-07-25 11:24:06 -04:00
yhirose 0e9cfd9f49 SSE client example 2020-07-25 11:20:57 -04:00
yhirose 90da199aba Disable compression when content-type is text/event-stream 2020-07-25 10:46:52 -04:00
yhirose 366d073490 Fixed build errors 2020-07-25 09:40:35 -04:00
yhirose 9ca1fa8b18 Fix #576 2020-07-25 09:37:57 -04:00
yhirose 15c4106a36 Added a unit test 2020-07-22 08:07:59 -04:00
yhirose 72ce293fed Removed set_timeout_sec and left set_base_dir 2020-07-20 17:15:16 -04:00
yhirose b476b55771 Fix #557 2020-07-20 17:04:50 -04:00
yhirose 0db9d21eb0 Fix #571 2020-07-19 18:40:55 -04:00
yhirose 5ddaf949d0 Fixed build error on Windows 2020-07-19 18:32:28 -04:00
yhirose 457a5a7501 Added compressor class 2020-07-19 17:44:45 -04:00
Daniel Ottiger 2ce080c2cb include <wincrypt.h> as otherwise CertOpenSystemStoreW can not be found (#568)
- visual studio 2019, version 16.6.3
- 64 bit target
2020-07-15 10:17:18 -04:00
yhirose 6ad25b6cf0 Fix #566 2020-07-12 20:41:02 -04:00
yhirose 3dff60eb16 Fix #565 2020-07-10 08:18:28 -04:00
yhirose 5038314b21 Fix #564 2020-07-08 13:56:06 -04:00
yhirose 6e1297cab0 Fix #150 (#556) 2020-07-07 18:55:46 -04:00
yhirose 7de743c962 Code format 2020-07-04 00:11:32 -04:00
Umiade 964fb5e5ca Fix: regex can't match when proxy was set to some web debugger(e.g. Fiddler) (#553)
Co-authored-by: Umiade <hanyuchao@corp.netease.com>
2020-07-03 07:17:04 -04:00
yhirose c4f3f9529b Fix #534 (#546) 2020-07-02 21:57:50 -04:00
Ilya Tsybulsky 887def9490 Fix logger never called when write_content_with_provider returns false (#549) 2020-07-01 17:09:43 -04:00
Ilya Tsybulsky bad6b2d22f fix-the-code-won't compile-with-sdl-checks-on (#550) 2020-07-01 17:09:19 -04:00
rundong08 3d47a51430 Fixed comparison of integers of different signs. (#544) 2020-06-29 21:19:56 -04:00
Ron Klein 0a2cb20223 fix documentation typo (#539)
fix "adress" --> "address"
2020-06-22 18:12:22 -04:00
yhirose ce502a73e1 Fix #531 2020-06-22 14:56:18 -04:00
yhirose 010e4479f4 Fixed test errors due to httpbin.org 2020-06-22 14:53:20 -04:00
Ahmet Karaahmetoğlu 70e193374a Fix #530 (#535) 2020-06-21 15:08:40 -04:00
yhirose 6b22409217 Code format 2020-06-18 23:33:07 -04:00
yhirose 969cccd52a Use && for parameter of boundary 2020-06-18 23:32:09 -04:00
yhirose 4a9c048bbc Fixed problem with set_socket_options 2020-06-18 23:31:41 -04:00
yhirose bfabbec8c7 Fix #528 2020-06-18 12:20:01 -04:00
yhirose 3e9c06cf79 Fixed #527 2020-06-18 12:18:43 -04:00
yhirose 29677540ae Removed unnecessary yeid. 2020-06-16 21:33:10 -04:00
yhirose 71fcfeb912 Removed unnecessary code 2020-06-16 21:21:03 -04:00
yhirose c7d22e451f Fixed timeout calculation bugs 2020-06-16 21:20:47 -04:00
yhirose 42f9f9107f Updated version in the User Agent string 2020-06-16 17:53:15 -04:00
yhirose 7cd25fbd63 Fix #499 2020-06-16 17:46:23 -04:00
yhirose 3dfb4ecac2 Fix #522 2020-06-15 23:09:46 -04:00
yhirose 144114f316 Fixed warnings on Windows 2020-06-13 23:20:21 -04:00
yhirose 0cc108d45e Updated ClientStop test 2020-06-13 23:18:59 -04:00
yhirose 0743d78c9b Fixed ClientStop test error. 2020-06-14 03:01:41 +00:00
yhirose e022b8b80b Refactoring to make it ready for KeepAlive connection on Client 2020-06-13 21:42:23 -04:00
yhirose 34282c79a9 Changd thread count in ClientStop 2020-06-13 01:45:08 -04:00
yhirose f80b6bd980 Added Endpoint structure in Client 2020-06-13 01:26:57 -04:00
yhirose 5af7222217 Fixed Client::stop problem with more than one requests on threads 2020-06-12 11:04:37 -04:00
KTGH ec00fe5d5b Use git to get full project version (#519)
This gets us the full version (aka with the patch version), instead of
just major and minor version from user agent.

Falls back to the user agent if it fails.
2020-06-10 18:23:45 -04:00
yhirose 24bdb736f0 Fix #506 2020-06-09 19:58:01 -04:00
yhirose d0dc200633 Code format 2020-06-09 19:17:58 -04:00
Nicolas Schneider 919a51091f replace usage of [[deprecated]] with CPPHTTPLIB_DEPRECATED (#513) 2020-06-03 13:12:31 -04:00
Nicolas Schneider 05e8b22989 fix cast warning (#512) 2020-06-03 07:44:16 -04:00
Nicolas Schneider 00dcd6b004 check for [[deprecated]] support via feature test macro (#511)
The [[deprecated]] specifier is a C++14 feature, so it might not always
be available on a C++11 compiler.
2020-06-03 07:43:56 -04:00
yhirose a42c6b99d3 Code cleanup 2020-06-02 19:06:16 -04:00
Wang Gao 812cb5bc3d fix get value function (#509) 2020-06-02 19:05:04 -04:00
yhirose aea60feb85 Code cleanup 2020-06-01 13:22:02 -04:00
yhirose b3a4045300 Fix #503 2020-05-28 19:19:18 -04:00
KTGH 5fcd8f7795 Add automatic versioning to Cmake (#505)
It pulls the version from the user-agent string in the header, so it
will not need to be manually adjusted. This version file is installed so
that you can check for a specific version with find_package(httplib)

Also added HTTPLIB_INCLUDE_DIR (root path without header name), and
HTTPLIB_LIBRARY (only if compiled).

Added HTTPLIB_VERSION, and HTTPLIB_FOUND (although it's recommended
to check if the target exists).

Updated CMakeLists documentation for all this.
2020-05-28 17:09:20 -04:00
yhirose d9fe3fa020 Fix #504 2020-05-28 17:08:05 -04:00
yhirose d8612ac02d Fixed build error... 2020-05-28 12:51:52 -04:00
yhirose 83ee6007da Fix #500 2020-05-28 12:06:11 -04:00
yhirose 3eaa769a2d Fix #481, #483, #487 2020-05-26 18:34:32 -04:00
yhirose b91540514d Fix #494 2020-05-25 10:50:24 -04:00
yhirose ab563ff52c Fix #496 2020-05-25 10:38:47 -04:00
KTGH 8cad160c0a Add HTTPLIB_COMPILE option to Cmake (#493)
This option (default OFF) automatically splits the file (with split.py)
into a header & source file, then compiles it as a shared/static
library. This requires an installed Python v3 executable to work.

This also adds a HTTPLIB_IS_COMPILED boolean that's available after a
finfind_package(httplib) call.

Note that the minimum Cmake version increased to 3.12 because of FindPython3.
Hopefully this isn't a problem, as it's already 3 years old at this point.
2020-05-24 16:07:44 -04:00
yhirose be7962f140 Fix #489 2020-05-24 15:18:34 -04:00
yhirose 509b8570b0 Updated README 2020-05-23 19:08:17 -04:00
yhirose 630f3465a9 Deprecated set_timeout_sec, added set_connection_timeout. 2020-05-23 18:00:24 -04:00
yhirose 9af1a4a08f Fixed problem with stop on windows 2020-05-23 13:49:49 -04:00
yhirose 0654e5dab4 Changed CPPHTTPLIB_IDLE_INTERVAL_USECOND to 0 2020-05-23 08:44:03 -04:00
yhirose 62e036f253 Fixed #488 again 2020-05-22 18:24:01 -04:00
yhirose f0adfb2e0c Fix #488 2020-05-22 12:18:07 -04:00
yhirose 139c816c16 Fixed the location of Client2 2020-05-19 21:02:58 -04:00
KTGH 9505a76491 Bringing Cmake back (#470)
* Revert "Removed CMakeLists.txt. (Fix #421)"

This reverts commit 8674555b88.

* Fail if cmake version too old

Previous behaviour is just a warning.

* Improve CMakeLists

Adds automatic dependency finding (if they were used).
Adds a way to require a specific version in the find_package(httplib) call.

You should link against the httplib::httplib IMPORTED target, which is
created automatically.

Add options to allow for strictly requiring OpenSSL/ZLIB

HTTPLIB_REQUIRE_OPENSSL & HTTPLIB_REQUIRE_ZLIB require the libs be found, or the build fails.

HTTPLIB_USE_OPENSSL_IF_AVAILABLE & HTTPLIB_USE_ZLIB_IF_AVAILABLE silently search for the libs.
If they aren't found, the build still continues, but shuts off support for those features.

* Add documentation to CMakeLists.txt

Has info on all the available options and what targets are produced.

Also put some things about installation on certain platforms.
2020-05-19 19:07:18 -04:00
yhirose 29fd136afd Code cleanup and format 2020-05-16 17:35:04 -04:00
yhirose f5598237b2 Fixed many redirects problem on Proxy 2020-05-16 17:34:03 -04:00
Daniel Ottiger 01058659ab make write timeout configurable (like the read timeout already is) (#477)
In case we want to send a lot of data,
and the receiver is slower than the sender.

This will first fill up the receivers queues and after this
eventually also the senders queues,
until the socket is temporarily unable to accept more data to send.

select_write is done with an timeout of zero,
which makes the select call used always return immediately:
(see http://man7.org/linux/man-pages/man2/select.2.html)

This means that every marginal unavailability will make it return false
for is_writable and therefore httplib will immediately abort the transfer.

Therefore make this values configurable in the same way
as the read timeout already is.

Set the default write timeout to 5 seconds,
the same default value used for the read timeout.
2020-05-16 17:31:46 -04:00
yhirose 66f698fab6 Fixed build errors with some examples 2020-05-16 00:50:52 -04:00
yhirose b9a9df4d73 Fixed problem with writing large data 2020-05-15 22:21:58 -04:00
yhirose 25aa3ca982 Added std::ostream os in DataSink. 2020-05-15 21:26:13 -04:00
yhirose 2d67211183 Added more unit tests for the simple interface 2020-05-14 18:25:18 -04:00
yhirose f4c5d94d74 Updated version in the User Agent string 2020-05-14 18:07:02 -04:00
yhirose 63a96aeb20 Improved Client2 interface 2020-05-14 12:51:34 -04:00
yhirose bbb83d12c1 Removed default parameter values in Client and SSLClient constructors 2020-05-14 08:51:32 -04:00
yhirose 2d4b42b70b Removed url 2020-05-14 01:43:06 -04:00
yhirose 1919d08f71 Added Client2 2020-05-14 01:36:56 -04:00
yhirose 824c02fcd3 Code cleanup 2020-05-14 01:08:36 -04:00
yhirose 2c0613f211 Fix #472 2020-05-13 21:48:14 -04:00
Saika Fatih be45ff1ff1 A detail about Gzip support (#475)
* Typos fixed

* README.md edited.libz should be linked for GZIP support.
2020-05-12 17:38:51 -04:00
Saika Fatih 803ebe1e20 Typos fixed (#474) 2020-05-12 13:18:58 -04:00
yhirose ba685dbe48 Fixed potential infinite loop with content receiver 2020-05-10 20:45:57 -04:00
yhirose 49c4c2f9c1 Fix #459 2020-05-10 20:39:16 -04:00
yhirose 58909f5917 Fix #466 2020-05-10 15:58:53 -04:00
yhirose 5982b5c360 Fix #471 2020-05-10 14:18:03 -04:00
yhirose eb1fe5b191 Fixed warnings 2020-05-09 15:08:49 -04:00
yhirose 5e01587ed6 Fixed problem created in the previous commit 2020-05-09 13:43:06 -04:00
yhirose 5935d9fa59 Commented out the unit test for digest auth. 2020-05-09 13:32:51 -04:00
PixlRainbow 5bb4c12c6b Fix #465 (#467)
update digest header username to use username parameter instead of "hello" test value
2020-05-09 08:29:08 -04:00
yhirose 85637844c9 Updated README 2020-05-07 21:13:45 -04:00
Daniel Ottiger d043b18097 keepalive: support multiple post using content provider (#461) 2020-05-07 08:31:14 -04:00
yhirose 31bb13abd2 Removed TravisCI badge from README 2020-05-04 22:19:17 -04:00
yhirose 8728db7477 Apply IPV6_V6ONLY only when socket is AF_INET6 2020-05-04 22:16:43 -04:00
yhirose 1c50ac3667 Stop using TravisCI anymore due to IPv6 issue 2020-05-04 22:14:03 -04:00
yhirose cf386f97fd Merge branch 'master' of https://github.com/yhirose/cpp-httplib 2020-05-04 22:13:17 -04:00
Daniel Ottiger b2203bb05a server: support dual-stack server socket (#450)
According to RFC 3493 the socket option IPV6_V6ONLY
should be off by default, see
https://tools.ietf.org/html/rfc3493#page-22 (chapter 5.3).

However this does not seem to be the case on all systems.
For instance on any Windows OS, the option is on by default.

Therefore clear this option in order to allow
an server socket which can support IPv6 and IPv4 at the same time.
2020-05-04 22:13:12 -04:00
yhirose f5b806d995 Added a test case for #396. 2020-05-04 21:26:14 -04:00
yhirose 3895210f19 Code format 2020-05-04 21:25:59 -04:00
yhirose d45250fd88 Appled HANDLE_EINTR to send and select system calls 2020-05-01 21:38:23 -04:00
yhirose 528cacdc0d Changed CPPHTTPLIB_THREAD_POOL_COUNT back to 8. (#454) 2020-05-01 21:23:02 -04:00
Matthew DeVore ed1b6afa10 Fix crash caused by header field regex complexity (#457) 2020-05-01 12:44:13 -04:00
yhirose 08fc7085e5 Fixed #456 2020-04-30 19:40:23 -04:00
yhirose 8333340e2c Chagned to use inline function instead of macro 2020-04-27 12:36:39 -04:00
yhirose 98a0887571 Merge branch 'je-ik-sketch-eintr-handling' 2020-04-27 12:33:21 -04:00
Jan Lukavsky b0a189e50e Sketch handling EINTR errors 2020-04-27 17:36:44 +02:00
yhirose 776b3ffbf9 Code format 2020-04-25 18:01:48 -04:00
yhirose a061b97677 Adjust appveyor.yml 2020-04-25 18:01:12 -04:00
yhirose d359e3a5f7 Renave queue_adjust to on_idle (#442) 2020-04-25 17:56:55 -04:00
evg82 5928e0af1a TaskQueue method to internal size adjust (#442)
I use a custom TaskQueue, with variable number of workers, adding workers on demand is an easy task when new connection arrive (in enqueue function) however i need another funtion to be called even (or better) went no new connections arrives to reduce workers count. I only added a new virtual method in TaskQueue class to allow custom class to adjust workers size over time. Even if this methods is called frequenlty custom class can keep a "last_update" counter to check if need to adjust worker count or any other internal task. Without this function i need an external thread to make this adjust task.
2020-04-25 17:55:20 -04:00
yhirose a5005789ff Fixed Visual Studio compiler warnings with x64 platform (Resolve #440 and #446) (#448) 2020-04-25 17:13:14 -04:00
yhirose fae30af47d Updated appveyor.yml 2020-04-25 15:48:19 -04:00
Hoa Thiên Vũ 2feea0c9ab Fixed error: ‘ULONG_MAX’ was not declared in this scope on line 1921 (#445)
* Fixed error:
ULONG_MAX is defined in the limits.h header file. Putting #include <climits>
```
httplib.h: In function ‘bool httplib::detail::read_content_chunked(httplib::Stream&, httplib::ContentReceiver)’:
httplib.h:1921:22: error: ‘ULONG_MAX’ was not declared in this scope
     if (chunk_len == ULONG_MAX) { return false; }
                      ^~~~~~~~~
httplib.h:1921:22: note: suggested alternative: ‘_SC_ULONG_MAX’
     if (chunk_len == ULONG_MAX) { return false; }
                      ^~~~~~~~~
                      _SC_ULONG_MAX
```

* Move #include <climits> to after #include <cassert>
2020-04-24 12:02:19 -04:00
yhirose a2e4af54b7 Fix #399 2020-04-23 23:09:04 -04:00
yhirose d0b123be26 Support remote_addr and remote_port REMOTE_PORT header in client Request (#433) 2020-04-23 22:12:12 -04:00
Matthew DeVore df138366e4 Fail to read a chunk if its length is >= ULONG_MAX (#444)
We cannot trivially support such large chunks, and the maximum value
std::strtoul can parse accurately is ULONG_MAX-1. Error out early if the
length is longer than that.
2020-04-23 10:59:15 -04:00
Matthew DeVore c49441ae64 Do not throw exceptions when parsing request chunks (#441)
detail::read_content_chunked was using std::stoul to parse the
hexadecimal chunk lengths for "Transfer-Encoding: chunked" requests.
This throws an exception if the string does not begin with any valid
digits. read_content_chunked is not called in the context of a try block
so this caused the process to terminate.

Rather than use exceptions, I opted for std::stroul, which is similar to
std::stoul but does not throw exceptions. Since malformed user input is
not particularly exceptional, and some projects are compiled without
exception support, this approach seems both more portable and more
correct.
2020-04-23 09:05:45 -04:00
yhirose e1506fa186 Code cleanup 2020-04-22 21:43:16 -04:00
yhirose ad9fd3bd93 Fix #436 2020-04-22 21:42:58 -04:00
yhirose 05e0253195 Fixed test error 2020-04-21 23:07:51 -04:00
yhirose da26b517a3 Added url::Get interface 2020-04-21 23:00:39 -04:00
yhirose 2b7a968468 Added a unit test for URL interface 2020-04-21 21:21:31 -04:00
yhirose 240cc85ccb Fixed regex problem for recirect location 2020-04-21 21:18:29 -04:00
yhirose 129e2f00b8 Removed unnecessary noexcept 2020-04-20 19:42:05 -04:00
Daniel Ottiger da746c6e67 SSLClient::set_ca_cert_store: mark as inline (#435) 2020-04-20 12:53:39 -04:00
yhirose 3451da940d Code format 2020-04-19 22:05:04 -04:00
yhirose 38a6b3e69f Fixed warning 2020-04-19 22:04:29 -04:00
yhirose d1037ee9fd Close #433 2020-04-18 16:38:15 -04:00
Daniel Ottiger 2ece5f116b Pass certs and keys from memory (#432)
* SSLServer: add constructor to pass ssl-certificates and key from memory

* SSLClient: add constructor to pass ssl-certificates and key from memory

* add TestCase for passing certificates from memory to SSLClient/SSLServer
2020-04-18 16:26:06 -04:00
yhirose c2b6e4ac04 Fix #431 2020-04-17 21:48:16 -04:00
yhirose 8674555b88 Removed CMakeLists.txt. (Fix #421) 2020-04-13 20:56:21 -04:00
yhirose 85327e19ae Fix #425 2020-04-12 15:33:08 -04:00
Alexandre Taillefer ed8efea98b Added support for DELETE request body (#418)
* Added support for DELETE request body

* Fixed DELETE request body test case typo

Co-authored-by: Alexandre Taillefer <alexandre.taillefer@pwc.ca>
2020-04-07 15:51:52 -04:00
Max 1ccddd1b0b SSL_shutdown() only if not already closed by remote (#413) 2020-04-06 10:30:21 -04:00
yhirose 992f3dc690 Code cleanup 2020-04-03 09:33:29 -04:00
yhirose 402d47e2cd Fix #407 2020-03-31 19:42:53 -04:00
yhirose 171fc2e353 Fix #403. Added more status codes based on MDN document 2020-03-26 20:50:40 -04:00
SoenkeHeeren ced4160d05 add http status code 201 to show the right status message in return headers (#402) 2020-03-26 12:20:32 -04:00
yhirose 5b51aa6851 Revert "Add 1000-concurrency-result report"
This reverts commit b0af78e340.
2020-03-24 17:30:32 -04:00
Igor [hyperxor] dc13cde820 Minor improvements in httplib classes (#395) 2020-03-23 06:54:13 -04:00
yhirose b0af78e340 Add 1000-concurrency-result report 2020-03-21 15:49:26 +00:00
Andrew Gasparovic 914c8860e8 Accept content by value to allow moving
Previously, calling set_content always resulted in 's' being copied. With this change, there will still be the same number of copies made (1) when doing `set_content(my_value, ...)`, but there will be no copies if a caller elects to do `set_content(std::move(value), ...)` or `set_content(some_function_that_returns_a_temporary(), ...)` instead.
2020-03-21 00:39:07 -04:00
yhirose dc6a72a0fd Fix #387 2020-03-17 18:03:52 -04:00
yhirose 685533ba50 Fixed warnings on Windows due to max/min macro 2020-03-16 13:58:09 -04:00
yhirose 6e46ccb37c Updated README 2020-03-15 12:05:12 -04:00
Aristo Chen ac18b70a0f Update calculation formula for progress percentage (#386) 2020-03-15 08:29:27 -04:00
yhirose e1acb949e7 Fix #382 2020-03-13 18:43:29 -04:00
yhirose ab96f49766 Fixed problem that line end char is missing on start messagein simple
server example
2020-03-13 16:36:33 -04:00
Oleg Vorobiov 7b3cea5317 Prevent an implicit capture of 'this' via '[=]' (#381) 2020-03-12 12:31:22 -04:00
yhirose 26deffe0c6 Not to send 'EXCEPTION_WHAT' header to client 2020-03-10 17:44:36 -04:00
Rafael Leira e07c5fec01 simplest way to catch handler exceptions 2020-03-10 17:44:36 -04:00
miketsts 6e473a7c5c Fix conversion to ‘int’ from ‘long int’ warning (#377)
Co-authored-by: Michael Tseitlin <michael.tseitlin@concertio.com>
2020-03-10 14:48:14 -04:00
yhirose c74129a1c2 Fix #372 (#374) 2020-03-09 23:59:00 -04:00
yhirose 18e750b4e7 Code cleanup 2020-03-09 19:47:28 -04:00
Matthew DeVore bf7700d192 Fix exception that occurs with libc++ regex engine (#368)
The regex that parses header lines potentially causes an unlimited
amount of backtracking, which can cause an exception in the libc++ regex
engine.

The exception that occurs looks like this and is identical to the
message of the exception fixed in
https://github.com/yhirose/cpp-httplib/pull/280:

	libc++abi.dylib: terminating with uncaught exception of type
	std::__1::regex_error: The complexity of an attempted match
	against a regular expression exceeded a pre-set level.

This commit eliminates the problematic backtracking.
2020-02-28 06:31:39 -05:00
yhirose 3da925d6fe Adjusted a unit test. 2020-02-25 08:29:29 -05:00
yhirose 319417f26d Updated README 2020-02-24 21:50:07 -05:00
yhirose 4c3b119dde Code format 2020-02-24 21:17:02 -05:00
yhirose 6de8684328 Merge branch 'hyperxor-remove_code_duplication_in_getting_query' 2020-02-24 21:15:01 -05:00
hyperxor ccc9a9b3f4 Remove code duplication in converting params to query 2020-02-25 02:30:34 +03:00
rymis f2bb9c45d6 Support for deflate compression (#360) 2020-02-24 11:48:00 -05:00
yhirose 9a663aa94e Added a unit test. 2020-02-21 11:48:47 -05:00
yhirose d0d744d520 Fixed compiler warning with Visual C++. Close #358 2020-02-20 17:30:04 -05:00
yhirose fce8e6fefd Merge pull request #356 from samhocevar/fix-shadow-declarations
Fix a few shadowed variable compilation warnings.
2020-02-17 09:01:47 -05:00
Sam Hocevar 180aa32ebf Fix a few shadowed variable compilation warnings. 2020-02-17 06:58:30 +01:00
yhirose fe01fa760b Update README 2020-02-14 22:07:39 -05:00
yhirose d61d63dd97 Added unit tests for SSLServer::bind_to_any_port 2020-02-14 21:59:37 -05:00
yhirose 3fe13ecc91 Updated README 2020-02-14 21:49:09 -05:00
yhirose 064cc6810e Fix #352 2020-02-13 17:40:06 -05:00
yhirose 464cc89b77 Fix #351 2020-02-12 22:35:52 -05:00
yhirose ca5a50d2c9 Fix #349 2020-02-11 00:26:15 -05:00
yhirose b251668522 Fixed build problem. 2020-02-03 22:45:50 -05:00
yhirose 9ee740fe8f Updated README 2020-02-02 20:57:29 -05:00
yhirose 851edaf77f Code format 2020-02-02 20:02:16 -05:00
yhirose 1a2a6e2b01 Updated README 2020-02-01 09:24:42 -05:00
yhirose ac7742bb32 Changed the order of parameters of set_mounting_point. 2020-02-01 09:18:24 -05:00
yhirose 82c11168c1 Merge pull request #342 from yhirose/remove_mount_point
Fix #341
2020-01-31 20:41:39 -05:00
yhirose 7c5197c86c Updated README 2020-01-31 20:40:33 -05:00
yhirose 8801e51138 Fix #341 2020-01-31 20:35:20 -05:00
yhirose 89740a808d Added a contributor to README 2020-01-28 06:34:10 -05:00
yhirose 2377a20e0b Merge pull request #340 from vitalyster/head_static
HEAD support for static file server
2020-01-28 06:32:32 -05:00
Vitaly Takmazov 5e43680486 HEAD support for static file server 2020-01-28 12:00:27 +03:00
yhirose 79df842cc5 Updated README 2020-01-26 18:34:50 -05:00
yhirose 48a4a5812e Merge pull request #338 from TheMaverickProgrammer/master
fixed missing import
2020-01-25 20:44:59 -05:00
TheMaverickProgrammer a7a6df49a4 Update split.py 2020-01-25 17:19:15 -06:00
TheMaverickProgrammer e932c7e1ec Update split.py
now works with legacy Python ( < 3.0)
2020-01-25 17:17:34 -06:00
yhirose f94c3f1dc9 Updated README 2020-01-21 23:45:21 -05:00
yhirose 4d545cb932 Fix #335 2020-01-21 09:09:05 -05:00
yhirose f5e19faae7 Updated README 2020-01-17 23:21:42 -05:00
yhirose 2e360f9dd6 Improved Stream interface 2020-01-16 23:28:11 -05:00
yhirose e5ca863de5 Merge pull request #334 from vvanelslande/add_503
Add status message: 503 Service Unavailable
2020-01-16 06:59:31 -05:00
Valentin Vanelslande 126f1d177e Add status message: 503 Service Unavailable 2020-01-16 00:59:24 -05:00
yhirose b557ac9328 Code format 2020-01-14 17:04:51 -05:00
yhirose f6db19959f Merge pull request #333 from stupedama/master
std::thread::hardware_concurrency
2020-01-14 17:04:17 -05:00
yhirose 6b4df41b30 Fix #330 2020-01-14 17:02:25 -05:00
yhirose 0d81e20129 Fix #332 2020-01-14 14:59:20 -05:00
Fredrik Fjeldvær 26cb83ed6c if std::thread::hardware_concurrency() is 0 use 2 threads. also -1 thread because we already have one thread from the main function 2020-01-14 17:48:51 +01:00
yhirose b4f808da74 Updated example/upload.cc 2020-01-10 18:38:38 -05:00
yhirose 7fbb8bb3fd Merge pull request #326 from vvanelslande/add_202
Add status message for 202 Accepted
2020-01-10 18:37:38 -05:00
Valentin Vanelslande db27812198 Add status message for 202 Accepted 2020-01-10 17:50:37 -05:00
yhirose 6fe6fd5dbe Fixed build errors 2020-01-10 09:40:26 -05:00
yhirose 0ee9660f3d Code format 2020-01-10 09:35:07 -05:00
yhirose a15d16a9de Moved Stream concrete classes into detail namespace 2020-01-10 09:33:14 -05:00
yhirose dfebfd9033 Merge pull request #325 from rockwotj/master
Properly implement BufferStream::read
2020-01-10 07:54:15 -05:00
Tyler Rockwood fcdaa24fc5 Properly implement BufferStream::read
The existing implementation did not advance the buffer at all.
2020-01-09 22:05:59 -06:00
yhirose 7f749740f7 Merge pull request #324 from hyperxor/use_cpp_11_for_delays
Use sleep_for and chrono for delays in tests
2020-01-09 17:08:44 -05:00
hyperxor 7b4494748e fix indentation 2020-01-10 00:17:40 +03:00
hyperxor 7e1c107029 Use sleep_for and chrono for delays in tests 2020-01-10 00:15:18 +03:00
yhirose db7ae0ec21 Merge pull request #322 from hyperxor/fix_response_move
Fix suppressed Response class objects moving
2020-01-08 22:09:17 -05:00
hyperxor b16905ec8b Fix Response move 2020-01-08 23:13:04 +03:00
yhirose 2f72845008 Fix #319 2020-01-07 23:40:11 -05:00
yhirose 68aeb4a06a Merge pull request #317 from hyperxor/tiny_improvement_in_data_sink
Tiny improvement in DataSink class
2020-01-07 07:03:38 -05:00
hyperxor 568fda62b4 Tiny improvement in DataSink class 2020-01-07 13:25:04 +03:00
yhirose 04957c0f08 Fix #315 2020-01-06 17:13:31 -05:00
yhirose 96e9ec0663 Improved DataSink interface 2020-01-05 23:59:54 -05:00
yhirose c58fca5dba Added more tests 2020-01-03 07:57:40 -05:00
yhirose d2fae4031c Fixed build errors 2020-01-03 01:36:59 -05:00
yhirose de844e67ef Proxy support for Keep-Alive requests 2020-01-03 01:24:07 -05:00
yhirose 6c0e021554 Fixed socket leak 2020-01-02 17:01:02 -05:00
yhirose e8d33a77e0 Updated README 2020-01-02 13:08:40 -05:00
yhirose aa630e3062 Fix #311 2020-01-02 13:02:05 -05:00
yhirose 301a419c02 Updated README 2019-12-26 19:50:51 -05:00
yhirose fcbcbd53bd Fix #306 2019-12-26 18:48:22 -05:00
yhirose 1bf616d653 Fix #303 2019-12-26 17:50:53 -05:00
yhirose ba7c7dc4a3 Added linux to .travis.yaml 2019-12-24 22:46:32 -05:00
yhirose aa543240db Added test for post request with query string and body 2019-12-24 21:55:29 -05:00
yhirose 5675cad407 Added proxy test in Makefile 2019-12-22 21:07:26 -05:00
yhirose 079d3605ea Changed to use docker-compose for squid 2019-12-22 19:11:02 -05:00
yhirose 2c6da365d9 Merge pull request #300 from vvanelslande/accpet
Change Accpet-Encoding to Accept-Encoding
2019-12-22 15:39:47 -05:00
yhirose 38adeaf02c Fixed problem with proxy support and added unit tests 2019-12-22 15:37:01 -05:00
Valentin Vanelslande b3814b2b80 Change Accpet-Encoding to Accept-Encoding 2019-12-22 13:02:20 -05:00
yhirose a444b612af V0.5.0 2019-12-22 12:52:08 -05:00
yhirose ed6d949f42 Fix #299 2019-12-22 12:50:25 -05:00
yhirose d28cd3f937 Code cleanup 2019-12-21 23:20:30 -05:00
yhirose 8cc3e6c434 Merge pull request #296 from yhirose/connect
CONNECT method support on client
2019-12-21 23:09:10 -05:00
yhirose 26fbc1b7c0 Merge pull request #297 from hyperxor/fix_progress_redundant_copying
Fix redundant Progress copy in Get methods
2019-12-21 07:43:45 -05:00
hyperxor 0dc653f45a Fix redundant Progress copy in Get methods 2019-12-21 10:57:06 +03:00
yhirose 7a58c0a430 Updated README regarding regex issue in g++ 4.8 and below 2019-12-20 23:16:05 -05:00
yhirose dabaa51a7d Updated README 2019-12-20 23:12:24 -05:00
yhirose a1cfc0f377 Fixed problem with redirect 2019-12-20 13:25:11 -05:00
yhirose eb4fcb5003 CONNECT method support on client 2019-12-20 06:59:59 -05:00
yhirose ae43c96984 Merge pull request #295 from yhirose/timeout
Fix #294
2019-12-18 17:57:23 -05:00
yhirose 9c81693801 Fix #294 2019-12-18 17:47:36 -05:00
yhirose 80202c9f62 Merge pull request #292 from Bendr0id/fix_socket_create_on_older_windows_systems
Adds workaround for socket creation on older Windows variants
2019-12-18 07:09:48 -05:00
Ben Gräf 094a6a614a Adds workaround for socket creation on older Windows variants
Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 and above the socket creation fails on older Windows Systems.
     
Let's try to create a socket the old way in this case.
     
Reference:
https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa
     
WSA_FLAG_NO_HANDLE_INHERIT:
This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with SP1, and later
2019-12-18 07:49:36 +01:00
313 changed files with 108685 additions and 32327 deletions
+2
View File
@@ -0,0 +1,2 @@
/test/www*/dir/*.html text eol=lf
/test/www*/dir/*.txt text eol=lf
+70
View File
@@ -0,0 +1,70 @@
# SPDX-FileCopyrightText: 2025 Andrea Pappacoda <andrea@pappacoda.it>
# SPDX-License-Identifier: MIT
name: abidiff
on: [push, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: true
defaults:
run:
shell: sh
jobs:
abi:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
container:
image: debian:testing
steps:
- name: Install dependencies
run: apt -y --update install --no-install-recommends
abigail-tools
ca-certificates
g++
git
libbrotli-dev
libssl-dev
libzstd-dev
meson
pkg-config
python3
zlib1g-dev
- uses: actions/checkout@v4
with:
path: current
- uses: actions/checkout@v4
with:
path: previous
fetch-depth: 0
- name: Checkout previous
working-directory: previous
run: |
git switch master
git describe --tags --abbrev=0 master | xargs git checkout
- name: Build current
working-directory: current
run: |
meson setup --buildtype=debug -Dcpp-httplib_compile=true build
ninja -C build
- name: Build previous
working-directory: previous
run: |
meson setup --buildtype=debug -Dcpp-httplib_compile=true build
ninja -C build
- name: Run abidiff
run: abidiff
--headers-dir1 previous/build
--headers-dir2 current/build
previous/build/libcpp-httplib.so
current/build/libcpp-httplib.so
+32
View File
@@ -0,0 +1,32 @@
name: CIFuzz
on: [pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: true
jobs:
Fuzzing:
runs-on: ubuntu-latest
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'cpp-httplib'
dry-run: false
language: c++
- name: Run Fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'cpp-httplib'
fuzz-seconds: 600
dry-run: false
language: c++
- name: Upload Crash
uses: actions/upload-artifact@v4
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts
+34
View File
@@ -0,0 +1,34 @@
name: docs
on:
push:
branches: [master]
paths:
- 'docs-src/**'
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install docs-gen
run: cargo install docs-gen
- name: Build
run: docs-gen build docs-src docs
- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v3
with:
path: docs
- id: deployment
uses: actions/deploy-pages@v4
+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 }}
+36
View File
@@ -0,0 +1,36 @@
name: 32-bit Build Test
on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: true
jobs:
test-win32:
name: Windows 32-bit (MSVC x86)
runs-on: windows-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Build (Win32)
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
cl /std:c++14 /EHsc /W4 /WX /c /Fo:NUL test\test_32bit_build.cpp
test-arm32:
name: ARM 32-bit (cross-compile)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Install cross compiler
run: sudo apt-get update && sudo apt-get install -y g++-arm-linux-gnueabihf
- name: Build (ARM 32-bit)
run: arm-linux-gnueabihf-g++ -std=c++11 -Wall -Wextra -Wno-psabi -Werror -c -o /dev/null test/test_32bit_build.cpp
+246 -9
View File
@@ -1,17 +1,254 @@
name: test
on: [push, pull_request]
on:
push:
pull_request:
workflow_dispatch:
inputs:
gtest_filter:
description: 'Google Test filter'
test_linux:
description: 'Test on Linux'
type: boolean
default: true
test_macos:
description: 'Test on MacOS'
type: boolean
default: true
test_windows:
description: 'Test on Windows'
type: boolean
default: true
concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: true
env:
GTEST_FILTER: ${{ github.event.inputs.gtest_filter || '*' }}
jobs:
build:
runs-on: ${{ matrix.os }}
style-check:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
continue-on-error: true
steps:
- name: checkout
uses: actions/checkout@v4
- name: run style check
run: |
clang-format --version
cd test && make style_check
build-and-test-on-32bit:
runs-on: ubuntu-latest
if: >
(github.event_name == 'push') ||
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
strategy:
matrix:
os: [macOS-latest, ubuntu-latest]
config:
- arch_flags: -m32
arch_suffix: :i386
name: (32-bit)
steps:
- name: checkout
uses: actions/checkout@v1
- name: make
run: cd test && make
- name: checkout
uses: actions/checkout@v4
- name: install libraries
run: |
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install -y libc6-dev${{ matrix.config.arch_suffix }} libstdc++-13-dev${{ matrix.config.arch_suffix }} \
libssl-dev${{ matrix.config.arch_suffix }} libcurl4-openssl-dev${{ matrix.config.arch_suffix }} \
zlib1g-dev${{ matrix.config.arch_suffix }} libbrotli-dev${{ matrix.config.arch_suffix }} \
libzstd-dev${{ matrix.config.arch_suffix }}
- name: build and run tests
run: cd test && make test EXTRA_CXXFLAGS="${{ matrix.config.arch_flags }}"
ubuntu:
runs-on: ubuntu-latest
if: >
(github.event_name == 'push') ||
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
strategy:
matrix:
tls_backend: [openssl, mbedtls, wolfssl]
name: ubuntu (${{ matrix.tls_backend }})
steps:
- name: checkout
uses: actions/checkout@v4
- name: install common libraries
run: |
sudo apt-get update
sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev libbrotli-dev libzstd-dev
- name: install OpenSSL
if: matrix.tls_backend == 'openssl'
run: sudo apt-get install -y libssl-dev
- name: install Mbed TLS
if: matrix.tls_backend == 'mbedtls'
run: sudo apt-get install -y libmbedtls-dev
- name: install wolfSSL
if: matrix.tls_backend == 'wolfssl'
run: sudo apt-get install -y libwolfssl-dev
- name: build and run tests (OpenSSL)
if: matrix.tls_backend == 'openssl'
run: cd test && make test_split && make test_openssl_parallel
env:
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
- name: build and run tests (Mbed TLS)
if: matrix.tls_backend == 'mbedtls'
run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
- name: build and run tests (wolfSSL)
if: matrix.tls_backend == 'wolfssl'
run: cd test && make test_split_wolfssl && make test_wolfssl_parallel
- name: run fuzz test target
if: matrix.tls_backend == 'openssl'
run: cd test && make fuzz_test
- name: build and run WebSocket heartbeat test
if: matrix.tls_backend == 'openssl'
run: cd test && make test_websocket_heartbeat && ./test_websocket_heartbeat
- name: build and run ThreadPool test
run: cd test && make test_thread_pool && ./test_thread_pool
macos:
runs-on: macos-latest
if: >
(github.event_name == 'push') ||
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true')
strategy:
matrix:
tls_backend: [openssl, mbedtls, wolfssl]
name: macos (${{ matrix.tls_backend }})
steps:
- name: checkout
uses: actions/checkout@v4
- name: install Mbed TLS
if: matrix.tls_backend == 'mbedtls'
run: brew install mbedtls@3
- name: install wolfSSL
if: matrix.tls_backend == 'wolfssl'
run: brew install wolfssl
- name: build and run tests (OpenSSL)
if: matrix.tls_backend == 'openssl'
run: cd test && make test_split && make test_openssl_parallel
env:
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
- name: build and run tests (Mbed TLS)
if: matrix.tls_backend == 'mbedtls'
run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
- name: build and run tests (wolfSSL)
if: matrix.tls_backend == 'wolfssl'
run: cd test && make test_split_wolfssl && make test_wolfssl_parallel
- name: run fuzz test target
if: matrix.tls_backend == 'openssl'
run: cd test && make fuzz_test
- name: build and run WebSocket heartbeat test
if: matrix.tls_backend == 'openssl'
run: cd test && make test_websocket_heartbeat && ./test_websocket_heartbeat
- name: build and run ThreadPool test
run: cd test && make test_thread_pool && ./test_thread_pool
windows:
runs-on: windows-latest
if: >
(github.event_name == 'push') ||
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_windows == 'true')
strategy:
matrix:
config:
- with_ssl: false
compiled: false
run_tests: true
name: without SSL
- with_ssl: true
compiled: false
run_tests: true
name: with SSL
- with_ssl: false
compiled: true
run_tests: false
name: compiled
name: windows ${{ matrix.config.name }}
steps:
- name: Prepare Git for Checkout on Windows
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- name: Checkout
uses: actions/checkout@v4
- name: Export GitHub Actions cache environment variables
uses: actions/github-script@v7
with:
script: |
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
- name: Setup msbuild on windows
uses: microsoft/setup-msbuild@v2
- name: Cache vcpkg packages
id: vcpkg-cache
uses: actions/cache@v4
with:
path: C:/vcpkg/installed
key: vcpkg-installed-windows-gtest-curl-zlib-brotli-zstd
- name: Install vcpkg dependencies
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
run: vcpkg install gtest curl zlib brotli zstd
- name: Install OpenSSL
if: ${{ matrix.config.with_ssl }}
run: choco install openssl
- name: Configure CMake ${{ matrix.config.name }}
run: >
cmake -B build -S .
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake
-DHTTPLIB_TEST=ON
-DHTTPLIB_COMPILE=${{ matrix.config.compiled && 'ON' || 'OFF' }}
-DHTTPLIB_USE_OPENSSL_IF_AVAILABLE=${{ matrix.config.with_ssl && 'ON' || 'OFF' }}
-DHTTPLIB_REQUIRE_ZLIB=ON
-DHTTPLIB_REQUIRE_BROTLI=ON
-DHTTPLIB_REQUIRE_ZSTD=ON
-DHTTPLIB_REQUIRE_OPENSSL=${{ matrix.config.with_ssl && 'ON' || 'OFF' }}
- name: Build ${{ matrix.config.name }}
run: cmake --build build --config Release -- /v:m /clp:ShowCommandLine
- name: Run tests ${{ matrix.config.name }}
if: ${{ matrix.config.run_tests }}
shell: pwsh
working-directory: build/test
run: |
$shards = 4
$procs = @()
for ($i = 0; $i -lt $shards; $i++) {
$log = "shard_${i}.log"
$procs += Start-Process -FilePath ./Release/httplib-test.exe `
-ArgumentList "--gtest_color=yes","--gtest_filter=${{ github.event.inputs.gtest_filter || '*' }}" `
-NoNewWindow -PassThru -RedirectStandardOutput $log -RedirectStandardError "${log}.err" `
-Environment @{ GTEST_TOTAL_SHARDS="$shards"; GTEST_SHARD_INDEX="$i" }
}
$procs | Wait-Process
$failed = $false
for ($i = 0; $i -lt $shards; $i++) {
$log = "shard_${i}.log"
if (Select-String -Path $log -Pattern "\[ PASSED \]" -Quiet) {
$passed = (Select-String -Path $log -Pattern "\[ PASSED \]").Line
Write-Host "Shard ${i}: $passed"
} else {
Write-Host "=== Shard $i FAILED ==="
Get-Content $log
if (Test-Path "${log}.err") { Get-Content "${log}.err" }
$failed = $true
}
}
if ($failed) { exit 1 }
Write-Host "All shards passed."
env:
VCPKG_ROOT: "C:/vcpkg"
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
+79
View File
@@ -0,0 +1,79 @@
name: benchmark
on:
push:
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: true
jobs:
ubuntu:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: checkout
uses: actions/checkout@v4
- name: build and run
run: cd test && make test_benchmark && ./test_benchmark
macos:
runs-on: macos-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: checkout
uses: actions/checkout@v4
- name: build and run
run: cd test && make test_benchmark && ./test_benchmark
windows:
runs-on: windows-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Prepare Git for Checkout on Windows
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- name: checkout
uses: actions/checkout@v4
- name: Export GitHub Actions cache environment variables
uses: actions/github-script@v7
with:
script: |
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
- name: Cache vcpkg packages
id: vcpkg-cache
uses: actions/cache@v4
with:
path: C:/vcpkg/installed
key: vcpkg-installed-windows-gtest
- name: Install vcpkg dependencies
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
run: vcpkg install gtest
- name: Configure and build
shell: pwsh
run: |
$cmake_content = @"
cmake_minimum_required(VERSION 3.14)
project(httplib-benchmark CXX)
find_package(GTest REQUIRED)
add_executable(httplib-benchmark test/test_benchmark.cc)
target_include_directories(httplib-benchmark PRIVATE .)
target_link_libraries(httplib-benchmark PRIVATE GTest::gtest_main)
target_compile_options(httplib-benchmark PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
"@
New-Item -ItemType Directory -Force -Path build_bench/test | Out-Null
Set-Content -Path build_bench/CMakeLists.txt -Value $cmake_content
Copy-Item -Path httplib.h -Destination build_bench/
Copy-Item -Path test/test_benchmark.cc -Destination build_bench/test/
cmake -B build_bench/build -S build_bench `
-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"
cmake --build build_bench/build --config Release
- name: Run with retry
run: ctest --output-on-failure --test-dir build_bench/build -C Release --repeat until-pass:5
env:
VCPKG_ROOT: "C:/vcpkg"
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
+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 test_split EXTRA_CXXFLAGS="-fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS" && make test_openssl_parallel EXTRA_CXXFLAGS="-fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS"
+62
View File
@@ -0,0 +1,62 @@
name: test_offline
on:
push:
pull_request:
workflow_dispatch:
inputs:
test_linux:
description: 'Test on Linux'
type: boolean
default: true
concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: true
env:
GTEST_FILTER: "-*.*_Online"
jobs:
ubuntu:
runs-on: ubuntu-latest
if: >
(github.event_name == 'push') ||
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
strategy:
matrix:
tls_backend: [openssl, no-tls]
name: ubuntu (${{ matrix.tls_backend }})
steps:
- name: checkout
uses: actions/checkout@v4
- name: install common libraries
run: |
sudo apt-get update
sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev libbrotli-dev libzstd-dev
- name: install OpenSSL
if: matrix.tls_backend == 'openssl'
run: sudo apt-get install -y libssl-dev
- name: disable network
run: |
sudo iptables -A OUTPUT -o lo -j ACCEPT
sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A OUTPUT -j REJECT
sudo ip6tables -A OUTPUT -o lo -j ACCEPT
sudo ip6tables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo ip6tables -A OUTPUT -j REJECT
- name: build and run tests (OpenSSL)
if: matrix.tls_backend == 'openssl'
run: cd test && make test_split && make test_openssl_parallel
env:
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
- name: build and run tests (No TLS)
if: matrix.tls_backend == 'no-tls'
run: cd test && make test_no_tls_parallel
- name: restore network
if: always()
run: |
sudo iptables -F OUTPUT
sudo ip6tables -F OUTPUT
+37
View File
@@ -0,0 +1,37 @@
name: Proxy Test
on: [push, pull_request]
jobs:
test-proxy:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
strategy:
matrix:
tls_backend: [openssl, mbedtls]
name: proxy (${{ matrix.tls_backend }})
steps:
- uses: actions/checkout@v4
- name: Install common dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential zlib1g-dev libcurl4-openssl-dev libbrotli-dev libzstd-dev netcat-openbsd
- name: Install OpenSSL
if: matrix.tls_backend == 'openssl'
run: sudo apt-get install -y libssl-dev
- name: Install Mbed TLS
if: matrix.tls_backend == 'mbedtls'
run: sudo apt-get install -y libmbedtls-dev
- name: Run proxy tests (OpenSSL)
if: matrix.tls_backend == 'openssl'
run: cd test && make proxy
env:
COMPOSE_FILE: docker-compose.yml:docker-compose.ci.yml
- name: Run proxy tests (Mbed TLS)
if: matrix.tls_backend == 'mbedtls'
run: cd test && make proxy_mbedtls
env:
COMPOSE_FILE: docker-compose.yml:docker-compose.ci.yml
+52 -1
View File
@@ -1,21 +1,70 @@
tags
AGENTS.md
docs-src/pages/AGENTS.md
plans/
work/
# 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/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/wsecho
!example/wsecho.*
example/*.pem
test/httplib.cc
test/httplib.h
test/test
test/test_mbedtls
test/test_wolfssl
test/test_no_tls
test/server_fuzzer
test/test_proxy
test/test_proxy_mbedtls
test/test_proxy_wolfssl
test/test_split
test/test_split_mbedtls
test/test_split_wolfssl
test/test_split_no_tls
test/test_websocket_heartbeat
test/test_thread_pool
test/test_benchmark
test/test.xcodeproj/xcuser*
test/test.xcodeproj/*/xcuser*
test/*.o
test/*.pem
test/*.srl
test/*.log
test/_build_*
benchmark/server*
*.swp
build/
Debug
Release
*.vcxproj.user
@@ -25,5 +74,7 @@ Release
*.db
ipch
*.dSYM
*.pyc
.*
!/.travis.yml
!/.gitattributes
!/.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] # インプレースで修正
-13
View File
@@ -1,13 +0,0 @@
# Environment
language: cpp
os:
- osx
# Compiler selection
compiler:
- clang
# Build/test steps
script:
- cd ${TRAVIS_BUILD_DIR}/test
- make all
+460 -19
View File
@@ -1,29 +1,470 @@
cmake_minimum_required(VERSION 3.7.0)
project(httplib)
#[[
Build options:
* Standard BUILD_SHARED_LIBS is supported and sets HTTPLIB_SHARED default value.
* HTTPLIB_USE_OPENSSL_IF_AVAILABLE (default on)
* HTTPLIB_USE_WOLFSSL_IF_AVAILABLE (default off)
* HTTPLIB_USE_MBEDTLS_IF_AVAILABLE (default off)
* HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on)
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
* HTTPLIB_USE_ZSTD_IF_AVAILABLE (default on)
* HTTPLIB_BUILD_MODULES (default off)
* HTTPLIB_REQUIRE_OPENSSL (default off)
* HTTPLIB_REQUIRE_WOLFSSL (default off)
* HTTPLIB_REQUIRE_MBEDTLS (default off)
* HTTPLIB_REQUIRE_ZLIB (default off)
* HTTPLIB_REQUIRE_BROTLI (default off)
* HTTPLIB_REQUIRE_ZSTD (default off)
* HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES (default off)
* HTTPLIB_USE_NON_BLOCKING_GETADDRINFO (default on)
* HTTPLIB_COMPILE (default off)
* HTTPLIB_INSTALL (default on)
* HTTPLIB_SHARED (default off) builds as a shared library (if HTTPLIB_COMPILE is ON)
* HTTPLIB_TEST (default off)
* BROTLI_USE_STATIC_LIBS - tells Cmake to use the static Brotli libs (only works if you have them installed).
* OPENSSL_USE_STATIC_LIBS - tells Cmake to use the static OpenSSL libs (only works if you have them installed).
set(CMAKE_CXX_STANDARD 11)
-------------------------------------------------------------------------------
# Include
After installation with Cmake, a find_package(httplib COMPONENTS OpenSSL wolfssl MbedTLS ZLIB Brotli zstd) is available.
This creates a httplib::httplib target (if found and if listed components are supported).
It can be linked like so:
target_link_libraries(your_exe httplib::httplib)
The following will build & install for later use.
Linux/macOS:
mkdir -p build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
sudo cmake --build . --target install
Windows:
mkdir build
cd build
cmake ..
runas /user:Administrator "cmake --build . --config Release --target install"
-------------------------------------------------------------------------------
These variables are available after you run find_package(httplib)
* HTTPLIB_HEADER_PATH - this is the full path to the installed header (e.g. /usr/include/httplib.h).
* HTTPLIB_IS_USING_OPENSSL - a bool for if OpenSSL support is enabled.
* HTTPLIB_IS_USING_WOLFSSL - a bool for if wolfSSL support is enabled.
* HTTPLIB_IS_USING_MBEDTLS - a bool for if MbedTLS support is enabled.
* HTTPLIB_IS_USING_ZLIB - a bool for if ZLIB support is enabled.
* HTTPLIB_IS_USING_BROTLI - a bool for if Brotli support is enabled.
* HTTPLIB_IS_USING_ZSTD - a bool for if ZSTD support is enabled.
* HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES - a bool for if support of loading system certs from the Apple Keychain is enabled.
* HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO - a bool for if nonblocking getaddrinfo is enabled.
* HTTPLIB_IS_COMPILED - a bool for if the library is compiled, or otherwise header-only.
* HTTPLIB_INCLUDE_DIR - the root path to httplib's header (e.g. /usr/include).
* HTTPLIB_LIBRARY - the full path to the library if compiled (e.g. /usr/lib/libhttplib.so).
* httplib_VERSION or HTTPLIB_VERSION - the project's version string.
* HTTPLIB_FOUND - a bool for if the target was found.
Want to use precompiled headers (Cmake feature since v3.16)?
It's as simple as doing the following (before linking):
target_precompile_headers(httplib::httplib INTERFACE "${HTTPLIB_HEADER_PATH}")
-------------------------------------------------------------------------------
ARCH_INDEPENDENT option of write_basic_package_version_file() requires Cmake v3.14
]]
cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR)
# Get the CPPHTTPLIB_VERSION value and use it as a version
# This gets the string with the CPPHTTPLIB_VERSION value from the header.
# This is so the maintainer doesn't actually need to update this manually.
file(STRINGS httplib.h _raw_version_string REGEX "CPPHTTPLIB_VERSION \"([0-9]+\\.[0-9]+\\.[0-9]+)\"")
# Extracts just the version string itself from the whole string contained in _raw_version_string
# since _raw_version_string would contain the entire line of code where it found the version string
string(REGEX MATCH "([0-9]+\\.?)+" _httplib_version "${_raw_version_string}")
project(httplib
VERSION ${_httplib_version}
LANGUAGES CXX
DESCRIPTION "A C++ header-only HTTP/HTTPS server and client library."
HOMEPAGE_URL "https://github.com/yhirose/cpp-httplib"
)
# Change as needed to set an OpenSSL minimum version.
# This is used in the installed Cmake config file.
set(_HTTPLIB_OPENSSL_MIN_VER "3.0.0")
# Lets you disable C++ exception during CMake configure time.
# The value is used in the install CMake config file.
option(HTTPLIB_NO_EXCEPTIONS "Disable the use of C++ exceptions" OFF)
# Allow for a build to require OpenSSL to pass, instead of just being optional
option(HTTPLIB_REQUIRE_OPENSSL "Requires OpenSSL to be found & linked, or fails build." OFF)
option(HTTPLIB_REQUIRE_WOLFSSL "Requires wolfSSL to be found & linked, or fails build." OFF)
option(HTTPLIB_REQUIRE_MBEDTLS "Requires MbedTLS to be found & linked, or fails build." OFF)
option(HTTPLIB_REQUIRE_ZLIB "Requires ZLIB to be found & linked, or fails build." OFF)
# Allow for a build to casually enable OpenSSL/ZLIB support, but silently continue if not found.
# Make these options so their automatic use can be specifically disabled (as needed)
option(HTTPLIB_USE_OPENSSL_IF_AVAILABLE "Uses OpenSSL (if available) to enable HTTPS support." ON)
option(HTTPLIB_USE_WOLFSSL_IF_AVAILABLE "Uses wolfSSL (if available) to enable HTTPS support." OFF)
option(HTTPLIB_USE_MBEDTLS_IF_AVAILABLE "Uses MbedTLS (if available) to enable HTTPS support." OFF)
option(HTTPLIB_USE_ZLIB_IF_AVAILABLE "Uses ZLIB (if available) to enable Zlib compression support." ON)
# Lets you compile the program as a regular library instead of header-only
option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a compilable header & source file (requires Python v3)." OFF)
# Lets you disable the installation (useful when fetched from another CMake project)
option(HTTPLIB_INSTALL "Enables the installation target" ON)
option(HTTPLIB_TEST "Enables testing and builds tests" OFF)
option(HTTPLIB_REQUIRE_BROTLI "Requires Brotli to be found & linked, or fails build." OFF)
option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON)
option(HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES "Disable loading system certs from the Apple Keychain on macOS." OFF)
option(HTTPLIB_USE_NON_BLOCKING_GETADDRINFO "Enables the non-blocking alternatives for getaddrinfo." ON)
option(HTTPLIB_REQUIRE_ZSTD "Requires ZSTD to be found & linked, or fails build." OFF)
option(HTTPLIB_USE_ZSTD_IF_AVAILABLE "Uses ZSTD (if available) to enable zstd support." ON)
# C++20 modules support requires CMake 3.28 or later
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.28")
option(HTTPLIB_BUILD_MODULES "Build httplib modules (requires HTTPLIB_COMPILE to be ON)." OFF)
else()
set(HTTPLIB_BUILD_MODULES OFF CACHE INTERNAL "Build httplib modules disabled (requires CMake 3.28+)" FORCE)
if(DEFINED CACHE{HTTPLIB_BUILD_MODULES} AND HTTPLIB_BUILD_MODULES)
message(WARNING "HTTPLIB_BUILD_MODULES requires CMake 3.28 or later. Current version is ${CMAKE_VERSION}. Modules support has been disabled.")
endif()
endif()
# Incompatibility between TLS libraries
set(TLS_LIBRARIES_USED_TMP 0)
foreach(tls_library OPENSSL WOLFSSL MBEDTLS)
set(TLS_REQUIRED ${HTTPLIB_REQUIRE_${tls_library}})
set(TLS_IF_AVAILABLE ${HTTPLIB_USE_${tls_library}_IF_AVAILABLE})
if(TLS_REQUIRED OR TLS_IF_AVAILABLE)
math(EXPR TLS_LIBRARIES_USED_TMP "${TLS_LIBRARIES_USED_TMP} + 1")
endif()
endforeach()
if(TLS_LIBRARIES_USED_TMP GREATER 1)
message(FATAL_ERROR "TLS libraries are mutually exclusive.")
endif()
# Defaults to static library but respects standard BUILD_SHARED_LIBS if set
include(CMakeDependentOption)
cmake_dependent_option(HTTPLIB_SHARED "Build the library as a shared library instead of static. Has no effect if using header-only."
"${BUILD_SHARED_LIBS}" HTTPLIB_COMPILE OFF
)
if(HTTPLIB_SHARED)
set(HTTPLIB_LIB_TYPE SHARED)
if(WIN32)
# Necessary for Windows if building shared libs
# See https://stackoverflow.com/a/40743080
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()
else()
set(HTTPLIB_LIB_TYPE STATIC)
endif()
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
if(CMAKE_SYSTEM_VERSION)
if(${CMAKE_SYSTEM_VERSION} VERSION_LESS "10.0.0")
message(WARNING "Windows ${CMAKE_SYSTEM_VERSION} or lower is not supported. Please use Windows 10 or later.")
endif()
else()
set(CMAKE_SYSTEM_VERSION "10.0.19041.0")
message(WARNING "The target is Windows but CMAKE_SYSTEM_VERSION is not set, the default system version is set to Windows 10.")
endif()
endif()
# Set some variables that are used in-tree and while building based on our options
set(HTTPLIB_IS_COMPILED ${HTTPLIB_COMPILE})
set(HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES TRUE)
if(HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES)
set(HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES FALSE)
endif()
set(HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO ${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO})
# Threads needed for <thread> on some systems, and for <pthread.h> on Linux
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads REQUIRED)
# Since Cmake v3.11, Crypto & SSL became optional when not specified as COMPONENTS.
if(HTTPLIB_REQUIRE_OPENSSL)
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL REQUIRED)
set(HTTPLIB_IS_USING_OPENSSL TRUE)
elseif(HTTPLIB_USE_OPENSSL_IF_AVAILABLE)
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL QUIET)
# Avoid a rare circumstance of not finding all components but the end-user did their
# own call for OpenSSL, which might trick us into thinking we'd otherwise have what we wanted
if (TARGET OpenSSL::SSL AND TARGET OpenSSL::Crypto)
set(HTTPLIB_IS_USING_OPENSSL ${OPENSSL_FOUND})
else()
set(HTTPLIB_IS_USING_OPENSSL FALSE)
endif()
endif()
if(HTTPLIB_REQUIRE_WOLFSSL)
find_package(wolfssl REQUIRED)
set(HTTPLIB_IS_USING_WOLFSSL TRUE)
elseif(HTTPLIB_USE_WOLFSSL_IF_AVAILABLE)
find_package(wolfssl QUIET)
set(HTTPLIB_IS_USING_WOLFSSL ${wolfssl_FOUND})
endif()
if(HTTPLIB_REQUIRE_MBEDTLS)
find_package(MbedTLS REQUIRED)
set(HTTPLIB_IS_USING_MBEDTLS TRUE)
elseif(HTTPLIB_USE_MBEDTLS_IF_AVAILABLE)
find_package(MbedTLS QUIET)
set(HTTPLIB_IS_USING_MBEDTLS ${MbedTLS_FOUND})
endif()
if(HTTPLIB_REQUIRE_ZLIB)
find_package(ZLIB REQUIRED)
set(HTTPLIB_IS_USING_ZLIB TRUE)
elseif(HTTPLIB_USE_ZLIB_IF_AVAILABLE)
find_package(ZLIB QUIET)
# FindZLIB doesn't have a ZLIB_FOUND variable, so check the target.
if(TARGET ZLIB::ZLIB)
set(HTTPLIB_IS_USING_ZLIB TRUE)
endif()
endif()
# Adds our cmake folder to the search path for find_package
# This is so we can use our custom FindBrotli.cmake
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
if(HTTPLIB_REQUIRE_BROTLI)
find_package(Brotli COMPONENTS encoder decoder common REQUIRED)
set(HTTPLIB_IS_USING_BROTLI TRUE)
elseif(HTTPLIB_USE_BROTLI_IF_AVAILABLE)
find_package(Brotli COMPONENTS encoder decoder common QUIET)
set(HTTPLIB_IS_USING_BROTLI ${Brotli_FOUND})
endif()
# NOTE:
# zstd < 1.5.6 does not provide the CMake imported target `zstd::libzstd`.
# Older versions must be consumed via their pkg-config file.
if(HTTPLIB_REQUIRE_ZSTD)
if (NOT TARGET zstd::libzstd)
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()
endif()
set(HTTPLIB_IS_USING_ZSTD TRUE)
elseif(HTTPLIB_USE_ZSTD_IF_AVAILABLE)
if (TARGET zstd::libzstd)
set(HTTPLIB_IS_USING_ZSTD TRUE)
else()
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()
endif()
# Used for default, common dirs that the end-user can change (if needed)
# like CMAKE_INSTALL_INCLUDEDIR or CMAKE_INSTALL_DATADIR
include(GNUInstallDirs)
include(ExternalProject)
add_library(${PROJECT_NAME} INTERFACE)
target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_11)
if(HTTPLIB_COMPILE)
# Put the split script into the build dir
configure_file(split.py "${CMAKE_CURRENT_BINARY_DIR}/split.py"
COPYONLY
)
# Needs to be in the same dir as the python script
configure_file(httplib.h "${CMAKE_CURRENT_BINARY_DIR}/httplib.h"
COPYONLY
)
target_include_directories(${PROJECT_NAME} INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>)
# Used outside of this if-else
set(_INTERFACE_OR_PUBLIC PUBLIC)
# Brings in the Python3_EXECUTABLE path we can use.
find_package(Python3 REQUIRED)
# Actually split the file
# Keeps the output in the build dir to not pollute the main dir
execute_process(COMMAND ${Python3_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/split.py"
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
ERROR_VARIABLE _httplib_split_error
)
if(_httplib_split_error)
message(FATAL_ERROR "Failed when trying to split cpp-httplib with the Python script.\n${_httplib_split_error}")
endif()
install(TARGETS ${PROJECT_NAME} EXPORT httplibConfig
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# 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()
install(FILES httplib.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
# split.py puts output in "out"
set(_httplib_build_includedir "${CMAKE_CURRENT_BINARY_DIR}/out")
add_library(${PROJECT_NAME} ${HTTPLIB_LIB_TYPE} "${_httplib_build_includedir}/httplib.cc")
target_sources(${PROJECT_NAME}
PUBLIC
$<BUILD_INTERFACE:${_httplib_build_includedir}/httplib.h>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/httplib.h>
)
install(EXPORT httplibConfig DESTINATION share/httplib/cmake)
# 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()
export(TARGETS ${PROJECT_NAME} FILE httplibConfig.cmake)
set_target_properties(${PROJECT_NAME}
PROPERTIES
VERSION ${${PROJECT_NAME}_VERSION}
SOVERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}"
OUTPUT_NAME cpp-httplib
)
else()
# This is for header-only.
set(_INTERFACE_OR_PUBLIC INTERFACE)
add_library(${PROJECT_NAME} INTERFACE)
set(_httplib_build_includedir "${CMAKE_CURRENT_SOURCE_DIR}")
endif()
# Lets you address the target with httplib::httplib
# Only useful if building in-tree, versus using it from an installation.
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
#add_subdirectory(example)
#add_subdirectory(test)
# 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}>
)
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_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES}>>:-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>
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::decoder>
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:ZLIB::ZLIB>
$<$<BOOL:${HTTPLIB_IS_USING_ZSTD}>:zstd::libzstd>
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:OpenSSL::SSL>
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:OpenSSL::Crypto>
$<$<BOOL:${HTTPLIB_IS_USING_WOLFSSL}>:wolfssl::wolfssl>
$<$<BOOL:${HTTPLIB_IS_USING_MBEDTLS}>:MbedTLS::mbedtls>
)
# Set the definitions to enable optional features
target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
$<$<BOOL:${HTTPLIB_NO_EXCEPTIONS}>:CPPHTTPLIB_NO_EXCEPTIONS>
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:CPPHTTPLIB_BROTLI_SUPPORT>
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:CPPHTTPLIB_ZLIB_SUPPORT>
$<$<BOOL:${HTTPLIB_IS_USING_ZSTD}>:CPPHTTPLIB_ZSTD_SUPPORT>
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
$<$<BOOL:${HTTPLIB_IS_USING_WOLFSSL}>:CPPHTTPLIB_WOLFSSL_SUPPORT>
$<$<BOOL:${HTTPLIB_IS_USING_MBEDTLS}>:CPPHTTPLIB_MBEDTLS_SUPPORT>
$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES}>>:CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES>
$<$<BOOL:${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO}>:CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO>
)
# CMake configuration files installation directory
set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
include(CMakePackageConfigHelpers)
# Configures the meta-file httplibConfig.cmake.in to replace variables with paths/values/etc.
configure_package_config_file("cmake/${PROJECT_NAME}Config.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
INSTALL_DESTINATION "${_TARGET_INSTALL_CMAKEDIR}"
# Passes the includedir install path
PATH_VARS CMAKE_INSTALL_FULL_INCLUDEDIR
)
if(HTTPLIB_COMPILE)
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
# Example: if you find_package(httplib 0.5.4)
# then anything >= 0.5.4 and < 0.6 is accepted
COMPATIBILITY SameMinorVersion
)
else()
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
# Example: if you find_package(httplib 0.5.4)
# then anything >= 0.5.4 and < 0.6 is accepted
COMPATIBILITY SameMinorVersion
# Tells Cmake that it's a header-only lib
# Mildly useful for end-users :)
ARCH_INDEPENDENT
)
endif()
if(HTTPLIB_INSTALL)
# Creates the export httplibTargets.cmake
# This is strictly what holds compilation requirements
# and linkage information (doesn't find deps though).
if(HTTPLIB_BUILD_MODULES)
install(TARGETS ${PROJECT_NAME} EXPORT httplibTargets FILE_SET CXX_MODULES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/httplib/modules CXX_MODULES_BMI DESTINATION ${CMAKE_INSTALL_LIBDIR}/httplib/modules)
else()
install(TARGETS ${PROJECT_NAME} EXPORT httplibTargets)
endif()
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
# Install it so it can be used later by the httplibConfig.cmake file.
# Put it in the same dir as our config file instead of a global path so we don't potentially stomp on other packages.
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindBrotli.cmake"
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
)
# NOTE: This path changes depending on if it's on Windows or Linux
install(EXPORT httplibTargets
# Puts the targets into the httplib namespace
# So this makes httplib::httplib linkable after doing find_package(httplib)
NAMESPACE ${PROJECT_NAME}::
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
)
# Install documentation & license
# ex: /usr/share/doc/httplib/README.md and /usr/share/licenses/httplib/LICENSE
install(FILES "README.md" DESTINATION "${CMAKE_INSTALL_DOCDIR}")
install(FILES "LICENSE" DESTINATION "${CMAKE_INSTALL_DATADIR}/licenses/${PROJECT_NAME}")
include(CPack)
endif()
if(HTTPLIB_BUILD_MODULES AND NOT HTTPLIB_COMPILE)
message(FATAL_ERROR "HTTPLIB_BUILD_MODULES requires HTTPLIB_COMPILE to be ON.")
endif()
if(HTTPLIB_TEST)
include(CTest)
add_subdirectory(test)
endif()
+13
View File
@@ -0,0 +1,13 @@
FROM yhirose4dockerhub/ubuntu-builder AS builder
WORKDIR /build
COPY httplib.h .
COPY docker/main.cc .
RUN g++ -std=c++23 -static -o server -O2 -I. main.cc && strip server
FROM scratch
COPY --from=builder /build/server /server
COPY docker/html/index.html /html/index.html
EXPOSE 80
ENTRYPOINT ["/server"]
CMD ["--host", "0.0.0.0", "--port", "80", "--mount", "/:./html"]
+204
View File
@@ -0,0 +1,204 @@
# 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);
// Update headers at any time (thread-safe)
sse.set_headers({{"Authorization", "Bearer new_token"}});
```
#### 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();
```
### Refreshing Auth Token on Reconnect
```cpp
httplib::sse::SSEClient sse(cli, "/events",
{{"Authorization", "Bearer " + get_token()}});
// Preemptively refresh token on each successful connection
sse.on_open([&sse]() {
sse.set_headers({{"Authorization", "Bearer " + get_token()}});
});
// Or reactively refresh on auth failure (401 triggers reconnect)
sse.on_error([&sse](httplib::Error) {
sse.set_headers({{"Authorization", "Bearer " + refresh_token()}});
});
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 │ ← SSE-specific, parsed events
│ - on_message(), on_event() │
│ - Auto-reconnect, Last-Event-ID │
├─────────────────────────────────────────────┤
│ stream::Get() / stream::Result │ ← Iterator-based streaming
│ - while (result.next()) { ... } │
├─────────────────────────────────────────────┤
│ open_stream() / StreamHandle │ ← General-purpose streaming
│ - handle.read(buf, len) │
├─────────────────────────────────────────────┤
│ Client::Get() │ ← Traditional, full buffering
└─────────────────────────────────────────────┘
```
| Use Case | Recommended API |
|----------|----------------|
| SSE with auto-reconnect | `SSEClient` (see [README-sse.md](README-sse.md)) |
| LLM streaming (JSON Lines) | `stream::Get()` |
| Large file download | `stream::Get()` or `open_stream()` |
| Reverse proxy | `open_stream()` |
| Small responses with Keep-Alive | `Client::Get()` |
## API Reference
### Low-Level API: `StreamHandle`
The `StreamHandle` struct provides direct control over streaming responses. It takes ownership of the socket connection and reads data directly from the network.
> **Note:** When using `open_stream()`, the connection is dedicated to streaming and **Keep-Alive is not supported**. For Keep-Alive connections, use `client.Get()` instead.
```cpp
// Open a stream (takes ownership of socket)
httplib::Client cli("http://localhost:8080");
auto handle = cli.open_stream("GET", "/path");
// Check validity
if (handle.is_valid()) {
// Access response headers immediately
int status = handle.response->status;
auto content_type = handle.response->get_header_value("Content-Type");
// Read body incrementally
char buf[4096];
ssize_t n;
while ((n = handle.read(buf, sizeof(buf))) > 0) {
process(buf, n);
}
}
```
#### StreamHandle Members
| Member | Type | Description |
|--------|------|-------------|
| `response` | `std::unique_ptr<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
+411
View File
@@ -0,0 +1,411 @@
# WebSocket - RFC 6455 WebSocket Support
A simple, blocking WebSocket implementation for C++11.
> [!IMPORTANT]
> This is a blocking I/O WebSocket implementation using a thread-per-connection model. If you need high-concurrency WebSocket support with non-blocking/async I/O (e.g., thousands of simultaneous connections), this is not the one that you want.
## Features
- **RFC 6455 compliant**: Full WebSocket protocol support
- **Server and Client**: Both sides included
- **SSL/TLS support**: `wss://` scheme for secure connections
- **Text and Binary**: Both message types supported
- **Automatic heartbeat**: Periodic Ping/Pong keeps connections alive
- **Subprotocol negotiation**: `Sec-WebSocket-Protocol` support for GraphQL, MQTT, etc.
## Quick Start
### Server
```cpp
httplib::Server svr;
svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
std::string msg;
while (ws.read(msg)) {
ws.send("echo: " + msg);
}
});
svr.listen("localhost", 8080);
```
### Client
```cpp
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws");
if (ws.connect()) {
ws.send("hello");
std::string msg;
if (ws.read(msg)) {
std::cout << msg << std::endl; // "echo: hello"
}
ws.close();
}
```
## API Reference
### ReadResult
```cpp
enum ReadResult : int {
Fail = 0, // Connection closed or error
Text = 1, // UTF-8 text message
Binary = 2, // Binary message
};
```
Returned by `read()`. Since `Fail` is `0`, the result works naturally in boolean contexts — `while (ws.read(msg))` continues until the connection closes. When you need to distinguish text from binary, check the return value directly.
### CloseStatus
```cpp
enum class CloseStatus : uint16_t {
Normal = 1000,
GoingAway = 1001,
ProtocolError = 1002,
UnsupportedData = 1003,
NoStatus = 1005,
Abnormal = 1006,
InvalidPayload = 1007,
PolicyViolation = 1008,
MessageTooBig = 1009,
MandatoryExtension = 1010,
InternalError = 1011,
};
```
### Server Registration
```cpp
// Basic handler
Server &WebSocket(const std::string &pattern, WebSocketHandler handler);
// With subprotocol negotiation
Server &WebSocket(const std::string &pattern, WebSocketHandler handler,
SubProtocolSelector sub_protocol_selector);
```
**Type aliases:**
```cpp
using WebSocketHandler =
std::function<void(const Request &, ws::WebSocket &)>;
using SubProtocolSelector =
std::function<std::string(const std::vector<std::string> &protocols)>;
```
The `SubProtocolSelector` receives the list of subprotocols proposed by the client (from the `Sec-WebSocket-Protocol` header) and returns the selected one. Return an empty string to decline all proposed subprotocols.
### WebSocket (Server-side)
Passed to the handler registered with `Server::WebSocket()`. The handler runs in a dedicated thread per connection.
```cpp
// Read next message (blocks until received, returns Fail/Text/Binary)
ReadResult read(std::string &msg);
// Send messages
bool send(const std::string &data); // Text
bool send(const char *data, size_t len); // Binary
// Close the connection
void close(CloseStatus status = CloseStatus::Normal,
const std::string &reason = "");
// Access the original HTTP upgrade request
const Request &request() const;
// Check if the connection is still open
bool is_open() const;
```
### WebSocketClient
```cpp
// Constructor - accepts ws:// or wss:// URL
explicit WebSocketClient(const std::string &scheme_host_port_path,
const Headers &headers = {});
// Check if the URL was parsed successfully
bool is_valid() const;
// Connect (performs HTTP upgrade handshake)
bool connect();
// Get the subprotocol selected by the server (empty if none)
const std::string &subprotocol() const;
// Read/Send/Close (same as server-side WebSocket)
ReadResult read(std::string &msg);
bool send(const std::string &data);
bool send(const char *data, size_t len);
void close(CloseStatus status = CloseStatus::Normal,
const std::string &reason = "");
bool is_open() const;
// Timeouts
void set_read_timeout(time_t sec, time_t usec = 0);
void set_write_timeout(time_t sec, time_t usec = 0);
// SSL configuration (wss:// only, requires CPPHTTPLIB_OPENSSL_SUPPORT)
void set_ca_cert_path(const std::string &path);
void set_ca_cert_store(tls::ca_store_t store);
void enable_server_certificate_verification(bool enabled);
```
## Examples
### Echo Server with Connection Logging
```cpp
httplib::Server svr;
svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
std::cout << "Connected from " << req.remote_addr << std::endl;
std::string msg;
while (ws.read(msg)) {
ws.send("echo: " + msg);
}
std::cout << "Disconnected" << std::endl;
});
svr.listen("localhost", 8080);
```
### Client: Continuous Read Loop
```cpp
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws");
if (ws.connect()) {
ws.send("hello");
ws.send("world");
std::string msg;
while (ws.read(msg)) { // blocks until a message arrives
std::cout << msg << std::endl; // "echo: hello", "echo: world"
}
// read() returns false when the server closes the connection
}
```
### Text and Binary Messages
Check the `ReadResult` return value to distinguish between text and binary:
```cpp
// Server
svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
std::string msg;
httplib::ws::ReadResult ret;
while ((ret = ws.read(msg))) {
if (ret == httplib::ws::Text) {
ws.send("echo: " + msg);
} else {
ws.send(msg.data(), msg.size()); // Binary echo
}
}
});
// Client
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws");
if (ws.connect()) {
// Send binary data
const char binary[] = {0x00, 0x01, 0x02, 0x03};
ws.send(binary, sizeof(binary));
// Receive and check the type
std::string msg;
if (ws.read(msg) == httplib::ws::Binary) {
// Process binary data in msg
}
ws.close();
}
```
### SSL Client
```cpp
httplib::ws::WebSocketClient ws("wss://echo.example.com/ws");
if (ws.connect()) {
ws.send("hello over TLS");
std::string msg;
if (ws.read(msg)) {
std::cout << msg << std::endl;
}
ws.close();
}
```
### Close with Status
```cpp
// Client-side: close with a specific status code and reason
ws.close(httplib::ws::CloseStatus::GoingAway, "shutting down");
// Server-side: close with a policy violation status
ws.close(httplib::ws::CloseStatus::PolicyViolation, "forbidden");
```
### Accessing the Upgrade Request
```cpp
svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
// Access headers from the original HTTP upgrade request
auto auth = req.get_header_value("Authorization");
if (auth.empty()) {
ws.close(httplib::ws::CloseStatus::PolicyViolation, "unauthorized");
return;
}
std::string msg;
while (ws.read(msg)) {
ws.send("echo: " + msg);
}
});
```
### Custom Headers and Timeouts
```cpp
httplib::Headers headers = {
{"Authorization", "Bearer token123"}
};
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws", headers);
ws.set_read_timeout(30, 0); // 30 seconds
ws.set_write_timeout(10, 0); // 10 seconds
if (ws.connect()) {
std::string msg;
while (ws.read(msg)) {
std::cout << msg << std::endl;
}
}
```
### Subprotocol Negotiation
The server can negotiate a subprotocol with the client using `Sec-WebSocket-Protocol`. This is required for protocols like GraphQL over WebSocket (`graphql-ws`) and MQTT.
```cpp
// Server: register a handler with a subprotocol selector
svr.WebSocket(
"/ws",
[](const httplib::Request &req, httplib::ws::WebSocket &ws) {
std::string msg;
while (ws.read(msg)) {
ws.send("echo: " + msg);
}
},
[](const std::vector<std::string> &protocols) -> std::string {
// The client proposed a list of subprotocols; pick one
for (const auto &p : protocols) {
if (p == "graphql-ws" || p == "graphql-transport-ws") {
return p;
}
}
return ""; // Decline all
});
// Client: propose subprotocols via Sec-WebSocket-Protocol header
httplib::Headers headers = {
{"Sec-WebSocket-Protocol", "graphql-ws, graphql-transport-ws"}
};
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws", headers);
if (ws.connect()) {
// Check which subprotocol the server selected
std::cout << "Subprotocol: " << ws.subprotocol() << std::endl;
// => "graphql-ws"
ws.close();
}
```
### SSL Client with Certificate Configuration
```cpp
httplib::ws::WebSocketClient ws("wss://example.com/ws");
ws.set_ca_cert_path("/path/to/ca-bundle.crt");
ws.enable_server_certificate_verification(true);
if (ws.connect()) {
ws.send("secure message");
ws.close();
}
```
## Configuration
| Macro | Default | Description |
|---------------------------------------------|-------------------|----------------------------------------------------------|
| `CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH` | `16777216` (16MB) | Maximum payload size per message |
| `CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND` | `300` | Read timeout for WebSocket connections (seconds) |
| `CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND` | `5` | Timeout for waiting peer's Close response (seconds) |
| `CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND` | `30` | Automatic Ping interval for heartbeat (seconds) |
### Runtime Ping Interval
You can override the ping interval at runtime instead of changing the compile-time macro. Set it to `0` to disable automatic pings entirely.
```cpp
// Server side
httplib::Server svr;
svr.set_websocket_ping_interval(10); // 10 seconds
// Or using std::chrono
svr.set_websocket_ping_interval(std::chrono::seconds(10));
// Client side
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws");
ws.set_websocket_ping_interval(10); // 10 seconds
// Disable automatic pings
ws.set_websocket_ping_interval(0);
```
## Threading Model
WebSocket connections share the same thread pool as HTTP requests. Each WebSocket connection occupies one thread for its entire lifetime.
The default thread pool uses dynamic scaling: it maintains a base thread count of `CPPHTTPLIB_THREAD_POOL_COUNT` (8 or `std::thread::hardware_concurrency() - 1`, whichever is greater) and can scale up to 4x that count under load (`CPPHTTPLIB_THREAD_POOL_MAX_COUNT`). When all base threads are busy, temporary threads are spawned automatically up to the maximum. These dynamic threads exit after an idle timeout (`CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT`, default 3 seconds).
This dynamic scaling helps accommodate WebSocket connections alongside HTTP requests. However, if you expect many simultaneous WebSocket connections, you should configure the thread pool accordingly:
```cpp
httplib::Server svr;
svr.new_task_queue = [] {
return new httplib::ThreadPool(/*base_threads=*/8, /*max_threads=*/128);
};
```
Choose sizes that account for both your expected HTTP load and the maximum number of simultaneous WebSocket connections.
## Protocol
The implementation follows [RFC 6455](https://tools.ietf.org/html/rfc6455):
- Handshake via HTTP Upgrade with `Sec-WebSocket-Key` / `Sec-WebSocket-Accept`
- Subprotocol negotiation via `Sec-WebSocket-Protocol`
- Frame masking (client-to-server)
- Control frames: Close, Ping, Pong
- Message fragmentation and reassembly
- Close handshake with status codes
## Browser Test
Run the echo server example and open `http://localhost:8080` in a browser:
```bash
cd example && make wsecho && ./wsecho
```
+1290 -168
View File
File diff suppressed because it is too large Load Diff
-9
View File
@@ -1,9 +0,0 @@
version: 1.0.{build}
image: Visual Studio 2017
build_script:
- cmd: >-
cd test
msbuild.exe test.sln /verbosity:minimal /t:Build /p:Configuration=Debug;Platform=Win32
test_script:
- cmd: Debug\test.exe
+50
View File
@@ -0,0 +1,50 @@
CXXFLAGS = -O2 -I..
CPPHTTPLIB_CXXFLAGS = -std=c++11
CROW_CXXFLAGS = -std=c++17
CPPHTTPLIB_FLAGS = -DCPPHTTPLIB_THREAD_POOL_COUNT=16
BENCH = bombardier -c 10 -d 5s localhost:8080
MONITOR = ali http://localhost:8080
# cpp-httplib
bench: server
@echo "--------------------\n cpp-httplib latest\n--------------------\n"
@./server & export PID=$$!; $(BENCH); kill $${PID}
@echo ""
monitor: server
@./server & export PID=$$!; $(MONITOR); kill $${PID}
run : server
@./server
server : cpp-httplib/main.cpp ../httplib.h
@g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib/main.cpp
# crow
bench-crow: server-crow
@echo "-------------\n Crow v1.3.1\n-------------\n"
@./server-crow & export PID=$$!; $(BENCH); kill $${PID}
@echo ""
monitor-crow: server-crow
@./server-crow & export PID=$$!; $(MONITOR); kill $${PID}
run-crow : server-crow
@./server-crow
server-crow : crow/main.cpp crow/crow_all.h
@g++ -o $@ $(CXXFLAGS) $(CROW_CXXFLAGS) crow/main.cpp
# misc
build: server server-crow
bench-all: bench-crow bench
issue:
bombardier -c 10 -d 30s localhost:8080
clean:
rm -rf server*
+12
View File
@@ -0,0 +1,12 @@
#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
+17
View File
@@ -0,0 +1,17 @@
#include "crow_all.h"
class CustomLogger : public crow::ILogHandler {
public:
void log(const std::string &, crow::LogLevel) {}
};
int main() {
CustomLogger logger;
crow::logger::setHandler(&logger);
crow::SimpleApp app;
CROW_ROUTE(app, "/")([]() { return "Hello world!"; });
app.port(8080).multithreaded().run();
}
+173
View File
@@ -0,0 +1,173 @@
# A simple FindBrotli package for Cmake's find_package function.
# Note: This find package doesn't have version support, as the version file doesn't seem to be installed on most systems.
#
# If you want to find the static packages instead of shared (the default), define BROTLI_USE_STATIC_LIBS as TRUE.
# The targets will have the same names, but it will use the static libs.
#
# Valid find_package COMPONENTS names: "decoder", "encoder", and "common"
# Note that if you're requiring "decoder" or "encoder", then "common" will be automatically added as required.
#
# Defines the libraries (if found): Brotli::decoder, Brotli::encoder, Brotli::common
# and the includes path variable: Brotli_INCLUDE_DIR
#
# If it's failing to find the libraries, try setting BROTLI_ROOT_DIR to the folder containing your library & include dir.
# If they asked for a specific version, warn/fail since we don't support it.
# TODO: if they start distributing the version somewhere, implement finding it.
# See https://github.com/google/brotli/issues/773#issuecomment-579133187
if(Brotli_FIND_VERSION)
set(_brotli_version_error_msg "FindBrotli.cmake doesn't have version support!")
# If the package is required, throw a fatal error
# Otherwise, if not running quietly, we throw a warning
if(Brotli_FIND_REQUIRED)
message(FATAL_ERROR "${_brotli_version_error_msg}")
elseif(NOT Brotli_FIND_QUIETLY)
message(WARNING "${_brotli_version_error_msg}")
endif()
endif()
# Since both decoder & encoder require the common lib, force its requirement..
# if the user is requiring either of those other libs.
if(Brotli_FIND_REQUIRED_decoder OR Brotli_FIND_REQUIRED_encoder)
set(Brotli_FIND_REQUIRED_common TRUE)
endif()
# Support preference of static libs by adjusting CMAKE_FIND_LIBRARY_SUFFIXES
# Credit to FindOpenSSL.cmake for this
if(BROTLI_USE_STATIC_LIBS)
set(_brotli_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
if(WIN32)
set(CMAKE_FIND_LIBRARY_SUFFIXES .lib .a ${CMAKE_FIND_LIBRARY_SUFFIXES})
else()
set(CMAKE_FIND_LIBRARY_SUFFIXES .a)
endif()
endif()
# Make PkgConfig optional, since some users (mainly Windows) don't have it.
# But it's a lot more clean than manually using find_library.
find_package(PkgConfig QUIET)
# Only used if the PkgConfig libraries aren't used.
find_path(Brotli_INCLUDE_DIR
NAMES
"brotli/decode.h"
"brotli/encode.h"
HINTS
${BROTLI_ROOT_DIR}
PATH_SUFFIXES
"include"
"includes"
DOC "The path to Brotli's include directory."
)
# Hides this var from the GUI
mark_as_advanced(Brotli_INCLUDE_DIR)
# Just used for PkgConfig stuff in the loop below
set(_brotli_stat_str "")
if(BROTLI_USE_STATIC_LIBS)
set(_brotli_stat_str "_STATIC")
endif()
# Each string here is "ComponentName;LiteralName" (the semi-colon is a delimiter)
foreach(_listvar "common;common" "decoder;dec" "encoder;enc")
# Split the component name and literal library name from the listvar
list(GET _listvar 0 _component_name)
list(GET _listvar 1 _libname)
# NOTE: We can't rely on PkgConf for static libs since the upstream static lib support is broken
# See https://github.com/google/brotli/issues/795
# TODO: whenever their issue is fixed upstream, remove this "AND NOT BROTLI_USE_STATIC_LIBS" check
if(PKG_CONFIG_FOUND AND NOT BROTLI_USE_STATIC_LIBS)
# These need to be GLOBAL for MinGW when making ALIAS libraries against them.
# Have to postfix _STATIC on the name to tell PkgConfig to find the static libs.
pkg_check_modules(Brotli_${_component_name}${_brotli_stat_str} QUIET GLOBAL IMPORTED_TARGET libbrotli${_libname})
endif()
# 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)
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)
if(Brotli_FIND_REQUIRED_${_component_name})
# If the lib is required, we can add its literal path as a required var for FindPackageHandleStandardArgs
# Since it won't accept the PkgConfig targets
if(BROTLI_USE_STATIC_LIBS)
list(APPEND _brotli_req_vars "Brotli_${_component_name}_STATIC_LIBRARIES")
else()
list(APPEND _brotli_req_vars "Brotli_${_component_name}_LINK_LIBRARIES")
endif()
endif()
# Skip searching for the libs with find_library since it was already found by Pkgconf
continue()
endif()
if(Brotli_FIND_REQUIRED_${_component_name})
# If it's required, we can set the name used in find_library as a required var for FindPackageHandleStandardArgs
list(APPEND _brotli_req_vars "Brotli_${_component_name}")
endif()
list(APPEND _brotli_lib_names
"brotli${_libname}"
"libbrotli${_libname}"
)
if(BROTLI_USE_STATIC_LIBS)
# Postfix "-static" to the libnames since we're looking for static libs
list(TRANSFORM _brotli_lib_names APPEND "-static")
endif()
find_library(Brotli_${_component_name}
NAMES ${_brotli_lib_names}
HINTS ${BROTLI_ROOT_DIR}
PATH_SUFFIXES
"lib"
"lib64"
"libs"
"libs64"
"lib/x86_64-linux-gnu"
)
# Hide the library variable from the Cmake GUI
mark_as_advanced(Brotli_${_component_name})
# Unset since otherwise it'll stick around for the next loop and break things
unset(_brotli_lib_names)
# Check if find_library found the library
if(Brotli_${_component_name})
# Tells HANDLE_COMPONENTS we found the component
set(Brotli_${_component_name}_FOUND TRUE)
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}"
IMPORTED_LOCATION "${Brotli_${_component_name}}"
)
else()
# Tells HANDLE_COMPONENTS we found the component
set(Brotli_${_component_name}_FOUND FALSE)
endif()
endforeach()
include(FindPackageHandleStandardArgs)
# Sets Brotli_FOUND, and fails the find_package(Brotli) call if it was REQUIRED but missing libs.
find_package_handle_standard_args(Brotli
FOUND_VAR
Brotli_FOUND
REQUIRED_VARS
Brotli_INCLUDE_DIR
${_brotli_req_vars}
HANDLE_COMPONENTS
)
# Restore the original find library ordering
if(BROTLI_USE_STATIC_LIBS)
set(CMAKE_FIND_LIBRARY_SUFFIXES ${_brotli_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES})
endif()
+119
View File
@@ -0,0 +1,119 @@
# Generates a macro to auto-configure everything
@PACKAGE_INIT@
# Setting these here so they're accessible after install.
# Might be useful for some users to check which settings were used.
set(HTTPLIB_IS_USING_OPENSSL @HTTPLIB_IS_USING_OPENSSL@)
set(HTTPLIB_IS_USING_WOLFSSL @HTTPLIB_IS_USING_WOLFSSL@)
set(HTTPLIB_IS_USING_MBEDTLS @HTTPLIB_IS_USING_MBEDTLS@)
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)
# We add find_dependency calls here to not make the end-user have to call them.
find_dependency(Threads)
if(@HTTPLIB_IS_USING_OPENSSL@)
# OpenSSL COMPONENTS were added in Cmake v3.11
if(CMAKE_VERSION VERSION_LESS "3.11")
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@)
else()
# Once the COMPONENTS were added, they were made optional when not specified.
# Since we use both, we need to search for both.
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ COMPONENTS Crypto SSL)
endif()
set(httplib_OpenSSL_FOUND ${OpenSSL_FOUND})
endif()
if(@HTTPLIB_IS_USING_ZLIB@)
find_dependency(ZLIB)
set(httplib_ZLIB_FOUND ${ZLIB_FOUND})
endif()
if(@HTTPLIB_IS_USING_WOLFSSL@)
find_dependency(wolfssl)
set(httplib_wolfssl_FOUND ${wolfssl_FOUND})
endif()
if(@HTTPLIB_IS_USING_MBEDTLS@)
find_dependency(MbedTLS)
set(httplib_MbedTLS_FOUND ${MbedTLS_FOUND})
endif()
if(@HTTPLIB_IS_USING_BROTLI@)
# Needed so we can use our own FindBrotli.cmake in this file.
# Note that the FindBrotli.cmake file is installed in the same dir as this file.
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
set(BROTLI_USE_STATIC_LIBS @BROTLI_USE_STATIC_LIBS@)
find_dependency(Brotli COMPONENTS common encoder decoder)
set(httplib_Brotli_FOUND ${Brotli_FOUND})
endif()
if(@HTTPLIB_IS_USING_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()
# Mildly useful for end-users
# Not really recommended to be used though
set_and_check(HTTPLIB_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@")
# Lets the end-user find the header path with the header appended
# This is helpful if you're using Cmake's pre-compiled header feature
set_and_check(HTTPLIB_HEADER_PATH "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@/httplib.h")
check_required_components(httplib)
# Brings in the target library, but only if all required components are found
if(NOT DEFINED httplib_FOUND OR httplib_FOUND)
include("${CMAKE_CURRENT_LIST_DIR}/httplibTargets.cmake")
endif()
# Outputs a "found httplib /usr/include/httplib.h" message when using find_package(httplib)
include(FindPackageMessage)
if(TARGET httplib::httplib)
set(HTTPLIB_FOUND TRUE)
# Since the compiled version has a lib, show that in the message
if(@HTTPLIB_COMPILE@)
# The list of configurations is most likely just 1 unless they installed a debug & release
get_target_property(_httplib_configs httplib::httplib "IMPORTED_CONFIGURATIONS")
# Need to loop since the "IMPORTED_LOCATION" property isn't want we want.
# Instead, we need to find the IMPORTED_LOCATION_RELEASE or IMPORTED_LOCATION_DEBUG which has the lib path.
foreach(_httplib_conf "${_httplib_configs}")
# Grab the path to the lib and sets it to HTTPLIB_LIBRARY
get_target_property(HTTPLIB_LIBRARY httplib::httplib "IMPORTED_LOCATION_${_httplib_conf}")
# Check if we found it
if(HTTPLIB_LIBRARY)
break()
endif()
endforeach()
unset(_httplib_configs)
unset(_httplib_conf)
find_package_message(httplib "Found httplib: ${HTTPLIB_LIBRARY} (found version \"${HTTPLIB_VERSION}\")" "[${HTTPLIB_LIBRARY}][${HTTPLIB_HEADER_PATH}]")
else()
find_package_message(httplib "Found httplib: ${HTTPLIB_HEADER_PATH} (found version \"${HTTPLIB_VERSION}\")" "[${HTTPLIB_HEADER_PATH}]")
endif()
endif()
+19
View File
@@ -0,0 +1,19 @@
# 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
BASE_DIRS
"${_httplib_build_includedir}"
FILES
"${_httplib_build_includedir}/httplib.cppm"
)
endif()
+7
View File
@@ -0,0 +1,7 @@
services:
http:
build: .
ports:
- "8080:80"
volumes:
- ./docker/html:/html
+21
View File
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<title>Welcome to cpp-httplib!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to cpp-httplib!</h1>
<p>If you see this page, the cpp-httplib web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="https://github.com/yhirose/cpp-httplib">github.com/yhirose/cpp-httplib</a>.<br/>
<p><em>Thank you for using cpp-httplib.</em></p>
</body>
</html>
+303
View File
@@ -0,0 +1,303 @@
//
// main.cc
//
// 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>
using namespace httplib;
const auto SERVER_NAME =
std::format("cpp-httplib-server/{}", CPPHTTPLIB_VERSION);
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(&time_t), "%d/%b/%Y:%H:%M:%S %z");
return ss.str();
}
std::string get_error_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(&time_t), "%Y/%m/%d %H:%M:%S");
return ss.str();
}
// 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 = "-";
std::cout << std::format("{} - {} [{}] \"{}\" {} {} \"{}\" \"{}\"",
req.remote_addr, remote_user, time_local, request,
status, body_bytes_sent, http_referer,
http_user_agent)
<< std::endl;
}
// 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";
if (req) {
auto request =
std::format("{} {} {}", req->method, req->path, req->version);
auto host = req->get_header_value("Host");
if (host.empty()) host = "-";
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_pre_routing_handler([](const Request & /*req*/, Response &res) {
res.set_header("Server", SERVER_NAME);
return Server::HandlerResponse::Unhandled;
});
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
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;
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;
}
+30
View File
@@ -0,0 +1,30 @@
[system]
theme = "monotone"
langs = ["en", "ja"]
[site]
title = "cpp-httplib"
version = "0.42.0"
hostname = "https://yhirose.github.io"
base_path = "/cpp-httplib"
footer_message = "© 2026 Yuji Hirose. All rights reserved."
[[nav]]
label = "Tour"
path = "tour/"
icon_svg = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/></svg>'
[[nav]]
label = "Cookbook"
path = "cookbook/"
icon_svg = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>'
[[nav]]
label = "LLM App"
path = "llm-app/"
icon_svg = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>'
[[nav]]
label = "GitHub"
url = "https://github.com/yhirose/cpp-httplib"
icon_svg = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>'
@@ -0,0 +1,60 @@
---
title: "C01. Get the Response Body / Save to a File"
order: 1
status: "draft"
---
## Get it as a string
```cpp
httplib::Client cli("http://localhost:8080");
auto res = cli.Get("/hello");
if (res && res->status == 200) {
std::cout << res->body << std::endl;
}
```
`res->body` is a `std::string`, ready to use as-is. The entire response is loaded into memory.
> **Warning:** If you fetch a large file with `res->body`, it all goes into memory. For large downloads, use a `ContentReceiver` as shown below.
## Save to a file
```cpp
httplib::Client cli("http://localhost:8080");
std::ofstream ofs("output.bin", std::ios::binary);
if (!ofs) {
std::cerr << "Failed to open file" << std::endl;
return 1;
}
auto res = cli.Get("/large-file",
[&](const char *data, size_t len) {
ofs.write(data, len);
return static_cast<bool>(ofs);
});
```
With a `ContentReceiver`, data arrives in chunks. You can write each chunk straight to disk without buffering the whole body in memory — perfect for large file downloads.
Return `false` from the callback to abort the download. In the example above, if writing to `ofs` fails, the download stops automatically.
> **Detail:** Want to check response headers like Content-Length before downloading? Combine a `ResponseHandler` with a `ContentReceiver`.
>
> ```cpp
> auto res = cli.Get("/large-file",
> [](const httplib::Response &res) {
> auto len = res.get_header_value("Content-Length");
> std::cout << "Size: " << len << std::endl;
> return true; // return false to skip the download
> },
> [&](const char *data, size_t len) {
> ofs.write(data, len);
> return static_cast<bool>(ofs);
> });
> ```
>
> The `ResponseHandler` is called after headers arrive but before the body. Return `false` to skip the download entirely.
> To show download progress, see C11. Use the progress callback.
+36
View File
@@ -0,0 +1,36 @@
---
title: "C02. Send and Receive JSON"
order: 2
status: "draft"
---
cpp-httplib doesn't include a JSON parser. Use a library like [nlohmann/json](https://github.com/nlohmann/json) to build and parse JSON. The examples here use `nlohmann/json`.
## Send JSON
```cpp
httplib::Client cli("http://localhost:8080");
nlohmann::json j = {{"name", "Alice"}, {"age", 30}};
auto res = cli.Post("/api/users", j.dump(), "application/json");
```
Pass the JSON string as the second argument to `Post()` and the Content-Type as the third. The same pattern works with `Put()` and `Patch()`.
> **Warning:** If you omit the Content-Type (the third argument), the server may not recognize the body as JSON. Always specify `"application/json"`.
## Receive a JSON response
```cpp
auto res = cli.Get("/api/users/1");
if (res && res->status == 200) {
auto j = nlohmann::json::parse(res->body);
std::cout << j["name"] << std::endl;
}
```
`res->body` is a `std::string`, so you can pass it straight to your JSON library.
> **Note:** Servers sometimes return HTML on errors. Check the status code before parsing to be safe. Some APIs also require an `Accept: application/json` header. If you're calling a JSON API repeatedly, C03. Set default headers can save you some boilerplate.
> For how to receive and return JSON on the server side, see S02. Receive JSON requests and return JSON responses.
@@ -0,0 +1,55 @@
---
title: "C03. Set Default Headers"
order: 3
status: "draft"
---
When you want the same headers on every request, use `set_default_headers()`. Once set, they're attached automatically to every request sent from that client.
## Basic usage
```cpp
httplib::Client cli("https://api.example.com");
cli.set_default_headers({
{"Accept", "application/json"},
{"User-Agent", "my-app/1.0"},
});
auto res = cli.Get("/users");
```
Register the headers you need on every API call — like `Accept` or `User-Agent` — in one place. No need to repeat them on each request.
## Send a Bearer token on every request
```cpp
httplib::Client cli("https://api.example.com");
cli.set_default_headers({
{"Authorization", "Bearer " + token},
{"Accept", "application/json"},
});
auto res1 = cli.Get("/me");
auto res2 = cli.Get("/projects");
```
Set the auth token once, and every subsequent request carries it. Handy when you're writing an API client that hits multiple endpoints.
> **Note:** `set_default_headers()` **replaces** the existing default headers. Even if you only want to add one, pass the full set again.
## Combine with per-request headers
You can still pass extra headers on individual requests, even with defaults set.
```cpp
httplib::Headers headers = {
{"X-Request-ID", "abc-123"},
};
auto res = cli.Get("/users", headers);
```
Per-request headers are **added** on top of the defaults. Both are sent to the server.
> For details on Bearer token auth, see C06. Call an API with a Bearer Token.
@@ -0,0 +1,38 @@
---
title: "C04. Follow Redirects"
order: 4
status: "draft"
---
By default, cpp-httplib does not follow HTTP redirects (3xx). If the server returns `302 Found`, you'll get it as a response with status code 302 — nothing more.
To follow redirects automatically, call `set_follow_location(true)`.
## Follow redirects
```cpp
httplib::Client cli("http://example.com");
cli.set_follow_location(true);
auto res = cli.Get("/old-path");
if (res && res->status == 200) {
std::cout << res->body << std::endl;
}
```
With `set_follow_location(true)`, the client reads the `Location` header and reissues the request to the new URL automatically. The final response ends up in `res`.
## Redirects from HTTP to HTTPS
```cpp
httplib::Client cli("http://example.com");
cli.set_follow_location(true);
auto res = cli.Get("/");
```
Many sites redirect HTTP traffic to HTTPS. With `set_follow_location(true)` on, this case is handled transparently — the client follows redirects even when the scheme or host changes.
> **Warning:** To follow redirects to HTTPS, you need to build cpp-httplib with OpenSSL (or another TLS backend). Without TLS support, redirects to HTTPS will fail.
> **Note:** Following redirects adds to the total request time. See C12. Set Timeouts for timeout configuration.
@@ -0,0 +1,46 @@
---
title: "C05. Use Basic Authentication"
order: 5
status: "draft"
---
For endpoints that require Basic authentication, pass the username and password to `set_basic_auth()`. cpp-httplib builds the `Authorization: Basic ...` header for you.
## Basic usage
```cpp
httplib::Client cli("https://api.example.com");
cli.set_basic_auth("alice", "s3cret");
auto res = cli.Get("/private");
if (res && res->status == 200) {
std::cout << res->body << std::endl;
}
```
Set it once, and every request from that client carries the credentials. No need to build the header each time.
## Per-request usage
If you want credentials on only one specific request, pass headers directly.
```cpp
httplib::Headers headers = {
httplib::make_basic_authentication_header("alice", "s3cret"),
};
auto res = cli.Get("/private", headers);
```
`make_basic_authentication_header()` builds the Base64-encoded header for you.
> **Warning:** Basic authentication **encodes** credentials in Base64 — it does not encrypt them. Always use it over HTTPS. Over plain HTTP, your password travels the network in the clear.
## Digest authentication
For the more secure Digest authentication scheme, use `set_digest_auth()`. This is only available when cpp-httplib is built with OpenSSL (or another TLS backend).
```cpp
cli.set_digest_auth("alice", "s3cret");
```
> To call an API with a Bearer token, see C06. Call an API with a Bearer Token.
@@ -0,0 +1,50 @@
---
title: "C06. Call an API with a Bearer Token"
order: 6
status: "draft"
---
For Bearer token authentication — common in OAuth 2.0 and modern Web APIs — use `set_bearer_token_auth()`. Pass the token and cpp-httplib builds the `Authorization: Bearer <token>` header for you.
## Basic usage
```cpp
httplib::Client cli("https://api.example.com");
cli.set_bearer_token_auth("eyJhbGciOiJIUzI1NiIs...");
auto res = cli.Get("/me");
if (res && res->status == 200) {
std::cout << res->body << std::endl;
}
```
Set it once and every subsequent request carries the token. This is the go-to pattern for token-based APIs like GitHub, Slack, or your own OAuth service.
## Per-request usage
When you want the token on only one request — or need a different token per request — pass it via headers.
```cpp
httplib::Headers headers = {
httplib::make_bearer_token_authentication_header(token),
};
auto res = cli.Get("/me", headers);
```
`make_bearer_token_authentication_header()` builds the `Authorization` header for you.
## Refresh the token
When a token expires, just call `set_bearer_token_auth()` again with the new one.
```cpp
if (res && res->status == 401) {
auto new_token = refresh_token();
cli.set_bearer_token_auth(new_token);
res = cli.Get("/me");
}
```
> **Warning:** A Bearer token is itself a credential. Always send it over HTTPS, and never hard-code it into source or config files.
> To set multiple headers at once, see C03. Set Default Headers.
@@ -0,0 +1,52 @@
---
title: "C07. Upload a File as Multipart Form Data"
order: 7
status: "draft"
---
When you want to send a file the same way an HTML `<input type="file">` does, use multipart form data (`multipart/form-data`). cpp-httplib offers two APIs — `UploadFormDataItems` and `FormDataProviderItems` — and you pick between them based on **file size**.
## Send a small file
Read the file into memory first, then send it. For small files, this is the simplest path.
```cpp
httplib::Client cli("http://localhost:8080");
std::ifstream ifs("avatar.png", std::ios::binary);
std::string content((std::istreambuf_iterator<char>(ifs)),
std::istreambuf_iterator<char>());
httplib::UploadFormDataItems items = {
{"name", "Alice", "", ""},
{"avatar", content, "avatar.png", "image/png"},
};
auto res = cli.Post("/upload", items);
```
Each `UploadFormData` entry is `{name, content, filename, content_type}`. For plain text fields, leave `filename` and `content_type` empty.
## Stream a large file
To avoid loading the whole file into memory, use `make_file_provider()`. It reads the file in chunks as it sends — so even huge files won't blow up your memory footprint.
```cpp
httplib::Client cli("http://localhost:8080");
httplib::UploadFormDataItems items = {
{"name", "Alice", "", ""},
};
httplib::FormDataProviderItems provider_items = {
httplib::make_file_provider("video", "large-video.mp4", "", "video/mp4"),
};
auto res = cli.Post("/upload", httplib::Headers{}, items, provider_items);
```
The arguments to `make_file_provider()` are `(form name, file path, file name, content type)`. Leave the file name empty to use the file path as-is.
> **Note:** You can mix `UploadFormDataItems` and `FormDataProviderItems` in the same request. A clean split is: text fields in `UploadFormDataItems`, files in `FormDataProviderItems`.
> To show upload progress, see C11. Use the progress callback.
@@ -0,0 +1,34 @@
---
title: "C08. POST a File as Raw Binary"
order: 8
status: "draft"
---
Sometimes you want to send a file's contents as the request body directly — no multipart wrapping. This is common for S3-compatible APIs or endpoints that take raw image data. For this, use `make_file_body()`.
## Basic usage
```cpp
httplib::Client cli("https://storage.example.com");
auto [size, provider] = httplib::make_file_body("backup.tar.gz");
if (size == 0) {
std::cerr << "Failed to open file" << std::endl;
return 1;
}
auto res = cli.Put("/bucket/backup.tar.gz", size,
provider, "application/gzip");
```
`make_file_body()` returns a pair of file size and a `ContentProvider`. Pass them to `Post()` or `Put()` and the file contents flow straight into the request body.
The `ContentProvider` reads the file in chunks, so even huge files never sit fully in memory.
## When the file can't be opened
If the file can't be opened, `make_file_body()` returns `size` as `0` and `provider` as an empty function object. Sending that would produce garbage — always check `size` first.
> **Warning:** `make_file_body()` needs to fix the Content-Length up front, so it reads the file size ahead of time. If the file size might change mid-upload, this API isn't the right fit.
> To send the file as multipart form data instead, see C07. Upload a File as Multipart Form Data.
@@ -0,0 +1,47 @@
---
title: "C09. Send the Body with Chunked Transfer"
order: 9
status: "draft"
---
When you don't know the body size up front — for data generated on the fly or piped from another stream — use `ContentProviderWithoutLength`. The client sends the body with HTTP chunked transfer encoding.
## Basic usage
```cpp
httplib::Client cli("http://localhost:8080");
auto res = cli.Post("/stream",
[&](size_t offset, httplib::DataSink &sink) {
std::string chunk = produce_next_chunk();
if (chunk.empty()) {
sink.done(); // done sending
return true;
}
return sink.write(chunk.data(), chunk.size());
},
"application/octet-stream");
```
The lambda's job is just: produce the next chunk and send it with `sink.write()`. When there's no more data, call `sink.done()` and you're finished.
## When the size is known
If you **do** know the total size ahead of time, use the `ContentProvider` overload (taking `size_t offset, size_t length, DataSink &sink`) and pass the total size as well.
```cpp
size_t total_size = get_total_size();
auto res = cli.Post("/upload", total_size,
[&](size_t offset, size_t length, httplib::DataSink &sink) {
auto data = read_range(offset, length);
return sink.write(data.data(), data.size());
},
"application/octet-stream");
```
With a known size, the request carries a Content-Length header — so the server can show progress. Prefer this form when you can.
> **Detail:** `sink.write()` returns a `bool` indicating whether the write succeeded. If it returns `false`, the connection is gone — return `false` from the lambda to stop.
> If you're just sending a file, `make_file_body()` is easier. See C08. POST a File as Raw Binary.
@@ -0,0 +1,52 @@
---
title: "C10. Receive a Response as a Stream"
order: 10
status: "draft"
---
To receive a response body chunk by chunk, use a `ContentReceiver`. It's the obvious choice for large files, but it's equally handy for NDJSON (newline-delimited JSON) or log streams where you want to start processing data as it arrives.
## Process each chunk
```cpp
httplib::Client cli("http://localhost:8080");
auto res = cli.Get("/logs/stream",
[](const char *data, size_t len) {
std::cout.write(data, len);
std::cout.flush();
return true; // return false to stop receiving
});
```
Data arrives in the lambda in the order it's received from the server. Return `false` from the callback to stop the download partway through.
## Parse NDJSON line by line
Here's a buffered approach for processing newline-delimited JSON one line at a time.
```cpp
std::string buffer;
auto res = cli.Get("/events",
[&](const char *data, size_t len) {
buffer.append(data, len);
size_t pos;
while ((pos = buffer.find('\n')) != std::string::npos) {
auto line = buffer.substr(0, pos);
buffer.erase(0, pos + 1);
if (!line.empty()) {
auto j = nlohmann::json::parse(line);
handle_event(j);
}
}
return true;
});
```
Accumulate into a buffer, then pull out and parse one line each time you see a newline. This is the standard pattern for consuming a streaming API in real time.
> **Warning:** When you pass a `ContentReceiver`, `res->body` stays **empty**. Store or process the body inside the callback yourself.
> To track download progress, combine this with C11. Use the Progress Callback.
> For Server-Sent Events (SSE), see E04. Receive SSE on the Client.
@@ -0,0 +1,59 @@
---
title: "C11. Use the Progress Callback"
order: 11
status: "draft"
---
To display download or upload progress, pass a `DownloadProgress` or `UploadProgress` callback. Both take two arguments: `(current, total)`.
## Download progress
```cpp
httplib::Client cli("http://localhost:8080");
auto res = cli.Get("/large-file",
[](size_t current, size_t total) {
auto percent = (total > 0) ? (current * 100 / total) : 0;
std::cout << "\rDownloading: " << percent << "% ("
<< current << "/" << total << ")" << std::flush;
return true; // return false to abort
});
std::cout << std::endl;
```
The callback fires each time data arrives. `total` comes from the Content-Length header — if the server doesn't send one, it may be `0`. In that case, you can't compute a percentage, so just display bytes received.
## Upload progress
Uploads work the same way. Pass an `UploadProgress` as the last argument to `Post()` or `Put()`.
```cpp
httplib::Client cli("http://localhost:8080");
std::string body = load_large_body();
auto res = cli.Post("/upload", body, "application/octet-stream",
[](size_t current, size_t total) {
auto percent = current * 100 / total;
std::cout << "\rUploading: " << percent << "%" << std::flush;
return true;
});
std::cout << std::endl;
```
## Cancel mid-transfer
Return `false` from the callback to abort the transfer. This is how you wire up a "Cancel" button in a UI — flip a flag, and the next progress tick stops the transfer.
```cpp
std::atomic<bool> cancelled{false};
auto res = cli.Get("/large-file",
[&](size_t current, size_t total) {
return !cancelled.load();
});
```
> **Note:** `ContentReceiver` and the progress callback can be used together. When you want to stream to a file and show progress at the same time, pass both.
> For a concrete example of saving to a file, see C01. Get the Response Body / Save to a File.
@@ -0,0 +1,50 @@
---
title: "C12. Set Timeouts"
order: 12
status: "draft"
---
The client has three kinds of timeouts, each set independently.
| Kind | API | Default | Meaning |
| --- | --- | --- | --- |
| Connection | `set_connection_timeout` | 300s | Time to wait for the TCP connection to establish |
| Read | `set_read_timeout` | 300s | Time to wait for a single `recv` when receiving the response |
| Write | `set_write_timeout` | 5s | Time to wait for a single `send` when sending the request |
## Basic usage
```cpp
httplib::Client cli("http://localhost:8080");
cli.set_connection_timeout(5, 0); // 5 seconds
cli.set_read_timeout(10, 0); // 10 seconds
cli.set_write_timeout(10, 0); // 10 seconds
auto res = cli.Get("/api/data");
```
Pass seconds and microseconds as two arguments. If you don't need the sub-second part, you can omit the second argument.
## Use `std::chrono`
There's also an overload that takes a `std::chrono` duration directly. It's easier to read — recommended.
```cpp
using namespace std::chrono_literals;
cli.set_connection_timeout(5s);
cli.set_read_timeout(10s);
cli.set_write_timeout(500ms);
```
## Watch out for the long 300s default
Connection and read timeouts default to **300 seconds (5 minutes)**. If the server hangs, you'll be waiting five minutes by default. Shorter values are usually a better idea.
```cpp
cli.set_connection_timeout(3s);
cli.set_read_timeout(10s);
```
> **Warning:** The read timeout covers a single receive call — not the whole request. If data keeps trickling in during a large download, the request can take half an hour without ever hitting the timeout. To cap the total request time, use C13. Set an Overall Timeout.
@@ -0,0 +1,42 @@
---
title: "C13. Set an Overall Timeout"
order: 13
status: "draft"
---
The three timeouts from C12. Set Timeouts all apply to a single `send` or `recv` call. To cap the total time a request can take, use `set_max_timeout()`.
## Basic usage
```cpp
httplib::Client cli("http://localhost:8080");
cli.set_max_timeout(5000); // 5 seconds (in milliseconds)
auto res = cli.Get("/slow-endpoint");
```
The value is in milliseconds. Connection, send, and receive together — the whole request is aborted if it exceeds the limit.
## Use `std::chrono`
There's also an overload that takes a `std::chrono` duration.
```cpp
using namespace std::chrono_literals;
cli.set_max_timeout(5s);
```
## When to use which
`set_read_timeout` fires when no data arrives for a while. If data keeps trickling in bit by bit, it will never fire. An endpoint that sends one byte per second can make `set_read_timeout` useless no matter how short you set it.
`set_max_timeout` caps elapsed time, so it handles those cases cleanly. It's great for calls to external APIs or anywhere you don't want users waiting forever.
```cpp
cli.set_connection_timeout(3s);
cli.set_read_timeout(10s);
cli.set_max_timeout(30s); // abort if the whole request takes over 30s
```
> **Note:** `set_max_timeout()` works alongside the regular timeouts. Short stalls get caught by `set_read_timeout`; long-running requests get capped by `set_max_timeout`. Use both for a safety net.
@@ -0,0 +1,53 @@
---
title: "C14. Understand Connection Reuse and Keep-Alive"
order: 14
status: "draft"
---
When you send multiple requests through the same `httplib::Client` instance, the TCP connection is reused automatically. HTTP/1.1 Keep-Alive does the work for you — you don't pay the TCP and TLS handshake cost on every call.
## Connections are reused automatically
```cpp
httplib::Client cli("https://api.example.com");
auto res1 = cli.Get("/users/1");
auto res2 = cli.Get("/users/2"); // reuses the same connection
auto res3 = cli.Get("/users/3"); // reuses the same connection
```
No special config required. Just hold on to `cli` — internally, the socket stays open across calls. The effect is especially noticeable over HTTPS, where the TLS handshake is expensive.
## Disable Keep-Alive explicitly
To force a fresh connection every time, call `set_keep_alive(false)`. Mostly useful for testing.
```cpp
cli.set_keep_alive(false);
```
For normal use, leave it on (the default).
## Don't create a `Client` per request
If you create a `Client` inside a loop and let it fall out of scope each iteration, you lose the reuse benefit. Create the instance outside the loop.
```cpp
// Bad: a new connection every iteration
for (auto id : ids) {
httplib::Client cli("https://api.example.com");
cli.Get("/users/" + id);
}
// Good: the connection is reused
httplib::Client cli("https://api.example.com");
for (auto id : ids) {
cli.Get("/users/" + id);
}
```
## Concurrent requests
If you want to send requests in parallel from multiple threads, give each thread its own `Client` instance. A single `Client` uses a single TCP connection, so firing concurrent requests at the same instance ends up serializing them anyway.
> **Note:** If the server closes the connection after its Keep-Alive timeout, cpp-httplib reconnects and retries transparently. You don't need to handle this in application code.
@@ -0,0 +1,47 @@
---
title: "C15. Enable Compression"
order: 15
status: "draft"
---
cpp-httplib supports compression when sending and decompression when receiving. You just need to build it with zlib or Brotli enabled.
## Build-time setup
To use compression, define these macros before including `httplib.h`:
```cpp
#define CPPHTTPLIB_ZLIB_SUPPORT // gzip / deflate
#define CPPHTTPLIB_BROTLI_SUPPORT // brotli
#include <httplib.h>
```
You'll also need to link against `zlib` or `brotli`.
## Compress the request body
```cpp
httplib::Client cli("https://api.example.com");
cli.set_compress(true);
std::string big_payload = build_payload();
auto res = cli.Post("/api/data", big_payload, "application/json");
```
With `set_compress(true)`, the body of POST or PUT requests gets gzipped before sending. The server needs to handle compressed bodies too.
## Decompress the response
```cpp
httplib::Client cli("https://api.example.com");
cli.set_decompress(true); // on by default
auto res = cli.Get("/api/data");
std::cout << res->body << std::endl;
```
With `set_decompress(true)`, the client automatically decompresses responses that arrive with `Content-Encoding: gzip` or similar. `res->body` contains the decompressed data.
It's on by default, so normally you don't need to do anything. Set it to `false` only if you want the raw compressed bytes.
> **Warning:** If you build without `CPPHTTPLIB_ZLIB_SUPPORT`, calling `set_compress()` or `set_decompress()` does nothing. If compression isn't working, check the macro definition first.
+52
View File
@@ -0,0 +1,52 @@
---
title: "C16. Send Requests Through a Proxy"
order: 16
status: "draft"
---
To route traffic through a corporate network or a specific path, send requests via an HTTP proxy. Just pass the proxy host and port to `set_proxy()`.
## Basic usage
```cpp
httplib::Client cli("https://api.example.com");
cli.set_proxy("proxy.internal", 8080);
auto res = cli.Get("/users");
```
The request goes through the proxy. For HTTPS, the client uses the CONNECT method to tunnel through — no extra setup required.
## Proxy authentication
If the proxy itself requires authentication, use `set_proxy_basic_auth()` or `set_proxy_bearer_token_auth()`.
```cpp
cli.set_proxy("proxy.internal", 8080);
cli.set_proxy_basic_auth("user", "password");
```
```cpp
cli.set_proxy_bearer_token_auth("token");
```
If cpp-httplib is built with OpenSSL (or another TLS backend), you can also use Digest authentication for the proxy.
```cpp
cli.set_proxy_digest_auth("user", "password");
```
## Combine with end-server authentication
Proxy authentication is separate from authenticating to the end server (C05, C06). When both are needed, set both.
```cpp
cli.set_proxy("proxy.internal", 8080);
cli.set_proxy_basic_auth("proxy-user", "proxy-pass");
cli.set_bearer_token_auth("api-token"); // for the end server
```
`Proxy-Authorization` is sent to the proxy, `Authorization` to the end server.
> **Note:** cpp-httplib does not read `HTTP_PROXY` or `HTTPS_PROXY` environment variables automatically. If you want to honor them, read them in your application and pass the values to `set_proxy()`.
@@ -0,0 +1,63 @@
---
title: "C17. Handle Error Codes"
order: 17
status: "draft"
---
`cli.Get()`, `cli.Post()`, and friends return a `Result`. When the request fails — can't reach the server, times out, etc. — the result is "falsy". To get the specific reason, use `Result::error()`.
## Basic check
```cpp
httplib::Client cli("http://localhost:8080");
auto res = cli.Get("/api/data");
if (res) {
// the request was sent and a response came back
std::cout << "status: " << res->status << std::endl;
} else {
// the network layer failed
std::cerr << "error: " << httplib::to_string(res.error()) << std::endl;
}
```
Use `if (res)` to check success. On failure, `res.error()` returns a `httplib::Error` enum value. Pass it to `to_string()` to get a human-readable description.
## Common errors
| Value | Meaning |
| --- | --- |
| `Error::Connection` | Couldn't connect to the server |
| `Error::ConnectionTimeout` | Connection timeout (`set_connection_timeout`) |
| `Error::Read` / `Error::Write` | Error during send or receive |
| `Error::Timeout` | Overall timeout set via `set_max_timeout` |
| `Error::ExceedRedirectCount` | Too many redirects |
| `Error::SSLConnection` | TLS handshake failed |
| `Error::SSLServerVerification` | Server certificate verification failed |
| `Error::Canceled` | A progress callback returned `false` |
## Network errors vs. HTTP errors
Even when `res` is truthy, the HTTP status code can still be 4xx or 5xx. These are two different things.
```cpp
auto res = cli.Get("/api/data");
if (!res) {
// network error (no response received at all)
std::cerr << "network error: " << httplib::to_string(res.error()) << std::endl;
return 1;
}
if (res->status >= 400) {
// HTTP error (response received, but the status is bad)
std::cerr << "http error: " << res->status << std::endl;
return 1;
}
// success
std::cout << res->body << std::endl;
```
Keep them separated in your head: network-layer errors go through `res.error()`, HTTP-level errors through `res->status`.
> To dig deeper into SSL-related errors, see C18. Handle SSL Errors.
@@ -0,0 +1,53 @@
---
title: "C18. Handle SSL Errors"
order: 18
status: "draft"
---
When an HTTPS request fails, `res.error()` returns values like `Error::SSLConnection` or `Error::SSLServerVerification`. Sometimes that's not enough to pinpoint the cause. That's where `Result::ssl_error()` and `Result::ssl_backend_error()` help.
## Get the SSL error details
```cpp
httplib::Client cli("https://api.example.com");
auto res = cli.Get("/");
if (!res) {
auto err = res.error();
std::cerr << "error: " << httplib::to_string(err) << std::endl;
if (err == httplib::Error::SSLConnection ||
err == httplib::Error::SSLServerVerification) {
std::cerr << "ssl_error: " << res.ssl_error() << std::endl;
std::cerr << "ssl_backend_error: " << res.ssl_backend_error() << std::endl;
}
}
```
`ssl_error()` returns the error code from the SSL library (e.g., OpenSSL's `SSL_get_error()`). `ssl_backend_error()` gives you the backend's more detailed error value — for OpenSSL, that's `ERR_get_error()`.
## Format OpenSSL errors as strings
When you have a value from `ssl_backend_error()`, pass it to OpenSSL's `ERR_error_string()` to get a readable message.
```cpp
#include <openssl/err.h>
if (res.ssl_backend_error() != 0) {
char buf[256];
ERR_error_string_n(res.ssl_backend_error(), buf, sizeof(buf));
std::cerr << "openssl: " << buf << std::endl;
}
```
## Common causes
| Symptom | Usual suspect |
| --- | --- |
| `SSLServerVerification` | CA certificate path isn't configured, or the cert is self-signed |
| `SSLServerHostnameVerification` | The cert's CN/SAN doesn't match the host |
| `SSLConnection` | TLS version mismatch, no shared cipher suite |
> **Note:** `ssl_backend_error()` was previously called `ssl_openssl_error()`. The old name is deprecated — use `ssl_backend_error()` now.
> To change certificate verification settings, see T02. Control SSL Certificate Verification.
@@ -0,0 +1,57 @@
---
title: "C19. Set a Logger on the Client"
order: 19
status: "draft"
---
To log requests sent and responses received by the client, use `set_logger()`. If you only care about errors, there's a separate `set_error_logger()`.
## Log requests and responses
```cpp
httplib::Client cli("https://api.example.com");
cli.set_logger([](const httplib::Request &req, const httplib::Response &res) {
std::cout << req.method << " " << req.path
<< " -> " << res.status << std::endl;
});
auto res = cli.Get("/users");
```
The callback you pass to `set_logger()` fires once for each completed request. You get both the request and the response as arguments — so you can log the method, path, status, headers, body, or whatever else you need.
## Catch errors only
When a network-layer error happens (like `Error::Connection`), `set_logger()` is **not** called — there's no response to log. For those cases, use `set_error_logger()`.
```cpp
cli.set_error_logger([](const httplib::Error &err, const httplib::Request *req) {
std::cerr << "error: " << httplib::to_string(err);
if (req) {
std::cerr << " (" << req->method << " " << req->path << ")";
}
std::cerr << std::endl;
});
```
The second argument `req` can be null — it happens when the failure occurred before the request was built. Always null-check before dereferencing.
## Use both together
A nice pattern is to log successes through one, failures through the other.
```cpp
cli.set_logger([](const auto &req, const auto &res) {
std::cout << "[ok] " << req.method << " " << req.path
<< " " << res.status << std::endl;
});
cli.set_error_logger([](const auto &err, const auto *req) {
std::cerr << "[ng] " << httplib::to_string(err);
if (req) std::cerr << " " << req->method << " " << req->path;
std::cerr << std::endl;
});
```
> **Note:** The log callbacks run synchronously on the same thread as the request. Heavy work inside them slows the request down — push it to a background queue if you need to do anything expensive.
@@ -0,0 +1,87 @@
---
title: "E01. Implement an SSE Server"
order: 47
status: "draft"
---
Server-Sent Events (SSE) is a simple protocol for pushing events one-way from server to client. The connection stays open, and the server can send data whenever it wants. It's lighter than WebSocket and fits entirely within HTTP — a nice combination.
cpp-httplib doesn't have a dedicated SSE server API, but you can implement one with `set_chunked_content_provider()` and `text/event-stream`.
## Basic SSE server
```cpp
svr.Get("/events", [](const httplib::Request &req, httplib::Response &res) {
res.set_chunked_content_provider(
"text/event-stream",
[](size_t offset, httplib::DataSink &sink) {
std::string message = "data: hello\n\n";
sink.write(message.data(), message.size());
std::this_thread::sleep_for(std::chrono::seconds(1));
return true;
});
});
```
Three things matter here:
1. Content-Type is `text/event-stream`
2. Messages follow the format `data: <content>\n\n` (the double newline separates events)
3. Each `sink.write()` delivers data to the client
The provider lambda keeps being called as long as the connection is alive.
## A continuous stream
Here's a simple example that sends the current time once per second.
```cpp
svr.Get("/time", [](const httplib::Request &req, httplib::Response &res) {
res.set_chunked_content_provider(
"text/event-stream",
[&req](size_t offset, httplib::DataSink &sink) {
if (req.is_connection_closed()) {
sink.done();
return true;
}
auto now = std::chrono::system_clock::now();
auto t = std::chrono::system_clock::to_time_t(now);
std::string msg = "data: " + std::string(std::ctime(&t)) + "\n";
sink.write(msg.data(), msg.size());
std::this_thread::sleep_for(std::chrono::seconds(1));
return true;
});
});
```
When the client disconnects, call `sink.done()` to stop. Details in S16. Detect When the Client Has Disconnected.
## Heartbeats via comment lines
Lines starting with `:` are SSE comments — clients ignore them, but they **keep the connection alive**. Handy for preventing proxies and load balancers from closing idle connections.
```cpp
// heartbeat every 30 seconds
if (tick_count % 30 == 0) {
std::string ping = ": ping\n\n";
sink.write(ping.data(), ping.size());
}
```
## Relationship with the thread pool
SSE connections stay open, so each client holds a worker thread. For lots of concurrent connections, enable dynamic scaling on the thread pool.
```cpp
svr.new_task_queue = [] {
return new httplib::ThreadPool(8, 128);
};
```
See S21. Configure the Thread Pool.
> **Note:** When `data:` contains newlines, split it into multiple `data:` lines — one per line. This is how the SSE spec requires multiline data to be transmitted.
> For event names, see E02. Use Named Events in SSE. For the client side, see E04. Receive SSE on the Client.
@@ -0,0 +1,84 @@
---
title: "E02. Use Named Events in SSE"
order: 48
status: "draft"
---
SSE lets you send multiple kinds of events over the same stream. Give each one a name with the `event:` field, and the client can dispatch to a different handler per type. Great for things like "new message", "user joined", "user left" in a chat app.
## Send events with names
```cpp
auto send_event = [](httplib::DataSink &sink,
const std::string &event,
const std::string &data) {
std::string msg = "event: " + event + "\n"
+ "data: " + data + "\n\n";
sink.write(msg.data(), msg.size());
};
svr.Get("/chat/stream", [&](const httplib::Request &req, httplib::Response &res) {
res.set_chunked_content_provider(
"text/event-stream",
[&, send_event](size_t offset, httplib::DataSink &sink) {
send_event(sink, "message", "Hello!");
std::this_thread::sleep_for(std::chrono::seconds(2));
send_event(sink, "join", "alice");
std::this_thread::sleep_for(std::chrono::seconds(2));
send_event(sink, "leave", "bob");
std::this_thread::sleep_for(std::chrono::seconds(2));
return true;
});
});
```
A message is `event:``data:` → blank line. If you omit `event:`, the client treats it as a default `"message"` event.
## Attach IDs for reconnect
When you include an `id:` field, the client automatically sends it back as `Last-Event-ID` on reconnect, telling the server "here's how far I got."
```cpp
auto send_event = [](httplib::DataSink &sink,
const std::string &event,
const std::string &data,
const std::string &id) {
std::string msg = "id: " + id + "\n"
+ "event: " + event + "\n"
+ "data: " + data + "\n\n";
sink.write(msg.data(), msg.size());
};
send_event(sink, "message", "Hello!", "42");
```
The ID format is up to you. Monotonic counters or UUIDs both work — just pick something unique and orderable on the server side. See E03. Handle SSE Reconnection for details.
## JSON payloads in data
For structured data, the usual move is to put JSON in `data:`.
```cpp
nlohmann::json payload = {
{"user", "alice"},
{"text", "Hello!"},
};
send_event(sink, "message", payload.dump(), "42");
```
On the client, parse the incoming `data` as JSON to get the original object back.
## Data with newlines
If the data value contains newlines, split it across multiple `data:` lines.
```cpp
std::string msg = "data: line1\n"
"data: line2\n"
"data: line3\n\n";
sink.write(msg.data(), msg.size());
```
On the client side, these come back as a single `data` string with newlines.
> **Note:** Using `event:` makes client-side dispatch cleaner, but it also helps in the browser DevTools — events are easier to filter by type. That matters more than you'd expect while debugging.
@@ -0,0 +1,85 @@
---
title: "E03. Handle SSE Reconnection"
order: 49
status: "draft"
---
SSE connections drop for all sorts of network reasons. Clients automatically try to reconnect, so it's a good idea to make your server resume from where it left off.
## Read `Last-Event-ID`
When the client reconnects, it sends the ID of the last event it received in the `Last-Event-ID` header. The server reads that and picks up from the next one.
```cpp
svr.Get("/events", [](const httplib::Request &req, httplib::Response &res) {
auto last_id = req.get_header_value("Last-Event-ID");
int start = last_id.empty() ? 0 : std::stoi(last_id) + 1;
res.set_chunked_content_provider(
"text/event-stream",
[start](size_t offset, httplib::DataSink &sink) mutable {
static int next_id = 0;
if (next_id < start) { next_id = start; }
std::string msg = "id: " + std::to_string(next_id) + "\n"
+ "data: event " + std::to_string(next_id) + "\n\n";
sink.write(msg.data(), msg.size());
++next_id;
std::this_thread::sleep_for(std::chrono::seconds(1));
return true;
});
});
```
On the first connect, `Last-Event-ID` is empty, so start from `0`. On reconnect, resume from the next ID. Event history is the server's responsibility — you need to keep recent events around somewhere.
## Set the reconnect interval
Sending a `retry:` field tells the client how long to wait before reconnecting, in milliseconds.
```cpp
std::string msg = "retry: 5000\n\n"; // reconnect after 5 seconds
sink.write(msg.data(), msg.size());
```
Usually you send this once at the start. During peak load or maintenance windows, a longer retry interval helps reduce reconnect storms.
## Buffer recent events
To support reconnection, keep a rolling buffer of recent events on the server.
```cpp
struct EventBuffer {
std::mutex mu;
std::deque<std::pair<int, std::string>> events; // {id, data}
int next_id = 0;
void push(const std::string &data) {
std::lock_guard<std::mutex> lock(mu);
events.push_back({next_id++, data});
if (events.size() > 1000) { events.pop_front(); }
}
std::vector<std::pair<int, std::string>> since(int id) {
std::lock_guard<std::mutex> lock(mu);
std::vector<std::pair<int, std::string>> out;
for (const auto &e : events) {
if (e.first >= id) { out.push_back(e); }
}
return out;
}
};
```
When a client reconnects, call `since(last_id)` to send any events it missed.
## How much to keep
The buffer size is a tradeoff between memory and how far back a client can resume. It depends on the use case:
- Real-time chat: a few minutes to half an hour
- Notifications: the last N items
- Trading data: persist to a database and pull from there
> **Warning:** `Last-Event-ID` is a client-provided value — don't trust it blindly. If you read it as a number, validate the range. If it's a string, sanitize it.
@@ -0,0 +1,99 @@
---
title: "E04. Receive SSE on the Client"
order: 50
status: "draft"
---
cpp-httplib ships a dedicated `sse::SSEClient` class. It handles auto-reconnect, per-event-name dispatch, and `Last-Event-ID` tracking for you — so receiving SSE is painless.
## Basic usage
```cpp
#include <httplib.h>
httplib::Client cli("http://localhost:8080");
httplib::sse::SSEClient sse(cli, "/events");
sse.on_message([](const httplib::sse::SSEMessage &msg) {
std::cout << "data: " << msg.data << std::endl;
});
sse.start(); // blocking
```
Build an `SSEClient` with a `Client` and a path, register a callback with `on_message()`, and call `start()`. The event loop kicks in and automatically reconnects if the connection drops.
## Dispatch by event name
When the server sends events with an `event:` field, register a handler per name via `on_event()`.
```cpp
sse.on_event("message", [](const auto &msg) {
std::cout << "chat: " << msg.data << std::endl;
});
sse.on_event("join", [](const auto &msg) {
std::cout << msg.data << " joined" << std::endl;
});
sse.on_event("leave", [](const auto &msg) {
std::cout << msg.data << " left" << std::endl;
});
```
`on_message()` serves as a generic fallback for unnamed events (the default `message` type).
## Connection lifecycle and errors
```cpp
sse.on_open([] {
std::cout << "connected" << std::endl;
});
sse.on_error([](httplib::Error err) {
std::cerr << "error: " << httplib::to_string(err) << std::endl;
});
```
Hook into connection open and error events. Even when the error handler fires, `SSEClient` keeps trying to reconnect in the background.
## Run asynchronously
If you don't want to block the main thread, use `start_async()`.
```cpp
sse.start_async();
// main thread continues to do other things
do_other_work();
// when you're done, stop it
sse.stop();
```
`start_async()` spawns a background thread to run the event loop. Use `stop()` to shut it down cleanly.
## Configure reconnection
You can tune the reconnect interval and maximum retries.
```cpp
sse.set_reconnect_interval(5000); // 5 seconds
sse.set_max_reconnect_attempts(10); // up to 10 (0 = unlimited)
```
If the server sends a `retry:` field, that takes precedence.
## Automatic Last-Event-ID
`SSEClient` tracks the `id` of each received event internally and sends it back as `Last-Event-ID` on reconnect. As long as the server sends events with `id:`, this all works automatically.
```cpp
std::cout << "last id: " << sse.last_event_id() << std::endl;
```
Use `last_event_id()` to read the current value.
> **Note:** `SSEClient::start()` blocks, which is fine for a one-off command-line tool. For GUI apps or embedded in a server, the `start_async()` + `stop()` pair is the usual pattern.
> For the server side, see E01. Implement an SSE Server.
+96
View File
@@ -0,0 +1,96 @@
---
title: "Cookbook"
order: 0
status: "draft"
---
A collection of recipes that answer "How do I...?" questions. Each recipe is self-contained — read only what you need. For an introduction to the basics, see the [Tour](../tour/).
## Client
### Basics
- [C01. Get the response body / save to a file](c01-get-response-body)
- [C02. Send and receive JSON](c02-json)
- [C03. Set default headers](c03-default-headers)
- [C04. Follow redirects](c04-follow-location)
### Authentication
- [C05. Use Basic authentication](c05-basic-auth)
- [C06. Call an API with a Bearer token](c06-bearer-token)
### File Upload
- [C07. Upload a file as multipart form data](c07-multipart-upload)
- [C08. POST a file as raw binary](c08-post-file-body)
- [C09. Send the body with chunked transfer](c09-chunked-upload)
### Streaming & Progress
- [C10. Receive a response as a stream](c10-stream-response)
- [C11. Use the progress callback](c11-progress-callback)
### Connection & Performance
- [C12. Set timeouts](c12-timeouts)
- [C13. Set an overall timeout](c13-max-timeout)
- [C14. Understand connection reuse and Keep-Alive behavior](c14-keep-alive)
- [C15. Enable compression](c15-compression)
- [C16. Send requests through a proxy](c16-proxy)
### Error Handling & Debugging
- [C17. Handle error codes](c17-error-codes)
- [C18. Handle SSL errors](c18-ssl-errors)
- [C19. Set up client logging](c19-client-logger)
## Server
### Basics
- [S01. Register GET / POST / PUT / DELETE handlers](s01-handlers)
- [S02. Receive JSON requests and return JSON responses](s02-json-api)
- [S03. Use path parameters](s03-path-params)
- [S04. Set up a static file server](s04-static-files)
### Streaming & Files
- [S05. Stream a large file in the response](s05-stream-response)
- [S06. Return a file download response](s06-download-response)
- [S07. Receive multipart data as a stream](s07-multipart-reader)
- [S08. Return a compressed response](s08-compress-response)
### Handler Chain
- [S09. Add pre-processing to all routes](s09-pre-routing)
- [S10. Add response headers with a post-routing handler](s10-post-routing)
- [S11. Authenticate per route with a pre-request handler](s11-pre-request)
- [S12. Pass data between handlers with `res.user_data`](s12-user-data)
### Error Handling & Debugging
- [S13. Return custom error pages](s13-error-handler)
- [S14. Catch exceptions](s14-exception-handler)
- [S15. Log requests](s15-server-logger)
- [S16. Detect client disconnection](s16-disconnect)
### Operations & Tuning
- [S17. Bind to any available port](s17-bind-any-port)
- [S18. Control startup order with `listen_after_bind`](s18-listen-after-bind)
- [S19. Shut down gracefully](s19-graceful-shutdown)
- [S20. Tune Keep-Alive](s20-keep-alive)
- [S21. Configure the thread pool](s21-thread-pool)
- [S22. Talk over a Unix domain socket](s22-unix-socket)
## TLS / Security
- [T01. Choosing between OpenSSL, mbedTLS, and wolfSSL](t01-tls-backends)
- [T02. Control SSL certificate verification](t02-cert-verification)
- [T03. Start an SSL/TLS server](t03-ssl-server)
- [T04. Configure mTLS](t04-mtls)
- [T05. Access the peer certificate on the server](t05-peer-cert)
## SSE
- [E01. Implement an SSE server](e01-sse-server)
- [E02. Use named events in SSE](e02-sse-event-names)
- [E03. Handle SSE reconnection](e03-sse-reconnect)
- [E04. Receive SSE on the client](e04-sse-client)
## WebSocket
- [W01. Implement a WebSocket echo server and client](w01-websocket-echo)
- [W02. Set a WebSocket heartbeat](w02-websocket-ping)
- [W03. Handle connection close](w03-websocket-close)
- [W04. Send and receive binary frames](w04-websocket-binary)
@@ -0,0 +1,66 @@
---
title: "S01. Register GET / POST / PUT / DELETE Handlers"
order: 20
status: "draft"
---
With `httplib::Server`, you register a handler per HTTP method. Just pass a pattern and a lambda to `Get()`, `Post()`, `Put()`, or `Delete()`.
## Basic usage
```cpp
#include <httplib.h>
int main() {
httplib::Server svr;
svr.Get("/hello", [](const httplib::Request &req, httplib::Response &res) {
res.set_content("Hello, World!", "text/plain");
});
svr.Post("/api/items", [](const httplib::Request &req, httplib::Response &res) {
// req.body holds the request body
res.status = 201;
res.set_content("Created", "text/plain");
});
svr.Put("/api/items/1", [](const httplib::Request &req, httplib::Response &res) {
res.set_content("Updated", "text/plain");
});
svr.Delete("/api/items/1", [](const httplib::Request &req, httplib::Response &res) {
res.status = 204;
});
svr.listen("0.0.0.0", 8080);
}
```
Handlers take `(const Request&, Response&)`. Use `res.set_content()` to set the body and Content-Type, and `res.status` for the status code. `listen()` starts the server and blocks.
## Read query parameters
```cpp
svr.Get("/search", [](const httplib::Request &req, httplib::Response &res) {
auto q = req.get_param_value("q");
auto limit = req.get_param_value("limit");
res.set_content("q=" + q + ", limit=" + limit, "text/plain");
});
```
`req.get_param_value()` pulls a value from the query string. Use `req.has_param("q")` if you want to check existence first.
## Read request headers
```cpp
svr.Get("/me", [](const httplib::Request &req, httplib::Response &res) {
auto ua = req.get_header_value("User-Agent");
res.set_content("UA: " + ua, "text/plain");
});
```
To add a response header, use `res.set_header("Name", "Value")`.
> **Note:** `listen()` is a blocking call. To run it on a different thread, wrap it in `std::thread`. If you need non-blocking startup, see S18. Control Startup Order with `listen_after_bind`.
> To use path parameters like `/users/:id`, see S03. Use Path Parameters.
@@ -0,0 +1,74 @@
---
title: "S02. Receive a JSON Request and Return a JSON Response"
order: 21
status: "draft"
---
cpp-httplib doesn't include a JSON parser. On the server side, combine it with something like [nlohmann/json](https://github.com/nlohmann/json). The examples below use `nlohmann/json`.
## Receive and return JSON
```cpp
#include <httplib.h>
#include <nlohmann/json.hpp>
int main() {
httplib::Server svr;
svr.Post("/api/users", [](const httplib::Request &req, httplib::Response &res) {
try {
auto in = nlohmann::json::parse(req.body);
nlohmann::json out = {
{"id", 42},
{"name", in["name"]},
{"created_at", "2026-04-10T12:00:00Z"},
};
res.status = 201;
res.set_content(out.dump(), "application/json");
} catch (const std::exception &e) {
res.status = 400;
res.set_content("{\"error\":\"invalid json\"}", "application/json");
}
});
svr.listen("0.0.0.0", 8080);
}
```
`req.body` is a plain `std::string`, so you pass it straight to your JSON library. For the response, `dump()` to a string and set the Content-Type to `application/json`.
## Check the Content-Type
```cpp
svr.Post("/api/users", [](const httplib::Request &req, httplib::Response &res) {
auto content_type = req.get_header_value("Content-Type");
if (content_type.find("application/json") == std::string::npos) {
res.status = 415; // Unsupported Media Type
return;
}
// ...
});
```
When you strictly want JSON only, verify the Content-Type up front.
## A helper for JSON responses
If you're writing the same pattern repeatedly, a small helper saves typing.
```cpp
auto send_json = [](httplib::Response &res, int status, const nlohmann::json &j) {
res.status = status;
res.set_content(j.dump(), "application/json");
};
svr.Get("/api/health", [&](const auto &req, auto &res) {
send_json(res, 200, {{"status", "ok"}});
});
```
> **Note:** A large JSON body ends up entirely in `req.body`, which means it all sits in memory. For huge payloads, consider streaming reception — see S07. Receive Multipart Data as a Stream.
> For the client side, see C02. Send and Receive JSON.
@@ -0,0 +1,53 @@
---
title: "S03. Use Path Parameters"
order: 22
status: "draft"
---
For dynamic URLs like `/users/:id` — the staple of REST APIs — just put `:name` in the path pattern. The matched values end up in `req.path_params`.
## Basic usage
```cpp
svr.Get("/users/:id", [](const httplib::Request &req, httplib::Response &res) {
auto id = req.path_params.at("id");
res.set_content("user id: " + id, "text/plain");
});
```
A request to `/users/42` fills `req.path_params["id"]` with `"42"`. `path_params` is a `std::unordered_map<std::string, std::string>`, so use `at()` to read it.
## Multiple parameters
You can have as many as you need.
```cpp
svr.Get("/orgs/:org/repos/:repo", [](const httplib::Request &req, httplib::Response &res) {
auto org = req.path_params.at("org");
auto repo = req.path_params.at("repo");
res.set_content(org + "/" + repo, "text/plain");
});
```
This matches paths like `/orgs/anthropic/repos/cpp-httplib`.
## Regex patterns
For more flexible matching, use a `std::regex`-based pattern.
```cpp
svr.Get(R"(/users/(\d+))", [](const httplib::Request &req, httplib::Response &res) {
auto id = req.matches[1];
res.set_content("user id: " + std::string(id), "text/plain");
});
```
Parentheses in the pattern become captures in `req.matches`. `req.matches[0]` is the full match; `req.matches[1]` onward are the captures.
## Which to use
- For plain IDs or slugs, `:name` is enough — readable, and the shape is obvious
- Use regex when you want to constrain the URL to, say, numbers only or a UUID format
- Mixing both can get confusing — stick with one style per project
> **Note:** Path parameters come in as strings. If you need an integer, convert with `std::stoi()` and don't forget to handle conversion errors.
@@ -0,0 +1,55 @@
---
title: "S04. Serve Static Files"
order: 23
status: "draft"
---
To serve static files like HTML, CSS, and images, use `set_mount_point()`. Just map a URL path to a local directory, and the whole directory becomes accessible.
## Basic usage
```cpp
httplib::Server svr;
svr.set_mount_point("/", "./public");
svr.listen("0.0.0.0", 8080);
```
`./public/index.html` is now reachable at `http://localhost:8080/index.html`, and `./public/css/style.css` at `http://localhost:8080/css/style.css`. The directory layout maps directly to URLs.
## Multiple mount points
You can register more than one mount point.
```cpp
svr.set_mount_point("/", "./public");
svr.set_mount_point("/assets", "./dist/assets");
svr.set_mount_point("/uploads", "./var/uploads");
```
You can even mount multiple directories at the same path — they're searched in registration order, and the first hit wins.
## Combine with API handlers
Static files and API handlers coexist happily. Handlers registered with `Get()` and friends take priority; the mount points are searched only when nothing matches.
```cpp
svr.Get("/api/users", [](const auto &req, auto &res) {
res.set_content("[]", "application/json");
});
svr.set_mount_point("/", "./public");
```
This gives you an SPA-friendly setup: `/api/*` hits the handlers, everything else is served from `./public/`.
## Add MIME types
cpp-httplib ships with a built-in extension-to-Content-Type map, but you can add your own.
```cpp
svr.set_file_extension_and_mimetype_mapping("wasm", "application/wasm");
```
> **Warning:** The static file server methods are **not thread-safe**. Don't call them after `listen()` — configure everything before starting the server.
> For download-style responses, see S06. Return a File Download Response.
@@ -0,0 +1,63 @@
---
title: "S05. Stream a Large File in the Response"
order: 24
status: "draft"
---
When the response is a huge file or data generated on the fly, loading the whole thing into memory isn't realistic. Use `Response::set_content_provider()` to produce data in chunks as you send it.
## When the size is known
```cpp
svr.Get("/download", [](const httplib::Request &req, httplib::Response &res) {
size_t total_size = get_file_size("large.bin");
res.set_content_provider(
total_size, "application/octet-stream",
[](size_t offset, size_t length, httplib::DataSink &sink) {
auto data = read_range_from_file("large.bin", offset, length);
sink.write(data.data(), data.size());
return true;
});
});
```
The lambda is called repeatedly with `offset` and `length`. Read just that range and write it to `sink`. Only a small chunk sits in memory at any given time.
## Just send a file
If you only want to serve a file, `set_file_content()` is far simpler.
```cpp
svr.Get("/download", [](const httplib::Request &req, httplib::Response &res) {
res.set_file_content("large.bin", "application/octet-stream");
});
```
It streams internally, so even huge files are safe. Omit the Content-Type and it's guessed from the extension.
## When the size is unknown — chunked transfer
For data generated on the fly, where you don't know the total size up front, use `set_chunked_content_provider()`. It's sent with HTTP chunked transfer encoding.
```cpp
svr.Get("/events", [](const httplib::Request &req, httplib::Response &res) {
res.set_chunked_content_provider(
"text/plain",
[](size_t offset, httplib::DataSink &sink) {
auto chunk = produce_next_chunk();
if (chunk.empty()) {
sink.done(); // done sending
return true;
}
sink.write(chunk.data(), chunk.size());
return true;
});
});
```
Call `sink.done()` to signal the end.
> **Note:** The provider lambda is called multiple times. Watch out for the lifetime of captured variables — wrap them in a `std::shared_ptr` if needed.
> To serve the file as a download, see S06. Return a File Download Response.
@@ -0,0 +1,50 @@
---
title: "S06. Return a File Download Response"
order: 25
status: "draft"
---
To force a browser to show a **download dialog** instead of rendering inline, send a `Content-Disposition` header. There's no special cpp-httplib API for this — it's just a header.
## Basic usage
```cpp
svr.Get("/download/report", [](const httplib::Request &req, httplib::Response &res) {
res.set_header("Content-Disposition", "attachment; filename=\"report.pdf\"");
res.set_file_content("reports/2026-04.pdf", "application/pdf");
});
```
`Content-Disposition: attachment` makes the browser pop up a "Save As" dialog. The `filename=` parameter becomes the default save name.
## Non-ASCII file names
For file names with non-ASCII characters or spaces, use the RFC 5987 `filename*` form.
```cpp
svr.Get("/download/report", [](const httplib::Request &req, httplib::Response &res) {
res.set_header(
"Content-Disposition",
"attachment; filename=\"report.pdf\"; "
"filename*=UTF-8''%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88.pdf");
res.set_file_content("reports/2026-04.pdf", "application/pdf");
});
```
The part after `filename*=UTF-8''` is URL-encoded UTF-8. Keep the ASCII `filename=` too, as a fallback for older browsers.
## Download dynamically generated data
You don't need a real file — you can serve a generated string as a download directly.
```cpp
svr.Get("/export.csv", [](const httplib::Request &req, httplib::Response &res) {
std::string csv = build_csv();
res.set_header("Content-Disposition", "attachment; filename=\"export.csv\"");
res.set_content(csv, "text/csv");
});
```
This is the classic pattern for CSV exports.
> **Note:** Some browsers will trigger a download based on Content-Type alone, even without `Content-Disposition`. Conversely, setting `inline` tries to render the content in the browser when possible.
@@ -0,0 +1,71 @@
---
title: "S07. Receive Multipart Data as a Stream"
order: 26
status: "draft"
---
A naive upload handler puts the whole request into `req.body`, which blows up memory for large files. Use `HandlerWithContentReader` to receive the body chunk by chunk.
## Basic usage
```cpp
svr.Post("/upload",
[](const httplib::Request &req, httplib::Response &res,
const httplib::ContentReader &content_reader) {
if (req.is_multipart_form_data()) {
content_reader(
// headers of each part
[&](const httplib::FormData &file) {
std::cout << "name: " << file.name
<< ", filename: " << file.filename << std::endl;
return true;
},
// body of each part (called multiple times)
[&](const char *data, size_t len) {
// write to disk here, for example
return true;
});
} else {
// plain request body
content_reader([&](const char *data, size_t len) {
return true;
});
}
res.set_content("ok", "text/plain");
});
```
The `content_reader` has two call shapes. For multipart data, pass two callbacks (one for headers, one for body). For plain bodies, pass just one.
## Write directly to disk
Here's how to stream an uploaded file to disk.
```cpp
svr.Post("/upload",
[](const httplib::Request &req, httplib::Response &res,
const httplib::ContentReader &content_reader) {
std::ofstream ofs;
content_reader(
[&](const httplib::FormData &file) {
if (!file.filename.empty()) {
ofs.open("uploads/" + file.filename, std::ios::binary);
}
return static_cast<bool>(ofs);
},
[&](const char *data, size_t len) {
ofs.write(data, len);
return static_cast<bool>(ofs);
});
res.set_content("uploaded", "text/plain");
});
```
Only a small chunk sits in memory at any moment, so gigabyte-scale files are no problem.
> **Warning:** When you use `HandlerWithContentReader`, `req.body` stays **empty**. Handle the body yourself inside the callbacks.
> For the client side of multipart uploads, see C07. Upload a File as Multipart Form Data.
@@ -0,0 +1,53 @@
---
title: "S08. Return a Compressed Response"
order: 27
status: "draft"
---
cpp-httplib automatically compresses response bodies when the client indicates support via `Accept-Encoding`. The handler doesn't need to do anything special. Supported encodings are gzip, Brotli, and Zstd.
## Build-time setup
To enable compression, define the relevant macros before including `httplib.h`:
```cpp
#define CPPHTTPLIB_ZLIB_SUPPORT // gzip
#define CPPHTTPLIB_BROTLI_SUPPORT // brotli
#define CPPHTTPLIB_ZSTD_SUPPORT // zstd
#include <httplib.h>
```
You'll also need to link `zlib`, `brotli`, and `zstd` respectively. Enable only what you need.
## Usage
```cpp
svr.Get("/api/data", [](const httplib::Request &req, httplib::Response &res) {
std::string body = build_large_response();
res.set_content(body, "application/json");
});
```
That's it. If the client sent `Accept-Encoding: gzip`, cpp-httplib compresses the response with gzip automatically. `Content-Encoding: gzip` and `Vary: Accept-Encoding` are added for you.
## Encoding priority
When the client accepts multiple encodings, cpp-httplib picks in this order (among those enabled at build time): Brotli → Zstd → gzip. Your code doesn't need to care — you always get the most efficient option available.
## Streaming responses are compressed too
Streaming responses via `set_chunked_content_provider()` get the same automatic compression.
```cpp
svr.Get("/events", [](const httplib::Request &req, httplib::Response &res) {
res.set_chunked_content_provider(
"text/plain",
[](size_t offset, httplib::DataSink &sink) {
// ...
});
});
```
> **Note:** Tiny responses barely benefit from compression and just waste CPU time. cpp-httplib skips compression for bodies that are too small to bother with.
> For the client-side counterpart, see C15. Enable Compression.
@@ -0,0 +1,54 @@
---
title: "S09. Add Pre-Processing to All Routes"
order: 28
status: "draft"
---
Sometimes you want the same logic to run before every request — auth checks, logging, rate limiting. Register those with `set_pre_routing_handler()`.
## Basic usage
```cpp
svr.set_pre_routing_handler(
[](const httplib::Request &req, httplib::Response &res) {
std::cout << req.method << " " << req.path << std::endl;
return httplib::Server::HandlerResponse::Unhandled;
});
```
The pre-routing handler runs **before routing**. It catches every request — including ones that don't match any handler.
The `HandlerResponse` return value is key:
- Return `Unhandled` → continue normally (routing and the actual handler run)
- Return `Handled` → the response is considered complete, skip the rest
## Use it for authentication
Put your shared auth check in one place.
```cpp
svr.set_pre_routing_handler(
[](const httplib::Request &req, httplib::Response &res) {
if (req.path.rfind("/public", 0) == 0) {
return httplib::Server::HandlerResponse::Unhandled; // no auth needed
}
auto auth = req.get_header_value("Authorization");
if (auth.empty()) {
res.status = 401;
res.set_content("unauthorized", "text/plain");
return httplib::Server::HandlerResponse::Handled;
}
return httplib::Server::HandlerResponse::Unhandled;
});
```
If auth fails, return `Handled` to respond with 401 immediately. If it passes, return `Unhandled` and let routing take over.
## For per-route auth
If you want different auth rules per route rather than a single global check, `set_pre_request_handler()` is a better fit. See S11. Authenticate Per Route with a Pre-Request Handler.
> **Note:** If all you want is to modify the response, `set_post_routing_handler()` is the right tool. See S10. Add Response Headers with a Post-Routing Handler.
@@ -0,0 +1,56 @@
---
title: "S10. Add Response Headers with a Post-Routing Handler"
order: 29
status: "draft"
---
Sometimes you want to add shared headers to the response after the handler has run — CORS headers, security headers, a request ID, and so on. That's what `set_post_routing_handler()` is for.
## Basic usage
```cpp
svr.set_post_routing_handler(
[](const httplib::Request &req, httplib::Response &res) {
res.set_header("X-Request-ID", generate_request_id());
});
```
The post-routing handler runs **after the route handler, before the response is sent**. From here you can call `res.set_header()` or `res.headers.erase()` to add or remove headers across every response in one place.
## Add CORS headers
CORS is a classic use case.
```cpp
svr.set_post_routing_handler(
[](const httplib::Request &req, httplib::Response &res) {
res.set_header("Access-Control-Allow-Origin", "*");
res.set_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
res.set_header("Access-Control-Allow-Headers", "Content-Type, Authorization");
});
```
For the preflight `OPTIONS` requests, register a separate handler — or handle them in the pre-routing handler.
```cpp
svr.Options("/.*", [](const auto &req, auto &res) {
res.status = 204;
});
```
## Bundle your security headers
Manage browser security headers in one spot.
```cpp
svr.set_post_routing_handler(
[](const httplib::Request &req, httplib::Response &res) {
res.set_header("X-Content-Type-Options", "nosniff");
res.set_header("X-Frame-Options", "DENY");
res.set_header("Referrer-Policy", "strict-origin-when-cross-origin");
});
```
No matter which handler produced the response, the same headers get attached.
> **Note:** The post-routing handler also runs for responses that didn't match any route and for responses from error handlers. That's exactly what you want when you need certain headers on every response, guaranteed.
@@ -0,0 +1,47 @@
---
title: "S11. Authenticate Per Route with a Pre-Request Handler"
order: 30
status: "draft"
---
The `set_pre_routing_handler()` from S09 runs **before routing**, so it has no idea which route matched. When you want per-route behavior, `set_pre_request_handler()` is what you need.
## Pre-routing vs. pre-request
| Hook | When it runs | Route info |
| --- | --- | --- |
| `set_pre_routing_handler` | Before routing | Not available |
| `set_pre_request_handler` | After routing, right before the route handler | Available via `req.matched_route` |
In a pre-request handler, `req.matched_route` holds the **pattern string** that matched. You can vary behavior based on the route definition itself.
## Switch auth per route
```cpp
svr.set_pre_request_handler(
[](const httplib::Request &req, httplib::Response &res) {
// require auth for routes starting with /admin
if (req.matched_route.rfind("/admin", 0) == 0) {
auto token = req.get_header_value("Authorization");
if (!is_admin_token(token)) {
res.status = 403;
res.set_content("forbidden", "text/plain");
return httplib::Server::HandlerResponse::Handled;
}
}
return httplib::Server::HandlerResponse::Unhandled;
});
```
`matched_route` is the pattern **before** path parameters are expanded (e.g. `/admin/users/:id`). You compare against the route definition, not the actual request path, so IDs or names don't throw you off.
## Return values
Same as pre-routing — return `HandlerResponse`.
- `Unhandled`: continue (the route handler runs)
- `Handled`: we're done, skip the route handler
## Passing auth info to the route handler
To pass decoded user info into the route handler, use `res.user_data`. See S12. Pass Data Between Handlers with `res.user_data`.
@@ -0,0 +1,56 @@
---
title: "S12. Pass Data Between Handlers with res.user_data"
order: 31
status: "draft"
---
Say your pre-request handler decodes an auth token and you want the route handler to use the result. That "data handoff between handlers" is what `res.user_data` is for — it holds values of arbitrary types.
## Basic usage
```cpp
struct AuthUser {
std::string id;
std::string name;
bool is_admin;
};
svr.set_pre_request_handler(
[](const httplib::Request &req, httplib::Response &res) {
auto token = req.get_header_value("Authorization");
auto user = decode_token(token); // decode the auth token
res.user_data.set("user", user);
return httplib::Server::HandlerResponse::Unhandled;
});
svr.Get("/me", [](const httplib::Request &req, httplib::Response &res) {
auto *user = res.user_data.get<AuthUser>("user");
if (!user) {
res.status = 401;
return;
}
res.set_content("Hello, " + user->name, "text/plain");
});
```
`user_data.set()` stores a value of any type, and `user_data.get<T>()` retrieves it. If you give the wrong type you get `nullptr` back — so be careful.
## Typical value types
Strings, numbers, structs, `std::shared_ptr` — anything copyable or movable works.
```cpp
res.user_data.set("user_id", std::string{"42"});
res.user_data.set("is_admin", true);
res.user_data.set("started_at", std::chrono::steady_clock::now());
```
## Where to set, where to read
The usual flow is: set it in `set_pre_routing_handler()` or `set_pre_request_handler()`, read it in the route handler. Pre-request runs after routing, so you can combine it with `req.matched_route` to set values only for specific routes.
## A gotcha
`user_data` lives on `Response`, not `Request`. That's because handlers get `Response&` (mutable) but only `const Request&`. It looks odd at first, but it makes sense once you think of it as "the mutable context shared between handlers."
> **Warning:** `user_data.get<T>()` returns `nullptr` when the type doesn't match. Use the exact same type on set and get. Storing as `AuthUser` and fetching as `const AuthUser` won't work.
@@ -0,0 +1,51 @@
---
title: "S13. Return a Custom Error Page"
order: 32
status: "draft"
---
To customize the response for 4xx or 5xx errors, use `set_error_handler()`. You can replace the plain default error page with your own HTML or JSON.
## Basic usage
```cpp
svr.set_error_handler([](const httplib::Request &req, httplib::Response &res) {
auto body = "<h1>Error " + std::to_string(res.status) + "</h1>";
res.set_content(body, "text/html");
});
```
The error handler runs right before an error response is sent — any time `res.status` is 4xx or 5xx. Replace the body with `res.set_content()` and every error response uses the same template.
## Branch by status code
```cpp
svr.set_error_handler([](const httplib::Request &req, httplib::Response &res) {
if (res.status == 404) {
res.set_content("<h1>Not Found</h1><p>" + req.path + "</p>", "text/html");
} else if (res.status >= 500) {
res.set_content("<h1>Server Error</h1>", "text/html");
}
});
```
Checking `res.status` lets you show a custom message for 404s and a "contact support" link for 5xx errors.
## JSON error responses
For an API server, you probably want errors as JSON.
```cpp
svr.set_error_handler([](const httplib::Request &req, httplib::Response &res) {
nlohmann::json j = {
{"error", true},
{"status", res.status},
{"path", req.path},
};
res.set_content(j.dump(), "application/json");
});
```
Now every error comes back in a consistent JSON shape.
> **Note:** `set_error_handler()` also fires for 500 responses caused by exceptions thrown from a route handler. To get at the exception itself, combine it with `set_exception_handler()`. See S14. Catch Exceptions.
@@ -0,0 +1,67 @@
---
title: "S14. Catch Exceptions"
order: 33
status: "draft"
---
When a route handler throws, cpp-httplib keeps the server running and responds with 500. By default, though, very little of the error information reaches the client. `set_exception_handler()` lets you intercept exceptions and build your own response.
## Basic usage
```cpp
svr.set_exception_handler(
[](const httplib::Request &req, httplib::Response &res,
std::exception_ptr ep) {
try {
std::rethrow_exception(ep);
} catch (const std::exception &e) {
res.status = 500;
res.set_content(std::string("error: ") + e.what(), "text/plain");
} catch (...) {
res.status = 500;
res.set_content("unknown error", "text/plain");
}
});
```
The handler receives a `std::exception_ptr`. The idiomatic move is to rethrow it with `std::rethrow_exception()` and catch by type. You can vary status code and message based on the exception type.
## Branch on custom exception types
If you throw your own exception types, you can map them to 400 or 404 responses.
```cpp
struct NotFound : std::runtime_error {
using std::runtime_error::runtime_error;
};
struct BadRequest : std::runtime_error {
using std::runtime_error::runtime_error;
};
svr.set_exception_handler(
[](const auto &req, auto &res, std::exception_ptr ep) {
try {
std::rethrow_exception(ep);
} catch (const NotFound &e) {
res.status = 404;
res.set_content(e.what(), "text/plain");
} catch (const BadRequest &e) {
res.status = 400;
res.set_content(e.what(), "text/plain");
} catch (const std::exception &e) {
res.status = 500;
res.set_content("internal error", "text/plain");
}
});
```
Now throwing `NotFound("user not found")` inside a handler is enough to return 404. No per-handler try/catch needed.
## Relationship with set_error_handler
`set_exception_handler()` runs the moment the exception is thrown. After that, if `res.status` is 4xx or 5xx, `set_error_handler()` also runs. The order is `exception_handler``error_handler`. Think of their roles as:
- **Exception handler**: interpret the exception, set the status and message
- **Error handler**: see the status and wrap it in the shared template
> **Note:** Without an exception handler, cpp-httplib returns a default 500 response and the exception details never make it to logs. Always set one for anything you want to debug.
@@ -0,0 +1,64 @@
---
title: "S15. Log Requests on the Server"
order: 34
status: "draft"
---
To log the requests the server receives and the responses it returns, use `Server::set_logger()`. The callback fires once per completed request, making it the foundation for access logs and metrics collection.
## Basic usage
```cpp
svr.set_logger([](const httplib::Request &req, const httplib::Response &res) {
std::cout << req.remote_addr << " "
<< req.method << " " << req.path
<< " -> " << res.status << std::endl;
});
```
The log callback receives both the `Request` and the `Response`. You can grab the method, path, status, client IP, headers, body — whatever you need.
## Access-log style format
Here's an Apache/Nginx-ish access log format.
```cpp
svr.set_logger([](const auto &req, const auto &res) {
auto now = std::time(nullptr);
char timebuf[32];
std::strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S",
std::localtime(&now));
std::cout << timebuf << " "
<< req.remote_addr << " "
<< "\"" << req.method << " " << req.path << "\" "
<< res.status << " "
<< res.body.size() << "B"
<< std::endl;
});
```
## Measure request time
To include request duration in the log, stash a start timestamp in `res.user_data` from a pre-routing handler, then subtract in the logger.
```cpp
svr.set_pre_routing_handler([](const auto &req, auto &res) {
res.user_data.set("start", std::chrono::steady_clock::now());
return httplib::Server::HandlerResponse::Unhandled;
});
svr.set_logger([](const auto &req, const auto &res) {
auto *start = res.user_data.get<std::chrono::steady_clock::time_point>("start");
auto elapsed = start
? std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - *start).count()
: 0;
std::cout << req.method << " " << req.path
<< " " << res.status << " " << elapsed << "ms" << std::endl;
});
```
For more on `user_data`, see S12. Pass Data Between Handlers with `res.user_data`.
> **Note:** The logger runs synchronously on the same thread as request processing. Heavy work inside it hurts throughput — push it to a queue and process asynchronously if you need anything expensive.
@@ -0,0 +1,55 @@
---
title: "S16. Detect When the Client Has Disconnected"
order: 35
status: "draft"
---
During a long-running response, the client might close the connection. There's no point continuing to do work no one's waiting for. In cpp-httplib, check `req.is_connection_closed()`.
## Basic usage
```cpp
svr.Get("/long-task", [](const httplib::Request &req, httplib::Response &res) {
for (int i = 0; i < 1000; ++i) {
if (req.is_connection_closed()) {
std::cout << "client disconnected" << std::endl;
return;
}
do_heavy_work(i);
}
res.set_content("done", "text/plain");
});
```
`is_connection_closed` is a `std::function<bool()>`, so call it with `()`. It returns `true` when the client is gone.
## With a streaming response
The same check works inside `set_chunked_content_provider()`. Capture the request by reference.
```cpp
svr.Get("/events", [](const httplib::Request &req, httplib::Response &res) {
res.set_chunked_content_provider(
"text/event-stream",
[&req](size_t offset, httplib::DataSink &sink) {
if (req.is_connection_closed()) {
sink.done();
return true;
}
auto event = generate_next_event();
sink.write(event.data(), event.size());
return true;
});
});
```
When you detect a disconnect, call `sink.done()` to stop the provider from being called again.
## How often should you check?
The call itself is cheap, but calling it in a tight inner loop doesn't add much value. Check at **boundaries where interrupting is safe** — after producing a chunk, after a database query, etc.
> **Warning:** `is_connection_closed()` is not guaranteed to reflect reality instantly. Because of how TCP works, sometimes you only notice the disconnect when you try to send. Don't expect pixel-perfect real-time detection — think of it as "we'll notice eventually."
@@ -0,0 +1,52 @@
---
title: "S17. Bind to Any Available Port"
order: 36
status: "draft"
---
Standing up a test server often hits port conflicts. With `bind_to_any_port()`, you let the OS pick a free port and then read back which one it gave you.
## Basic usage
```cpp
httplib::Server svr;
svr.Get("/", [](const auto &req, auto &res) {
res.set_content("hello", "text/plain");
});
int port = svr.bind_to_any_port("0.0.0.0");
std::cout << "listening on port " << port << std::endl;
svr.listen_after_bind();
```
`bind_to_any_port()` is equivalent to passing `0` as the port — the OS assigns a free one. The return value is the port actually used.
After that, call `listen_after_bind()` to start accepting. You can't combine bind and listen into a single call here, so you work in two steps.
## Useful in tests
This pattern is great for tests that spin up a server and hit it.
```cpp
httplib::Server svr;
svr.Get("/ping", [](const auto &, auto &res) { res.set_content("pong", "text/plain"); });
int port = svr.bind_to_any_port("127.0.0.1");
std::thread t([&] { svr.listen_after_bind(); });
// run the test while the server is up on another thread
httplib::Client cli("127.0.0.1", port);
auto res = cli.Get("/ping");
assert(res && res->body == "pong");
svr.stop();
t.join();
```
Because the port is assigned at runtime, parallel test runs don't collide.
> **Note:** `bind_to_any_port()` returns `-1` on failure (permission errors, no available ports, etc.). Always check the return value.
> To stop the server, see S19. Shut Down Gracefully.
@@ -0,0 +1,57 @@
---
title: "S18. Control Startup Order with listen_after_bind"
order: 37
status: "draft"
---
Normally `svr.listen("0.0.0.0", 8080)` handles bind and listen in one shot. When you need to do something between the two, split them into two calls.
## Separate bind and listen
```cpp
httplib::Server svr;
svr.Get("/", [](const auto &, auto &res) { res.set_content("ok", "text/plain"); });
if (!svr.bind_to_port("0.0.0.0", 8080)) {
std::cerr << "bind failed" << std::endl;
return 1;
}
// bind is done here. accept hasn't started yet.
drop_privileges();
signal_ready_to_parent_process();
svr.listen_after_bind(); // start the accept loop
```
`bind_to_port()` reserves the port; `listen_after_bind()` actually starts accepting. Splitting them gives you a window between the two steps.
## Common use cases
**Privilege drop**: Binding to a port under 1024 requires root. Bind as root, drop to a normal user, and all subsequent request handling runs with reduced privileges.
```cpp
svr.bind_to_port("0.0.0.0", 80);
drop_privileges();
svr.listen_after_bind();
```
**Startup notification**: Tell the parent process or systemd "I'm ready" before starting to accept connections.
**Test synchronization**: In tests, you can reliably catch "the moment the server is bound" and start the client after that.
## Check the return values
`bind_to_port()` returns `false` on failure — typically when the port is already taken. Always check it.
```cpp
if (!svr.bind_to_port("0.0.0.0", 8080)) {
std::cerr << "port already in use" << std::endl;
return 1;
}
```
`listen_after_bind()` blocks until the server stops and returns `true` on a clean shutdown.
> **Note:** To auto-pick a free port, see S17. Bind to Any Available Port. Under the hood, that's just `bind_to_any_port()` + `listen_after_bind()`.
@@ -0,0 +1,57 @@
---
title: "S19. Shut Down Gracefully"
order: 38
status: "draft"
---
To stop the server, call `Server::stop()`. It's safe to call even while requests are in flight, so you can wire it to SIGINT or SIGTERM for a graceful shutdown.
## Basic usage
```cpp
httplib::Server svr;
svr.Get("/", [](const auto &, auto &res) { res.set_content("ok", "text/plain"); });
std::thread t([&] { svr.listen("0.0.0.0", 8080); });
// wait for input on the main thread, or whatever
std::cin.get();
svr.stop();
t.join();
```
`listen()` blocks, so the typical pattern is: run the server on a background thread and call `stop()` from the main thread. After `stop()`, `listen()` returns and you can `join()`.
## Shut down on a signal
Here's how to stop the server on SIGINT (Ctrl+C) or SIGTERM.
```cpp
#include <csignal>
httplib::Server svr;
// global so the signal handler can reach it
httplib::Server *g_svr = nullptr;
int main() {
svr.Get("/", [](const auto &, auto &res) { res.set_content("ok", "text/plain"); });
g_svr = &svr;
std::signal(SIGINT, [](int) { if (g_svr) g_svr->stop(); });
std::signal(SIGTERM, [](int) { if (g_svr) g_svr->stop(); });
svr.listen("0.0.0.0", 8080);
std::cout << "server stopped" << std::endl;
}
```
`stop()` is thread-safe and signal-safe — you can call it from a signal handler. Even when `listen()` is running on the main thread, the signal pulls it out cleanly.
## What happens to in-flight requests
When you call `stop()`, new connections are refused, but requests already being processed are **allowed to finish**. Once all workers drain, `listen()` returns. That's what makes it graceful.
> **Warning:** There's a wait between calling `stop()` and `listen()` returning — it's the time in-flight requests take to finish. To enforce a timeout, you'll need to add your own shutdown timer in application code.
@@ -0,0 +1,57 @@
---
title: "S20. Tune Keep-Alive"
order: 39
status: "draft"
---
`httplib::Server` enables HTTP/1.1 Keep-Alive automatically. From the client's perspective, connections are reused — so they don't pay the TCP handshake cost on every request. When you need to tune the behavior, there are two setters.
## What you can configure
| API | Default | Meaning |
| --- | --- | --- |
| `set_keep_alive_max_count` | 100 | Max requests served over a single connection |
| `set_keep_alive_timeout` | 5s | How long an idle connection is kept before closing |
## Basic usage
```cpp
httplib::Server svr;
svr.set_keep_alive_max_count(20);
svr.set_keep_alive_timeout(10); // 10 seconds
svr.listen("0.0.0.0", 8080);
```
`set_keep_alive_timeout()` also has a `std::chrono` overload.
```cpp
using namespace std::chrono_literals;
svr.set_keep_alive_timeout(10s);
```
## Tuning ideas
**Too many idle connections eating resources**
Shorten the timeout so idle connections drop and release their worker threads.
```cpp
svr.set_keep_alive_timeout(2s);
```
**API is hammered and you want max reuse**
Raising the per-connection request cap improves benchmark numbers.
```cpp
svr.set_keep_alive_max_count(1000);
```
**Never reuse connections**
Set `set_keep_alive_max_count(1)` and every request gets its own connection. Mostly only useful for debugging or compatibility testing.
## Relationship with the thread pool
A Keep-Alive connection holds a worker thread for its entire lifetime. If `connections × concurrent requests` exceeds the thread pool size, new requests wait. For thread counts, see S21. Configure the Thread Pool.
> **Note:** For the client side, see C14. Understand Connection Reuse and Keep-Alive Behavior. Even when the server closes the connection on timeout, the client reconnects automatically.
@@ -0,0 +1,69 @@
---
title: "S21. Configure the Thread Pool"
order: 40
status: "draft"
---
cpp-httplib serves requests from a thread pool. By default, the base thread count is the greater of `std::thread::hardware_concurrency() - 1` and `8`, and it can scale up dynamically to 4× that. To set thread counts explicitly, provide your own factory via `new_task_queue`.
## Set thread counts
```cpp
httplib::Server svr;
svr.new_task_queue = [] {
return new httplib::ThreadPool(/*base_threads=*/8, /*max_threads=*/64);
};
svr.listen("0.0.0.0", 8080);
```
The factory is a lambda returning a `TaskQueue*`. Pass `base_threads` and `max_threads` to `ThreadPool` and the pool scales between them based on load. Idle threads exit after a timeout (3 seconds by default).
## Also cap the queue
The pending queue can eat memory if it grows unchecked. You can cap it too.
```cpp
svr.new_task_queue = [] {
return new httplib::ThreadPool(
/*base_threads=*/12,
/*max_threads=*/0, // disable dynamic scaling
/*max_queued_requests=*/18);
};
```
`max_threads=0` disables dynamic scaling — you get a fixed `base_threads`. Requests that don't fit in `max_queued_requests` are rejected.
## Use your own thread pool
You can plug in a fully custom thread pool by subclassing `TaskQueue` and returning it from the factory.
```cpp
class MyTaskQueue : public httplib::TaskQueue {
public:
MyTaskQueue(size_t n) { pool_.start_with_thread_count(n); }
bool enqueue(std::function<void()> fn) override { return pool_.post(std::move(fn)); }
void shutdown() override { pool_.shutdown(); }
private:
MyThreadPool pool_;
};
svr.new_task_queue = [] { return new MyTaskQueue(12); };
```
Handy when you already have a thread pool in your project and want to keep thread management unified.
## Compile-time tuning
You can set the defaults with macros if you want compile-time configuration.
```cpp
#define CPPHTTPLIB_THREAD_POOL_COUNT 16 // base thread count
#define CPPHTTPLIB_THREAD_POOL_MAX_COUNT 128 // max thread count
#define CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT 5 // seconds before idle threads exit
#include <httplib.h>
```
> **Note:** A WebSocket connection holds a worker thread for its entire lifetime. For lots of simultaneous WebSocket connections, enable dynamic scaling (e.g. `ThreadPool(8, 64)`).
@@ -0,0 +1,64 @@
---
title: "S22. Talk Over a Unix Domain Socket"
order: 41
status: "draft"
---
When you want to talk only to other processes on the same host, a Unix domain socket is a nice fit. It avoids TCP overhead and uses filesystem permissions for access control. Local IPC and services sitting behind a reverse proxy are classic use cases.
## Server side
```cpp
httplib::Server svr;
svr.set_address_family(AF_UNIX);
svr.Get("/", [](const auto &, auto &res) {
res.set_content("hello from unix socket", "text/plain");
});
svr.listen("/tmp/httplib.sock", 80);
```
Call `set_address_family(AF_UNIX)` first, then pass the socket file path as the first argument to `listen()`. The port number is unused but required by the signature — pass any value.
## Client side
```cpp
httplib::Client cli("/tmp/httplib.sock");
cli.set_address_family(AF_UNIX);
auto res = cli.Get("/");
if (res) {
std::cout << res->body << std::endl;
}
```
Pass the socket file path to the `Client` constructor and call `set_address_family(AF_UNIX)`. Everything else works like a normal HTTP request.
## When to use it
- **Behind a reverse proxy**: An nginx-to-backend setup over a Unix socket is faster than TCP and sidesteps port management
- **Local-only APIs**: IPC between tools that shouldn't be reachable from outside
- **In-container IPC**: Process-to-process communication within the same pod or container
- **Dev environments**: No more worrying about port conflicts
## Clean up the socket file
A Unix domain socket creates a real file in the filesystem. It doesn't get removed on shutdown, so delete it before starting if needed.
```cpp
std::remove("/tmp/httplib.sock");
svr.listen("/tmp/httplib.sock", 80);
```
## Permissions
You control who can connect via the socket file's permissions.
```cpp
svr.listen("/tmp/httplib.sock", 80);
// from another process or thread
chmod("/tmp/httplib.sock", 0660); // owner and group only
```
> **Warning:** Some Windows versions support AF_UNIX, but the implementation and behavior differ by platform. Test thoroughly before running cross-platform in production.
@@ -0,0 +1,49 @@
---
title: "T01. Choosing Between OpenSSL, mbedTLS, and wolfSSL"
order: 42
status: "draft"
---
cpp-httplib doesn't ship its own TLS implementation — it uses one of three backends that you pick at build time via a macro.
| Backend | Macro | Character |
| --- | --- | --- |
| OpenSSL | `CPPHTTPLIB_OPENSSL_SUPPORT` | Most widely used, richest feature set |
| mbedTLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | Lightweight, aimed at embedded |
| wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | Embedded-friendly, commercial support available |
## Build-time selection
Define the macro for your chosen backend before including `httplib.h`:
```cpp
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include <httplib.h>
```
You'll also need to link against the backend's libraries (`libssl`, `libcrypto`, `libmbedtls`, `libwolfssl`, etc.).
## Which to pick
**When in doubt, OpenSSL**
It has the most features and the best documentation. For normal server use or Linux desktop apps, start here — you probably won't need anything else.
**To shrink binary size or target embedded**
mbedTLS or wolfSSL are a better fit. They're far more compact than OpenSSL and run on memory-constrained devices.
**When you need commercial support**
wolfSSL offers commercial licensing and support. If you're shipping in a product, it's worth considering.
## Supporting multiple backends
The usual approach is to treat each backend as a build variant and recompile the same source with different macros. cpp-httplib smooths over most of the API differences, but the backends are not 100% identical — always test.
## APIs that work across all backends
Certificate verification control, standing up an SSLServer, reading the peer certificate — these all share the same API across backends:
- T02. Control SSL Certificate Verification
- T03. Start an SSL/TLS Server
- T05. Access the Peer Certificate on the Server Side
> **Note:** On macOS with an OpenSSL-family backend, cpp-httplib automatically loads root certificates from the system keychain (via `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`, on by default). To disable this, define `CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES`.
@@ -0,0 +1,53 @@
---
title: "T02. Control SSL Certificate Verification"
order: 43
status: "draft"
---
By default, an HTTPS client verifies the server certificate — it uses the OS root certificate store to check the chain and the hostname. Here are the APIs for changing that behavior.
## Specify a custom CA certificate
When connecting to a server whose certificate is signed by an internal CA, use `set_ca_cert_path()`.
```cpp
httplib::Client cli("https://internal.example.com");
cli.set_ca_cert_path("/etc/ssl/certs/internal-ca.pem");
auto res = cli.Get("/");
```
The first argument is the CA certificate file; the second is an optional CA directory. With the OpenSSL backend, you can also pass an `X509_STORE*` directly via `set_ca_cert_store()`.
## Disable certificate verification (not recommended)
For development servers or self-signed certificates, you can skip verification entirely.
```cpp
httplib::Client cli("https://self-signed.example.com");
cli.enable_server_certificate_verification(false);
auto res = cli.Get("/");
```
That's all it takes to disable chain verification.
> **Warning:** Disabling certificate verification removes protection against man-in-the-middle attacks. **Never do this in production.** If you find yourself needing it outside of dev/test, pause and make sure you're not doing something wrong.
## Disable hostname verification only
There's an in-between option: verify the certificate chain, but skip the hostname check. Useful when you need to reach a server whose cert CN/SAN doesn't match the request's hostname.
```cpp
cli.enable_server_hostname_verification(false);
```
The certificate itself is still validated, so this is safer than fully disabling verification — but still not recommended in production.
## Use the OS cert store as-is
On most Linux distributions, root certificates live in a single file like `/etc/ssl/certs/ca-certificates.crt`. cpp-httplib reads the OS default store at startup, so for most servers you don't need to configure anything.
> The same APIs work on the mbedTLS and wolfSSL backends. For choosing between backends, see T01. Choosing Between OpenSSL, mbedTLS, and wolfSSL.
> For details on diagnosing failures, see C18. Handle SSL Errors.
@@ -0,0 +1,78 @@
---
title: "T03. Start an SSL/TLS Server"
order: 44
status: "draft"
---
To stand up an HTTPS server, use `httplib::SSLServer` instead of `httplib::Server`. Pass a certificate and private key to the constructor, and you get back something that works exactly like `Server`.
## Basic usage
```cpp
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include <httplib.h>
int main() {
httplib::SSLServer svr("cert.pem", "key.pem");
svr.Get("/", [](const auto &req, auto &res) {
res.set_content("hello over TLS", "text/plain");
});
svr.listen("0.0.0.0", 443);
}
```
Pass the server certificate (PEM format) and private key file paths to the constructor. That's all you need for a TLS-enabled server. Registering handlers and calling `listen()` work the same as with `Server`.
## Password-protected private keys
The fifth argument is the private key password.
```cpp
httplib::SSLServer svr("cert.pem", "key.pem",
nullptr, nullptr, "password");
```
The third and fourth arguments are for client certificate verification (mTLS, see T04). For now, pass `nullptr`.
## Load PEM data from memory
When you want to load certs from memory instead of files, use the `PemMemory` struct.
```cpp
httplib::SSLServer::PemMemory pem{};
pem.cert_pem = cert_data.data();
pem.cert_pem_len = cert_data.size();
pem.key_pem = key_data.data();
pem.key_pem_len = key_data.size();
httplib::SSLServer svr(pem);
```
Handy when you pull certificates from environment variables or a secrets manager.
## Rotate certificates
Before a certificate expires, you may want to swap it out without restarting the server. That's what `update_certs_pem()` is for.
```cpp
svr.update_certs_pem(new_cert_pem, new_key_pem);
```
Existing connections keep using the old cert; new connections use the new one.
## Generating a test certificate
For a throwaway self-signed cert, use the `openssl` CLI.
```sh
openssl req -x509 -newkey rsa:2048 -days 365 -nodes \
-keyout key.pem -out cert.pem -subj "/CN=localhost"
```
In production, use certificates from Let's Encrypt or your internal CA.
> **Warning:** Binding an HTTPS server to port 443 requires root. For a safe way to do that, see the privilege-drop pattern in S18. Control Startup Order with `listen_after_bind`.
> For mutual TLS (client certificates), see T04. Configure mTLS.
+70
View File
@@ -0,0 +1,70 @@
---
title: "T04. Configure mTLS"
order: 45
status: "draft"
---
Regular TLS verifies the server certificate only. **mTLS** (mutual TLS) adds the other direction: the client presents a certificate too, and the server verifies it. It's common for zero-trust API-to-API traffic and internal system authentication.
## Server side
Pass the CA used to verify client certificates as the third (and fourth) argument to `SSLServer`.
```cpp
httplib::SSLServer svr(
"server-cert.pem", // server certificate
"server-key.pem", // server private key
"client-ca.pem", // CA that signs valid client certs
nullptr // CA directory (none)
);
svr.Get("/", [](const httplib::Request &req, httplib::Response &res) {
res.set_content("authenticated", "text/plain");
});
svr.listen("0.0.0.0", 443);
```
With this, any connection whose client certificate isn't signed by `client-ca.pem` is rejected at the handshake. By the time a handler runs, the client is already authenticated.
## Configure with in-memory PEM
```cpp
httplib::SSLServer::PemMemory pem{};
pem.cert_pem = server_cert.data();
pem.cert_pem_len = server_cert.size();
pem.key_pem = server_key.data();
pem.key_pem_len = server_key.size();
pem.client_ca_pem = client_ca.data();
pem.client_ca_pem_len = client_ca.size();
httplib::SSLServer svr(pem);
```
This is the clean way when you load certificates from environment variables or a secrets manager.
## Client side
On the client side, pass the client certificate and key to `SSLClient`.
```cpp
httplib::SSLClient cli("api.example.com", 443,
"client-cert.pem",
"client-key.pem");
auto res = cli.Get("/");
```
Note you're using `SSLClient` directly, not `Client`. If the private key has a password, pass it as the fifth argument.
## Read client info from a handler
To see which client connected from inside a handler, use `req.peer_cert()`. Details in T05. Access the Peer Certificate on the Server Side.
## Use cases
- **Microservice-to-microservice calls**: Issue a cert per service, use the cert as identity
- **IoT device management**: Burn a cert into each device and use it to gate API access
- **An alternative to internal VPN**: Put cert-based auth in front of public endpoints so internal resources can be reached safely
> **Note:** Issuing and revoking client certificates is more operational work than password-based auth. You'll need either an internal PKI setup or an automated flow using ACME-family tools.
@@ -0,0 +1,88 @@
---
title: "T05. Access the Peer Certificate on the Server Side"
order: 46
status: "draft"
---
In an mTLS setup, you can read the client's certificate from inside a handler. Pull out the CN or SAN to identify the user or log the request.
## Basic usage
```cpp
svr.Get("/me", [](const httplib::Request &req, httplib::Response &res) {
auto cert = req.peer_cert();
if (!cert) {
res.status = 401;
res.set_content("no client certificate", "text/plain");
return;
}
auto cn = cert.subject_cn();
res.set_content("hello, " + cn, "text/plain");
});
```
`req.peer_cert()` returns a `tls::PeerCert`. It's convertible to `bool`, so check whether a cert is present before using it.
## Available fields
From a `PeerCert`, you can get:
```cpp
auto cert = req.peer_cert();
std::string cn = cert.subject_cn(); // CN
std::string issuer = cert.issuer_name(); // issuer
std::string serial = cert.serial(); // serial number
time_t not_before, not_after;
cert.validity(not_before, not_after); // validity period
auto sans = cert.sans(); // SANs
for (const auto &san : sans) {
std::cout << san.value << std::endl;
}
```
There's also a helper to check if a hostname is covered by the SAN list:
```cpp
if (cert.check_hostname("alice.corp.example.com")) {
// matches
}
```
## Cert-based authorization
You can gate routes by CN or SAN.
```cpp
svr.set_pre_request_handler(
[](const httplib::Request &req, httplib::Response &res) {
auto cert = req.peer_cert();
if (!cert) {
res.status = 401;
return httplib::Server::HandlerResponse::Handled;
}
if (req.matched_route.rfind("/admin", 0) == 0) {
auto cn = cert.subject_cn();
if (!is_admin_cn(cn)) {
res.status = 403;
return httplib::Server::HandlerResponse::Handled;
}
}
return httplib::Server::HandlerResponse::Unhandled;
});
```
Combined with a pre-request handler, you can keep all authorization logic in one place. See S11. Authenticate Per Route with a Pre-Request Handler.
## SNI (Server Name Indication)
cpp-httplib handles SNI automatically. If one server hosts multiple domains, SNI is used under the hood — but normally handlers don't need to care.
> **Warning:** `req.peer_cert()` only returns a meaningful value when mTLS is enabled and the client actually presented a certificate. For plain TLS, you get an empty `PeerCert`. Always do the `bool` check before using it.
> To set up mTLS, see T04. Configure mTLS.
@@ -0,0 +1,88 @@
---
title: "W01. Implement a WebSocket Echo Server and Client"
order: 51
status: "draft"
---
WebSocket is a protocol for **two-way** messaging between client and server. cpp-httplib provides APIs for both sides. Let's start with the simplest example: an echo server.
## Server: echo server
```cpp
#include <httplib.h>
int main() {
httplib::Server svr;
svr.WebSocket("/echo", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
std::string msg;
while (ws.is_open()) {
auto result = ws.read(msg);
if (result == httplib::ws::ReadResult::Fail) {
break;
}
ws.send(msg); // echo back what we received
}
});
svr.listen("0.0.0.0", 8080);
}
```
Register a WebSocket handler with `svr.WebSocket()`. By the time the handler runs, the WebSocket handshake is already complete. Inside the loop, just `ws.read()` and `ws.send()` to get a working echo.
The `read()` return value is a `ReadResult` enum:
- `ReadResult::Text`: received a text message
- `ReadResult::Binary`: received a binary message
- `ReadResult::Fail`: error, or connection closed
## Client: talk to the echo server
```cpp
#include <httplib.h>
int main() {
httplib::ws::WebSocketClient cli("ws://localhost:8080/echo");
if (!cli.connect()) {
std::cerr << "failed to connect" << std::endl;
return 1;
}
cli.send("Hello, WebSocket!");
std::string msg;
if (cli.read(msg) != httplib::ws::ReadResult::Fail) {
std::cout << "received: " << msg << std::endl;
}
cli.close();
}
```
Use a `ws://` (plain) or `wss://` (TLS) URL. Call `connect()` to do the handshake, then `send()` and `read()` work the same as on the server side.
## Text vs. binary
`send()` has two overloads that let you choose the frame type.
```cpp
ws.send("Hello"); // text frame
ws.send(binary_data, binary_data_size); // binary frame
```
The `std::string` overload sends as **text**; the `const char*` + size overload sends as **binary**. A bit subtle, but once you know it, it's intuitive. See W04. Send and Receive Binary Frames for details.
## Thread pool implications
A WebSocket handler holds its worker thread for the entire life of the connection — one connection per thread. For many concurrent clients, configure a dynamic thread pool.
```cpp
svr.new_task_queue = [] {
return new httplib::ThreadPool(8, 128);
};
```
See S21. Configure the Thread Pool.
> **Note:** To run WebSocket over HTTPS, use `httplib::SSLServer` instead of `httplib::Server` — the same `WebSocket()` handler just works. On the client side, use a `wss://` URL.
@@ -0,0 +1,60 @@
---
title: "W02. Set a WebSocket Heartbeat"
order: 52
status: "draft"
---
WebSocket connections stay open for a long time, and proxies or load balancers will sometimes drop them for being "idle." To prevent that, you periodically send Ping frames to keep the connection alive. cpp-httplib can do this for you automatically.
## Server side
```cpp
svr.set_websocket_ping_interval(30); // ping every 30 seconds
svr.WebSocket("/chat", [](const auto &req, auto &ws) {
// ...
});
```
Just pass the interval in seconds. Every WebSocket connection this server accepts will be pinged on that interval.
There's a `std::chrono` overload too.
```cpp
using namespace std::chrono_literals;
svr.set_websocket_ping_interval(30s);
```
## Client side
The client has the same API.
```cpp
httplib::ws::WebSocketClient cli("ws://localhost:8080/chat");
cli.set_websocket_ping_interval(30);
cli.connect();
```
Call it before `connect()`.
## The default
The default interval is set by the build-time macro `CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND`. Usually you won't need to change it, but adjust downward if you're dealing with an aggressive proxy.
## What about Pong?
The WebSocket protocol requires that Ping frames are answered with Pong frames. cpp-httplib responds to Pings automatically — you don't need to think about it in application code.
## Picking an interval
| Environment | Suggested |
| --- | --- |
| Normal internet | 3060s |
| Strict proxies (e.g. AWS ALB) | 1530s |
| Mobile networks | 60s+ (too short drains battery) |
Too short wastes bandwidth; too long and connections get dropped. As a rule of thumb, target about **half the idle timeout** of whatever's between you and the client.
> **Warning:** A very short ping interval spawns background work per connection and increases CPU usage. For servers with many connections, keep the interval modest.
> For handling a closed connection, see W03. Handle Connection Close.
@@ -0,0 +1,91 @@
---
title: "W03. Handle Connection Close"
order: 53
status: "draft"
---
A WebSocket ends when either side closes it explicitly, or when the network drops. Handle close cleanly, and your cleanup and reconnect logic stays tidy.
## Detect a closed connection
When `ws.read()` returns `ReadResult::Fail`, the connection is gone — either cleanly or with an error. Break out of the loop and the handler will finish.
```cpp
svr.WebSocket("/chat", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
std::string msg;
while (ws.is_open()) {
auto result = ws.read(msg);
if (result == httplib::ws::ReadResult::Fail) {
std::cout << "disconnected" << std::endl;
break;
}
handle_message(ws, msg);
}
// cleanup runs once we're out of the loop
cleanup_user_session(req);
});
```
You can also check `ws.is_open()` — it's the same signal from a different angle.
## Close from the server side
To close explicitly, call `close()`.
```cpp
ws.close(httplib::ws::CloseStatus::Normal, "bye");
```
The first argument is the close status; the second is an optional reason. Common `CloseStatus` values:
| Value | Meaning |
| --- | --- |
| `Normal` (1000) | Normal closure |
| `GoingAway` (1001) | Server is shutting down |
| `ProtocolError` (1002) | Protocol violation detected |
| `UnsupportedData` (1003) | Received data that can't be handled |
| `PolicyViolation` (1008) | Violated a policy |
| `MessageTooBig` (1009) | Message too large |
| `InternalError` (1011) | Server-side error |
## Close from the client side
The client API is identical.
```cpp
cli.close(httplib::ws::CloseStatus::Normal);
```
Destroying the client also closes the connection, but calling `close()` explicitly makes the intent clearer.
## Graceful shutdown
To notify in-flight clients that the server is going down, use `GoingAway`.
```cpp
ws.close(httplib::ws::CloseStatus::GoingAway, "server restarting");
```
The client can inspect that status and decide whether to reconnect.
## Example: a tiny chat with quit
```cpp
svr.WebSocket("/chat", [](const auto &req, auto &ws) {
std::string msg;
while (ws.is_open()) {
if (ws.read(msg) == httplib::ws::ReadResult::Fail) break;
if (msg == "/quit") {
ws.send("goodbye");
ws.close(httplib::ws::CloseStatus::Normal, "user quit");
break;
}
ws.send("echo: " + msg);
}
});
```
> **Note:** On a sudden network drop, `read()` returns `Fail` with no chance to call `close()`. Put your cleanup at the end of the handler, and both paths — clean close and abrupt disconnect — end up in the same place.
@@ -0,0 +1,85 @@
---
title: "W04. Send and Receive Binary Frames"
order: 54
status: "draft"
---
WebSocket has two frame types: text and binary. JSON and plain text go in text frames; images and raw protocol bytes go in binary. In cpp-httplib, `send()` picks the right type via overload.
## How to pick a frame type
```cpp
ws.send(std::string("Hello")); // text
ws.send("Hello", 5); // binary
ws.send(binary_data, binary_data_size); // binary
```
The `std::string` overload sends as **text**. The `const char*` + size overload sends as **binary**. A bit subtle, but once you know it, it sticks.
If you have a `std::string` and want to send it as binary, pass `.data()` and `.size()` explicitly.
```cpp
std::string raw = build_binary_payload();
ws.send(raw.data(), raw.size()); // binary frame
```
## Detect frame type on receive
The return value of `ws.read()` tells you whether the received frame was text or binary.
```cpp
std::string msg;
auto result = ws.read(msg);
switch (result) {
case httplib::ws::ReadResult::Text:
std::cout << "text: " << msg << std::endl;
break;
case httplib::ws::ReadResult::Binary:
std::cout << "binary: " << msg.size() << " bytes" << std::endl;
handle_binary(msg.data(), msg.size());
break;
case httplib::ws::ReadResult::Fail:
// error or closed
break;
}
```
Binary frames still come back in a `std::string`, but treat its contents as raw bytes — use `msg.data()` and `msg.size()`.
## When binary is the right call
- **Images, video, audio**: No Base64 overhead
- **Custom protocols**: protobuf, MessagePack, or any structured binary format
- **Game networking**: When latency matters
- **Sensor data streams**: Push numeric arrays directly
## Ping is binary-ish, but hidden
WebSocket Ping/Pong frames are close cousins of binary frames at the opcode level, but cpp-httplib handles them automatically — you don't touch them. See W02. Set a WebSocket Heartbeat.
## Example: send an image
```cpp
// Server: push an image
svr.WebSocket("/image", [](const auto &req, auto &ws) {
auto img = read_image_file("logo.png");
ws.send(img.data(), img.size());
});
```
```cpp
// Client: receive and save
httplib::ws::WebSocketClient cli("ws://localhost:8080/image");
cli.connect();
std::string buf;
if (cli.read(buf) == httplib::ws::ReadResult::Binary) {
std::ofstream ofs("received.png", std::ios::binary);
ofs.write(buf.data(), buf.size());
}
```
You can mix text and binary in the same connection. A common pattern: JSON for control messages, binary for the actual data — you get efficient handling of metadata and payload both.
> **Note:** WebSocket frames don't have an infinite size limit. For very large data, chunk it in your application code. cpp-httplib can handle a big frame in one shot, but it does load it all into memory at once.
+25
View File
@@ -0,0 +1,25 @@
---
title: "cpp-httplib"
order: 0
---
[cpp-httplib](https://github.com/yhirose/cpp-httplib) is an HTTP/HTTPS library for C++. Just copy a single header file, [`httplib.h`](https://github.com/yhirose/cpp-httplib/raw/refs/tags/latest/httplib.h), and you're ready to go.
When you need a quick HTTP server or client in C++, you want something that just works. That's exactly why I built cpp-httplib. You can start writing both servers and clients in just a few lines of code.
The API uses a lambda-based design that feels natural. It runs anywhere you have a C++11 or later compiler. Windows, macOS, Linux — use whatever environment you already have.
HTTPS works too. Just link OpenSSL or mbedTLS, and both server and client gain TLS support. Content-Encoding (gzip, Brotli, etc.), file uploads, and other features you actually need in real-world development are all included. WebSocket is also supported.
Under the hood, it uses blocking I/O with a thread pool. It's not built for handling massive numbers of simultaneous connections. But for API servers, embedded HTTP in tools, mock servers for testing, and many other use cases, it delivers solid performance.
"Solve today's problem, today." That's the kind of simplicity cpp-httplib aims for.
## Documentation
- [A Tour of cpp-httplib](tour/) — A step-by-step tutorial covering the basics. Start here if you're new
- [Building a Desktop LLM App](llm-app/) — A hands-on guide to building a desktop app with llama.cpp, step by step
## Stay Tuned
- [Cookbook](cookbook/) — A collection of recipes organized by topic. Jump to whatever you need
Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

+236
View File
@@ -0,0 +1,236 @@
---
title: "1. Setting Up the Project Environment"
order: 1
---
Let's incrementally build a text translation REST API server using llama.cpp as the inference engine. By the end, a request like this will return a translation result.
```bash
curl -X POST http://localhost:8080/translate \
-H "Content-Type: application/json" \
-d '{"text": "The weather is nice today. Shall we go for a walk?", "target_lang": "ja"}'
```
```json
{
"translation": "今日はいい天気ですね。散歩に行きましょうか?"
}
```
The "Translation API" is just one example. By swapping out the prompt, you can adapt this to any LLM application you like, such as summarization, code generation, or a chatbot.
Here's the full list of APIs the server will provide.
| Method | Path | Description | Chapter |
| -------- | ---- | ---- | -- |
| `GET` | `/health` | Returns server status | 1 |
| `POST` | `/translate` | Translates text and returns JSON | 2 |
| `POST` | `/translate/stream` | SSE streaming on a per-token basis | 3 |
| `GET` | `/models` | Model list (available / downloaded / selected) | 4 |
| `POST` | `/models/select` | Select a model (automatically downloads if not yet downloaded) | 4 |
In this chapter, let's set up the project environment. We'll fetch the dependency libraries, create the directory structure, configure the build settings, and grab the model file, so that we're ready to start writing code in the next chapter.
## Prerequisites
- A C++20-compatible compiler (GCC 10+, Clang 10+, MSVC 2019 16.8+)
- CMake 3.20 or later
- OpenSSL (used for the HTTPS client in Chapter 4. macOS: `brew install openssl`, Ubuntu: `sudo apt install libssl-dev`)
- Sufficient disk space (model files can be several GB)
## 1.1 What We Will Use
Here are the libraries we'll use.
| Library | Role |
| ----------- | ------ |
| [cpp-httplib](https://github.com/yhirose/cpp-httplib) | HTTP server/client |
| [nlohmann/json](https://github.com/nlohmann/json) | JSON parser |
| [cpp-llamalib](https://github.com/yhirose/cpp-llamalib) | llama.cpp wrapper |
| [llama.cpp](https://github.com/ggml-org/llama.cpp) | LLM inference engine |
| [webview/webview](https://github.com/webview/webview) | Desktop WebView (used in Chapter 6) |
cpp-httplib, nlohmann/json, and cpp-llamalib are header-only libraries. You could just download a single header file with `curl` and `#include` it, but in this book we use CMake's `FetchContent` to fetch them automatically. Declare them in `CMakeLists.txt`, and `cmake -B build` downloads and builds everything for you. webview is used in Chapter 6, so you don't need to worry about it for now.
## 1.2 Directory Structure
The final structure will look like this.
```ascii
translate-app/
├── CMakeLists.txt
├── models/
│ └── (GGUF files)
└── src/
└── main.cpp
```
We don't include library source code in the project. CMake's `FetchContent` fetches them automatically at build time, so all you need is your own code.
Let's create the project directory and initialize a git repository.
```bash
mkdir translate-app && cd translate-app
mkdir src models
git init
```
## 1.3 Obtaining the GGUF Model File
You need a model file for LLM inference. GGUF is the model format used by llama.cpp, and you can find many models on Hugging Face.
Let's start by trying a small model. The quantized version of Google's Gemma 2 2B (~1.6 GB) is a good starting point. It's lightweight but supports multiple languages and works well for translation tasks.
```bash
curl -L -o models/gemma-2-2b-it-Q4_K_M.gguf \
https://huggingface.co/bartowski/gemma-2-2b-it-GGUF/resolve/main/gemma-2-2b-it-Q4_K_M.gguf
```
In Chapter 4, we'll add the ability to download models from within the app using cpp-httplib's client functionality.
## 1.4 CMakeLists.txt
Create a `CMakeLists.txt` in the project root. By declaring dependencies with `FetchContent`, CMake will automatically download and build them for you.
<!-- data-file="CMakeLists.txt" -->
```cmake
cmake_minimum_required(VERSION 3.20)
project(translate-server CXX)
set(CMAKE_CXX_STANDARD 20)
include(FetchContent)
# llama.cpp (LLM inference engine)
FetchContent_Declare(llama
GIT_REPOSITORY https://github.com/ggml-org/llama.cpp
GIT_TAG master
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(llama)
# cpp-httplib (HTTP server/client)
FetchContent_Declare(httplib
GIT_REPOSITORY https://github.com/yhirose/cpp-httplib
GIT_TAG master
)
FetchContent_MakeAvailable(httplib)
# nlohmann/json (JSON parser)
FetchContent_Declare(json
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
)
FetchContent_MakeAvailable(json)
# cpp-llamalib (header-only llama.cpp wrapper)
FetchContent_Declare(cpp_llamalib
GIT_REPOSITORY https://github.com/yhirose/cpp-llamalib
GIT_TAG main
)
FetchContent_MakeAvailable(cpp_llamalib)
add_executable(translate-server src/main.cpp)
target_link_libraries(translate-server PRIVATE
httplib::httplib
nlohmann_json::nlohmann_json
cpp-llamalib
)
```
`FetchContent_Declare` tells CMake where to find each library, and `FetchContent_MakeAvailable` fetches and builds them. The first `cmake -B build` will take some time because it downloads all libraries and builds llama.cpp, but subsequent runs will use the cache.
Just link with `target_link_libraries`, and each library's CMake configuration sets up include paths and build settings for you.
## 1.5 Creating the Skeleton Code
We'll use this skeleton code as a base and add functionality chapter by chapter.
<!-- data-file="main.cpp" -->
```cpp
// src/main.cpp
#include <httplib.h>
#include <nlohmann/json.hpp>
#include <csignal>
#include <iostream>
using json = nlohmann::json;
httplib::Server svr;
// Graceful shutdown on `Ctrl+C`
void signal_handler(int sig) {
if (sig == SIGINT || sig == SIGTERM) {
std::cout << "\nReceived signal, shutting down gracefully...\n";
svr.stop();
}
}
int main() {
// Log requests and responses
svr.set_logger([](const auto &req, const auto &res) {
std::cout << req.method << " " << req.path << " -> " << res.status
<< std::endl;
});
// Health check
svr.Get("/health", [](const auto &, auto &res) {
res.set_content(json{{"status", "ok"}}.dump(), "application/json");
});
// Stub implementations for each endpoint (replaced with real ones in later chapters)
svr.Post("/translate",
[](const auto &req, auto &res) {
res.set_content(json{{"translation", "TODO"}}.dump(), "application/json");
});
svr.Post("/translate/stream",
[](const auto &req, auto &res) {
res.set_content("data: \"TODO\"\n\ndata: [DONE]\n\n", "text/event-stream");
});
svr.Get("/models",
[](const auto &req, auto &res) {
res.set_content(json{{"models", json::array()}}.dump(), "application/json");
});
svr.Post("/models/select",
[](const auto &req, auto &res) {
res.set_content(json{{"status", "TODO"}}.dump(), "application/json");
});
// Allow the server to be stopped with `Ctrl+C` (`SIGINT`) or `kill` (`SIGTERM`)
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// Start the server
std::cout << "Listening on http://127.0.0.1:8080" << std::endl;
svr.listen("127.0.0.1", 8080);
}
```
## 1.6 Building and Verifying
Build the project, start the server, and verify that requests work with curl.
```bash
cmake -B build
cmake --build build -j
./build/translate-server
```
From another terminal, try it with curl.
```bash
curl http://localhost:8080/health
# => {"status":"ok"}
```
If you see JSON come back, the setup is complete.
## Next Chapter
Now that the environment is set up, in the next chapter we'll implement the translation REST API on top of this skeleton. We'll run inference with llama.cpp and expose it as an HTTP endpoint with cpp-httplib.
**Next:** [Integrating llama.cpp to Build a REST API](../ch02-rest-api)
+212
View File
@@ -0,0 +1,212 @@
---
title: "2. Integrating llama.cpp to Build a REST API"
order: 2
---
In the skeleton from Chapter 1, `/translate` simply returned `"TODO"`. In this chapter we integrate llama.cpp inference and turn it into an API that actually returns translation results.
Calling the llama.cpp API directly makes the code quite long, so we use a thin wrapper library called [cpp-llamalib](https://github.com/yhirose/cpp-llamalib). It lets you load a model and run inference in just a few lines, keeping the focus on cpp-httplib.
## 2.1 Initializing the LLM
Simply pass the path to a model file to `llamalib::Llama`, and model loading, context creation, and sampler configuration are all taken care of. If you downloaded a different model in Chapter 1, adjust the path accordingly.
```cpp
#include <cpp-llamalib.h>
int main() {
auto llm = llamalib::Llama{"models/gemma-2-2b-it-Q4_K_M.gguf"};
// LLM inference takes time, so set a longer timeout (default is 5 seconds)
svr.set_read_timeout(300);
svr.set_write_timeout(300);
// ... Build and start the HTTP server ...
}
```
If you want to change the number of GPU layers, context length, or other settings, you can specify them via `llamalib::Options`.
```cpp
auto llm = llamalib::Llama{"models/gemma-2-2b-it-Q4_K_M.gguf", {
.n_gpu_layers = 0, // CPU only
.n_ctx = 4096,
}};
```
## 2.2 The `/translate` Handler
We replace the handler that returned dummy JSON in Chapter 1 with actual inference.
```cpp
svr.Post("/translate",
[&](const httplib::Request &req, httplib::Response &res) {
// Parse JSON (3rd arg `false`: don't throw on failure, check with `is_discarded()`)
auto input = json::parse(req.body, nullptr, false);
if (input.is_discarded()) {
res.status = 400;
res.set_content(json{{"error", "Invalid JSON"}}.dump(),
"application/json");
return;
}
// Validate required fields
if (!input.contains("text") || !input["text"].is_string() ||
input["text"].get<std::string>().empty()) {
res.status = 400;
res.set_content(json{{"error", "'text' is required"}}.dump(),
"application/json");
return;
}
auto text = input["text"].get<std::string>();
auto target_lang = input.value("target_lang", "ja"); // Default is Japanese
// Build the prompt and run inference
auto prompt = "Translate the following text to " + target_lang +
". Output only the translation, nothing else.\n\n" + text;
try {
auto translation = llm.chat(prompt);
res.set_content(json{{"translation", translation}}.dump(),
"application/json");
} catch (const std::exception &e) {
res.status = 500;
res.set_content(json{{"error", e.what()}}.dump(), "application/json");
}
});
```
`llm.chat()` can throw exceptions during inference (for example, when the context length is exceeded). By catching them with `try/catch` and returning the error as JSON, we prevent the server from crashing.
## 2.3 Complete Code
Here is the finished code with all the changes so far.
<details>
<summary data-file="main.cpp">Complete code (main.cpp)</summary>
```cpp
#include <httplib.h>
#include <nlohmann/json.hpp>
#include <cpp-llamalib.h>
#include <csignal>
#include <iostream>
using json = nlohmann::json;
httplib::Server svr;
// Graceful shutdown on `Ctrl+C`
void signal_handler(int sig) {
if (sig == SIGINT || sig == SIGTERM) {
std::cout << "\nReceived signal, shutting down gracefully...\n";
svr.stop();
}
}
int main() {
// Load the model downloaded in Chapter 1
auto llm = llamalib::Llama{"models/gemma-2-2b-it-Q4_K_M.gguf"};
// LLM inference takes time, so set a longer timeout (default is 5 seconds)
svr.set_read_timeout(300);
svr.set_write_timeout(300);
// Log requests and responses
svr.set_logger([](const auto &req, const auto &res) {
std::cout << req.method << " " << req.path << " -> " << res.status
<< std::endl;
});
svr.Get("/health", [](const httplib::Request &, httplib::Response &res) {
res.set_content(json{{"status", "ok"}}.dump(), "application/json");
});
svr.Post("/translate",
[&](const httplib::Request &req, httplib::Response &res) {
// Parse JSON (3rd arg `false`: don't throw on failure, check with `is_discarded()`)
auto input = json::parse(req.body, nullptr, false);
if (input.is_discarded()) {
res.status = 400;
res.set_content(json{{"error", "Invalid JSON"}}.dump(),
"application/json");
return;
}
// Validate required fields
if (!input.contains("text") || !input["text"].is_string() ||
input["text"].get<std::string>().empty()) {
res.status = 400;
res.set_content(json{{"error", "'text' is required"}}.dump(),
"application/json");
return;
}
auto text = input["text"].get<std::string>();
auto target_lang = input.value("target_lang", "ja"); // Default is Japanese
// Build the prompt and run inference
auto prompt = "Translate the following text to " + target_lang +
". Output only the translation, nothing else.\n\n" + text;
try {
auto translation = llm.chat(prompt);
res.set_content(json{{"translation", translation}}.dump(),
"application/json");
} catch (const std::exception &e) {
res.status = 500;
res.set_content(json{{"error", e.what()}}.dump(), "application/json");
}
});
// Dummy implementations to be replaced with real ones in later chapters
svr.Get("/models",
[](const httplib::Request &, httplib::Response &res) {
res.set_content(json{{"models", json::array()}}.dump(), "application/json");
});
svr.Post("/models/select",
[](const httplib::Request &, httplib::Response &res) {
res.set_content(json{{"status", "TODO"}}.dump(), "application/json");
});
// Allow the server to be stopped with `Ctrl+C` (`SIGINT`) or `kill` (`SIGTERM`)
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// Start the server (blocks until `stop()` is called)
std::cout << "Listening on http://127.0.0.1:8080" << std::endl;
svr.listen("127.0.0.1", 8080);
}
```
</details>
## 2.4 Testing It Out
Rebuild and start the server, then verify that it now returns actual translation results.
```bash
cmake --build build -j
./build/translate-server
```
```bash
curl -X POST http://localhost:8080/translate \
-H "Content-Type: application/json" \
-d '{"text": "I had a great time visiting Tokyo last spring. The cherry blossoms were beautiful.", "target_lang": "ja"}'
# => {"translation":"去年の春に東京を訪れた。桜が綺麗だった。"}
```
In Chapter 1 the response was `"TODO"`, but now you get an actual translation back.
## Next Chapter
The REST API we built in this chapter waits for the entire translation to complete before sending the response, so for long texts the user has to wait with no indication of progress.
In the next chapter, we use SSE (Server-Sent Events) to stream tokens back in real time as they are generated.
**Next:** [Adding Token Streaming with SSE](../ch03-sse-streaming)
@@ -0,0 +1,264 @@
---
title: "3. Adding Token Streaming with SSE"
order: 3
---
The `/translate` endpoint from Chapter 2 returned the entire translation at once after completion. This is fine for short sentences, but for longer text the user has to wait several seconds with nothing displayed.
In this chapter, we add a `/translate/stream` endpoint that uses SSE (Server-Sent Events) to return tokens in real time as they are generated. This is the same approach used by the ChatGPT and Claude APIs.
## 3.1 What is SSE?
SSE is a way to send HTTP responses as a stream. When a client sends a request, the server keeps the connection open and gradually returns events. The format is simple text.
```text
data: "去年の"
data: "春に"
data: "東京を"
data: [DONE]
```
Each line starts with `data:` and events are separated by blank lines. The Content-Type is `text/event-stream`. Tokens are sent as escaped JSON strings, so they appear enclosed in double quotes (we implement this in Section 3.3).
## 3.2 Streaming with cpp-httplib
In cpp-httplib, you can use `set_chunked_content_provider` to send responses incrementally. Each time you write to `sink.os` inside the callback, data is sent to the client.
```cpp
res.set_chunked_content_provider(
"text/event-stream",
[](size_t offset, httplib::DataSink &sink) {
sink.os << "data: hello\n\n";
sink.done();
return true;
});
```
Calling `sink.done()` ends the stream. If the client disconnects mid-stream, writing to `sink.os` will fail and `sink.os.fail()` will return `true`. You can use this to detect disconnection and abort unnecessary inference.
## 3.3 The `/translate/stream` Handler
JSON parsing and validation are the same as the `/translate` endpoint from Chapter 2. The only difference is how the response is returned. We combine the streaming callback of `llm.chat()` with `set_chunked_content_provider`.
```cpp
svr.Post("/translate/stream",
[&](const httplib::Request &req, httplib::Response &res) {
// ... JSON parsing and validation same as /translate ...
res.set_chunked_content_provider(
"text/event-stream",
[&, prompt](size_t, httplib::DataSink &sink) {
try {
llm.chat(prompt, [&](std::string_view token) {
sink.os << "data: "
<< json(std::string(token)).dump(
-1, ' ', false, json::error_handler_t::replace)
<< "\n\n";
return sink.os.good(); // Abort inference on disconnect
});
sink.os << "data: [DONE]\n\n";
} catch (const std::exception &e) {
sink.os << "data: " << json({{"error", e.what()}}).dump() << "\n\n";
}
sink.done();
return true;
});
});
```
A few key points:
- When you pass a callback to `llm.chat()`, it is called each time a token is generated. If the callback returns `false`, generation is aborted
- After writing to `sink.os`, you can check whether the client is still connected with `sink.os.good()`. If the client has disconnected, it returns `false` to stop inference
- Each token is escaped as a JSON string using `json(token).dump()` before sending. This is safe even for tokens containing newlines or quotes
- The first three arguments of `dump(-1, ' ', false, ...)` are the defaults. What matters is the fourth argument, `json::error_handler_t::replace`. Since the LLM returns tokens at the subword level, multi-byte characters (such as Japanese) can be split mid-character across tokens. Passing an incomplete UTF-8 byte sequence directly to `dump()` would throw an exception, so `replace` safely substitutes them. The browser reassembles the bytes on its end, so everything displays correctly
- The entire lambda is wrapped in `try/catch`. `llm.chat()` can throw exceptions for reasons such as exceeding the context window. If an exception goes uncaught inside the lambda, the server will crash, so we return the error as an SSE event instead
- `data: [DONE]` follows the OpenAI API convention to signal the end of the stream to the client
## 3.4 Complete Code
Here is the complete code with the `/translate/stream` endpoint added to the code from Chapter 2.
<details>
<summary data-file="main.cpp">Complete code (main.cpp)</summary>
```cpp
#include <httplib.h>
#include <nlohmann/json.hpp>
#include <cpp-llamalib.h>
#include <csignal>
#include <iostream>
using json = nlohmann::json;
httplib::Server svr;
// Graceful shutdown on `Ctrl+C`
void signal_handler(int sig) {
if (sig == SIGINT || sig == SIGTERM) {
std::cout << "\nReceived signal, shutting down gracefully...\n";
svr.stop();
}
}
int main() {
// Load the GGUF model
auto llm = llamalib::Llama{"models/gemma-2-2b-it-Q4_K_M.gguf"};
// LLM inference takes time, so set a longer timeout (default is 5 seconds)
svr.set_read_timeout(300);
svr.set_write_timeout(300);
// Log requests and responses
svr.set_logger([](const auto &req, const auto &res) {
std::cout << req.method << " " << req.path << " -> " << res.status
<< std::endl;
});
svr.Get("/health", [](const httplib::Request &, httplib::Response &res) {
res.set_content(json{{"status", "ok"}}.dump(), "application/json");
});
// Standard translation endpoint from Chapter 2
svr.Post("/translate",
[&](const httplib::Request &req, httplib::Response &res) {
// JSON parsing and validation (see Chapter 2 for details)
auto input = json::parse(req.body, nullptr, false);
if (input.is_discarded()) {
res.status = 400;
res.set_content(json{{"error", "Invalid JSON"}}.dump(),
"application/json");
return;
}
if (!input.contains("text") || !input["text"].is_string() ||
input["text"].get<std::string>().empty()) {
res.status = 400;
res.set_content(json{{"error", "'text' is required"}}.dump(),
"application/json");
return;
}
auto text = input["text"].get<std::string>();
auto target_lang = input.value("target_lang", "ja");
auto prompt = "Translate the following text to " + target_lang +
". Output only the translation, nothing else.\n\n" + text;
try {
auto translation = llm.chat(prompt);
res.set_content(json{{"translation", translation}}.dump(),
"application/json");
} catch (const std::exception &e) {
res.status = 500;
res.set_content(json{{"error", e.what()}}.dump(), "application/json");
}
});
// SSE streaming translation endpoint
svr.Post("/translate/stream",
[&](const httplib::Request &req, httplib::Response &res) {
// JSON parsing and validation (same as /translate)
auto input = json::parse(req.body, nullptr, false);
if (input.is_discarded()) {
res.status = 400;
res.set_content(json{{"error", "Invalid JSON"}}.dump(),
"application/json");
return;
}
if (!input.contains("text") || !input["text"].is_string() ||
input["text"].get<std::string>().empty()) {
res.status = 400;
res.set_content(json{{"error", "'text' is required"}}.dump(),
"application/json");
return;
}
auto text = input["text"].get<std::string>();
auto target_lang = input.value("target_lang", "ja");
auto prompt = "Translate the following text to " + target_lang +
". Output only the translation, nothing else.\n\n" + text;
res.set_chunked_content_provider(
"text/event-stream",
[&, prompt](size_t, httplib::DataSink &sink) {
try {
llm.chat(prompt, [&](std::string_view token) {
sink.os << "data: "
<< json(std::string(token)).dump(
-1, ' ', false, json::error_handler_t::replace)
<< "\n\n";
return sink.os.good(); // Abort inference on disconnect
});
sink.os << "data: [DONE]\n\n";
} catch (const std::exception &e) {
sink.os << "data: " << json({{"error", e.what()}}).dump() << "\n\n";
}
sink.done();
return true;
});
});
// Dummy implementations to be replaced in later chapters
svr.Get("/models",
[](const httplib::Request &, httplib::Response &res) {
res.set_content(json{{"models", json::array()}}.dump(), "application/json");
});
svr.Post("/models/select",
[](const httplib::Request &, httplib::Response &res) {
res.set_content(json{{"status", "TODO"}}.dump(), "application/json");
});
// Allow the server to be stopped with `Ctrl+C` (`SIGINT`) or `kill` (`SIGTERM`)
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// Start the server (blocks until `stop()` is called)
std::cout << "Listening on http://127.0.0.1:8080" << std::endl;
svr.listen("127.0.0.1", 8080);
}
```
</details>
## 3.5 Testing It Out
Build and start the server.
```bash
cmake --build build -j
./build/translate-server
```
Using curl's `-N` option to disable buffering, you can see tokens displayed in real time as they arrive.
```bash
curl -N -X POST http://localhost:8080/translate/stream \
-H "Content-Type: application/json" \
-d '{"text": "I had a great time visiting Tokyo last spring. The cherry blossoms were beautiful.", "target_lang": "ja"}'
```
```text
data: "去年の"
data: "春に"
data: "東京を"
data: "訪れた"
data: "。"
data: "桜が"
data: "綺麗だった"
data: "。"
data: [DONE]
```
You should see tokens streaming in one by one. The `/translate` endpoint from Chapter 2 continues to work as well.
## Next Chapter
The server's translation functionality is now complete. In the next chapter, we use cpp-httplib's client functionality to add the ability to fetch and manage models from Hugging Face.
**Next:** [Adding Model Download and Management](../ch04-model-management)
@@ -0,0 +1,788 @@
---
title: "4. Adding Model Download and Management"
order: 4
---
By the end of Chapter 3, the server's translation functionality was fully in place. However, the only model file available is the one we manually downloaded in Chapter 1. In this chapter, we'll use cpp-httplib's **client functionality** to enable downloading and switching Hugging Face models from within the app.
Once complete, you'll be able to manage models with requests like these:
```bash
# Get the list of available models
curl http://localhost:8080/models
```
```json
{
"models": [
{"name": "gemma-2-2b-it", "params": "2B", "size": "1.6 GB", "downloaded": true, "selected": true},
{"name": "gemma-2-9b-it", "params": "9B", "size": "5.8 GB", "downloaded": false, "selected": false},
{"name": "Llama-3.1-8B-Instruct", "params": "8B", "size": "4.9 GB", "downloaded": false, "selected": false}
]
}
```
```bash
# Select a different model (automatically downloads if not yet available)
curl -N -X POST http://localhost:8080/models/select \
-H "Content-Type: application/json" \
-d '{"model": "gemma-2-9b-it"}'
```
```text
data: {"status":"downloading","progress":0}
data: {"status":"downloading","progress":12}
...
data: {"status":"downloading","progress":100}
data: {"status":"loading"}
data: {"status":"ready"}
```
## 4.1 httplib::Client Basics
So far we've only used `httplib::Server`, but cpp-httplib also provides client functionality. Since Hugging Face uses HTTPS, we need a TLS-capable client.
```cpp
#include <httplib.h>
// Including the URL scheme automatically uses SSLClient
httplib::Client cli("https://huggingface.co");
// Automatically follow redirects (Hugging Face redirects to a CDN)
cli.set_follow_location(true);
auto res = cli.Get("/api/models");
if (res && res->status == 200) {
std::cout << res->body << std::endl;
}
```
To use HTTPS, you need to enable OpenSSL at build time. Add the following to your `CMakeLists.txt`:
```cmake
find_package(OpenSSL REQUIRED)
target_link_libraries(translate-server PRIVATE OpenSSL::SSL OpenSSL::Crypto)
target_compile_definitions(translate-server PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
# macOS: required for loading system certificates
if(APPLE)
target_link_libraries(translate-server PRIVATE "-framework CoreFoundation" "-framework Security")
endif()
```
Defining `CPPHTTPLIB_OPENSSL_SUPPORT` enables `httplib::Client("https://...")` to make TLS connections. On macOS, you also need to link the CoreFoundation and Security frameworks to access the system certificate store. See Section 4.8 for the complete `CMakeLists.txt`.
## 4.2 Defining the Model List
Let's define the list of models that the app can handle. Here are four models we've verified for translation tasks.
```cpp
struct ModelInfo {
std::string name; // Display name
std::string params; // Parameter count
std::string size; // GGUF Q4 size
std::string repo; // Hugging Face repository
std::string filename; // GGUF filename
};
const std::vector<ModelInfo> MODELS = {
{
.name = "gemma-2-2b-it",
.params = "2B",
.size = "1.6 GB",
.repo = "bartowski/gemma-2-2b-it-GGUF",
.filename = "gemma-2-2b-it-Q4_K_M.gguf",
},
{
.name = "gemma-2-9b-it",
.params = "9B",
.size = "5.8 GB",
.repo = "bartowski/gemma-2-9b-it-GGUF",
.filename = "gemma-2-9b-it-Q4_K_M.gguf",
},
{
.name = "Llama-3.1-8B-Instruct",
.params = "8B",
.size = "4.9 GB",
.repo = "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF",
.filename = "Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf",
},
};
```
## 4.3 Model Storage Location
Up through Chapter 3, we stored models in the `models/` directory within the project. However, when managing multiple models, a dedicated app directory makes more sense. On macOS/Linux we use `~/.translate-app/models/`, and on Windows we use `%APPDATA%\translate-app\models\`.
```cpp
std::filesystem::path get_models_dir() {
#ifdef _WIN32
auto env = std::getenv("APPDATA");
auto base = env ? std::filesystem::path(env) : std::filesystem::path(".");
return base / "translate-app" / "models";
#else
auto env = std::getenv("HOME");
auto base = env ? std::filesystem::path(env) : std::filesystem::path(".");
return base / ".translate-app" / "models";
#endif
}
```
If the environment variable isn't set, it falls back to the current directory. The app creates this directory at startup (`create_directories` won't error even if it already exists).
## 4.4 Rewriting Model Initialization
We rewrite the model initialization at the beginning of `main()`. In Chapter 1 we hardcoded the path, but from here on we support model switching. We track the currently loaded filename in `selected_model` and load the first entry in `MODELS` at startup. The `GET /models` and `POST /models/select` handlers reference and update this variable.
Since cpp-httplib runs handlers concurrently on a thread pool, reassigning `llm` while another thread is calling `llm.chat()` would crash. We add a `std::mutex` to protect against this.
```cpp
int main() {
auto models_dir = get_models_dir();
std::filesystem::create_directories(models_dir);
std::string selected_model = MODELS[0].filename;
auto path = models_dir / selected_model;
// Automatically download the default model if not yet present
if (!std::filesystem::exists(path)) {
std::cout << "Downloading " << selected_model << "..." << std::endl;
if (!download_model(MODELS[0], [](int pct) {
std::cout << "\r" << pct << "%" << std::flush;
return true;
})) {
std::cerr << "\nFailed to download model." << std::endl;
return 1;
}
std::cout << std::endl;
}
auto llm = llamalib::Llama{path};
std::mutex llm_mutex; // Protect access during model switching
// ...
}
```
This ensures that users don't need to manually download models with curl on first launch. It uses the `download_model` function from Section 4.6 and displays progress on the console.
## 4.5 The `GET /models` Handler
This returns the model list with information about whether each model has been downloaded and whether it's currently selected.
```cpp
svr.Get("/models",
[&](const httplib::Request &, httplib::Response &res) {
auto arr = json::array();
for (const auto &m : MODELS) {
auto path = get_models_dir() / m.filename;
arr.push_back({
{"name", m.name},
{"params", m.params},
{"size", m.size},
{"downloaded", std::filesystem::exists(path)},
{"selected", m.filename == selected_model},
});
}
res.set_content(json{{"models", arr}}.dump(), "application/json");
});
```
## 4.6 Downloading Large Files
GGUF models are several gigabytes, so we can't load the entire file into memory. By passing callbacks to `httplib::Client::Get`, we can receive data chunk by chunk.
```cpp
// content_receiver: callback that receives data chunks
// progress: download progress callback
cli.Get(url,
[&](const char *data, size_t len) { // content_receiver
ofs.write(data, len);
return true; // returning false aborts the download
},
[&](size_t current, size_t total) { // progress
int pct = total ? (int)(current * 100 / total) : 0;
std::cout << pct << "%" << std::endl;
return true; // returning false aborts the download
});
```
Let's use this to create a function that downloads models from Hugging Face.
```cpp
#include <filesystem>
#include <fstream>
// Download a model and report progress via progress_cb.
// If progress_cb returns false, the download is aborted.
bool download_model(const ModelInfo &model,
std::function<bool(int)> progress_cb) {
httplib::Client cli("https://huggingface.co");
cli.set_follow_location(true);
cli.set_read_timeout(std::chrono::hours(1));
auto url = "/" + model.repo + "/resolve/main/" + model.filename;
auto path = get_models_dir() / model.filename;
auto tmp_path = std::filesystem::path(path).concat(".tmp");
std::ofstream ofs(tmp_path, std::ios::binary);
if (!ofs) { return false; }
auto res = cli.Get(url,
[&](const char *data, size_t len) {
ofs.write(data, len);
return ofs.good();
},
[&](size_t current, size_t total) {
return progress_cb(total ? (int)(current * 100 / total) : 0);
});
ofs.close();
if (!res || res->status != 200) {
std::filesystem::remove(tmp_path);
return false;
}
// Write to .tmp first, then rename, so that an incomplete file
// is never mistaken for a usable model if the download is interrupted
std::filesystem::rename(tmp_path, path);
return true;
}
```
## 4.7 The `/models/select` Handler
This handles model selection requests. We always respond with SSE, reporting status in sequence: download progress, loading, and ready.
```cpp
svr.Post("/models/select",
[&](const httplib::Request &req, httplib::Response &res) {
auto input = json::parse(req.body, nullptr, false);
if (input.is_discarded() || !input.contains("model")) {
res.status = 400;
res.set_content(json{{"error", "'model' is required"}}.dump(),
"application/json");
return;
}
auto name = input["model"].get<std::string>();
// Find the model in the list
auto it = std::find_if(MODELS.begin(), MODELS.end(),
[&](const ModelInfo &m) { return m.name == name; });
if (it == MODELS.end()) {
res.status = 404;
res.set_content(json{{"error", "Unknown model"}}.dump(),
"application/json");
return;
}
const auto &model = *it;
// Always respond with SSE (same format whether already downloaded or not)
res.set_chunked_content_provider(
"text/event-stream",
[&, model](size_t, httplib::DataSink &sink) {
// SSE event sending helper
auto send = [&](const json &event) {
sink.os << "data: " << event.dump() << "\n\n";
};
// Download if not yet present (report progress via SSE)
auto path = get_models_dir() / model.filename;
if (!std::filesystem::exists(path)) {
bool ok = download_model(model, [&](int pct) {
send({{"status", "downloading"}, {"progress", pct}});
return sink.os.good(); // Abort download on client disconnect
});
if (!ok) {
send({{"status", "error"}, {"message", "Download failed"}});
sink.done();
return true;
}
}
// Load and switch to the model
send({{"status", "loading"}});
{
std::lock_guard<std::mutex> lock(llm_mutex);
llm = llamalib::Llama{path};
selected_model = model.filename;
}
send({{"status", "ready"}});
sink.done();
return true;
});
});
```
A few notes:
- We send SSE events directly from the `download_model` progress callback. This is an application of `set_chunked_content_provider` + `sink.os` from Chapter 3
- Since the callback returns `sink.os.good()`, the download stops if the client disconnects. The cancel button we add in Chapter 5 uses this
- When we update `selected_model`, it's reflected in the `selected` flag of `GET /models`
- The `llm` reassignment is protected by `llm_mutex`. The `/translate` and `/translate/stream` handlers also lock the same mutex, so inference can't run during a model switch (see the complete code)
## 4.8 Complete Code
Here is the complete code with model management added to the Chapter 3 code.
<details>
<summary data-file="CMakeLists.txt">Complete code (CMakeLists.txt)</summary>
```cmake
cmake_minimum_required(VERSION 3.20)
project(translate-server CXX)
set(CMAKE_CXX_STANDARD 20)
include(FetchContent)
# llama.cpp
FetchContent_Declare(llama
GIT_REPOSITORY https://github.com/ggml-org/llama.cpp
GIT_TAG master
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(llama)
# cpp-httplib
FetchContent_Declare(httplib
GIT_REPOSITORY https://github.com/yhirose/cpp-httplib
GIT_TAG master
)
FetchContent_MakeAvailable(httplib)
# nlohmann/json
FetchContent_Declare(json
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
)
FetchContent_MakeAvailable(json)
# cpp-llamalib
FetchContent_Declare(cpp_llamalib
GIT_REPOSITORY https://github.com/yhirose/cpp-llamalib
GIT_TAG main
)
FetchContent_MakeAvailable(cpp_llamalib)
find_package(OpenSSL REQUIRED)
add_executable(translate-server src/main.cpp)
target_link_libraries(translate-server PRIVATE
httplib::httplib
nlohmann_json::nlohmann_json
cpp-llamalib
OpenSSL::SSL OpenSSL::Crypto
)
target_compile_definitions(translate-server PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
if(APPLE)
target_link_libraries(translate-server PRIVATE
"-framework CoreFoundation"
"-framework Security"
)
endif()
```
</details>
<details>
<summary data-file="main.cpp">Complete code (main.cpp)</summary>
```cpp
#include <httplib.h>
#include <nlohmann/json.hpp>
#include <cpp-llamalib.h>
#include <algorithm>
#include <csignal>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <mutex>
using json = nlohmann::json;
// -------------------------------------------------------------------------
// Model definitions
// -------------------------------------------------------------------------
struct ModelInfo {
std::string name;
std::string params;
std::string size;
std::string repo;
std::string filename;
};
const std::vector<ModelInfo> MODELS = {
{
.name = "gemma-2-2b-it",
.params = "2B",
.size = "1.6 GB",
.repo = "bartowski/gemma-2-2b-it-GGUF",
.filename = "gemma-2-2b-it-Q4_K_M.gguf",
},
{
.name = "gemma-2-9b-it",
.params = "9B",
.size = "5.8 GB",
.repo = "bartowski/gemma-2-9b-it-GGUF",
.filename = "gemma-2-9b-it-Q4_K_M.gguf",
},
{
.name = "Llama-3.1-8B-Instruct",
.params = "8B",
.size = "4.9 GB",
.repo = "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF",
.filename = "Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf",
},
};
// -------------------------------------------------------------------------
// Model storage directory
// -------------------------------------------------------------------------
std::filesystem::path get_models_dir() {
#ifdef _WIN32
auto env = std::getenv("APPDATA");
auto base = env ? std::filesystem::path(env) : std::filesystem::path(".");
return base / "translate-app" / "models";
#else
auto env = std::getenv("HOME");
auto base = env ? std::filesystem::path(env) : std::filesystem::path(".");
return base / ".translate-app" / "models";
#endif
}
// -------------------------------------------------------------------------
// Model download
// -------------------------------------------------------------------------
// If progress_cb returns false, the download is aborted
bool download_model(const ModelInfo &model,
std::function<bool(int)> progress_cb) {
httplib::Client cli("https://huggingface.co");
cli.set_follow_location(true); // Hugging Face redirects to a CDN
cli.set_read_timeout(std::chrono::hours(1)); // Set a long timeout for large models
auto url = "/" + model.repo + "/resolve/main/" + model.filename;
auto path = get_models_dir() / model.filename;
auto tmp_path = std::filesystem::path(path).concat(".tmp");
std::ofstream ofs(tmp_path, std::ios::binary);
if (!ofs) { return false; }
auto res = cli.Get(url,
// content_receiver: receive data chunk by chunk and write to file
[&](const char *data, size_t len) {
ofs.write(data, len);
return ofs.good();
},
// progress: report download progress (returning false aborts)
[&, last_pct = -1](size_t current, size_t total) mutable {
int pct = total ? (int)(current * 100 / total) : 0;
if (pct == last_pct) return true; // Skip if same value
last_pct = pct;
return progress_cb(pct);
});
ofs.close();
if (!res || res->status != 200) {
std::filesystem::remove(tmp_path);
return false;
}
// Rename after download completes
std::filesystem::rename(tmp_path, path);
return true;
}
// -------------------------------------------------------------------------
// Server
// -------------------------------------------------------------------------
httplib::Server svr;
void signal_handler(int sig) {
if (sig == SIGINT || sig == SIGTERM) {
std::cout << "\nReceived signal, shutting down gracefully...\n";
svr.stop();
}
}
int main() {
// Create the model storage directory
auto models_dir = get_models_dir();
std::filesystem::create_directories(models_dir);
// Automatically download the default model if not yet present
std::string selected_model = MODELS[0].filename;
auto path = models_dir / selected_model;
if (!std::filesystem::exists(path)) {
std::cout << "Downloading " << selected_model << "..." << std::endl;
if (!download_model(MODELS[0], [](int pct) {
std::cout << "\r" << pct << "%" << std::flush;
return true;
})) {
std::cerr << "\nFailed to download model." << std::endl;
return 1;
}
std::cout << std::endl;
}
auto llm = llamalib::Llama{path};
std::mutex llm_mutex; // Protect access during model switching
// Set a long timeout since LLM inference takes time (default is 5 seconds)
svr.set_read_timeout(300);
svr.set_write_timeout(300);
svr.set_logger([](const auto &req, const auto &res) {
std::cout << req.method << " " << req.path << " -> " << res.status
<< std::endl;
});
svr.Get("/health", [](const httplib::Request &, httplib::Response &res) {
res.set_content(json{{"status", "ok"}}.dump(), "application/json");
});
// --- Translation endpoint (Chapter 2) ------------------------------------
svr.Post("/translate",
[&](const httplib::Request &req, httplib::Response &res) {
// JSON parsing and validation (see Chapter 2 for details)
auto input = json::parse(req.body, nullptr, false);
if (input.is_discarded()) {
res.status = 400;
res.set_content(json{{"error", "Invalid JSON"}}.dump(),
"application/json");
return;
}
if (!input.contains("text") || !input["text"].is_string() ||
input["text"].get<std::string>().empty()) {
res.status = 400;
res.set_content(json{{"error", "'text' is required"}}.dump(),
"application/json");
return;
}
auto text = input["text"].get<std::string>();
auto target_lang = input.value("target_lang", "ja");
auto prompt = "Translate the following text to " + target_lang +
". Output only the translation, nothing else.\n\n" + text;
try {
std::lock_guard<std::mutex> lock(llm_mutex);
auto translation = llm.chat(prompt);
res.set_content(json{{"translation", translation}}.dump(),
"application/json");
} catch (const std::exception &e) {
res.status = 500;
res.set_content(json{{"error", e.what()}}.dump(), "application/json");
}
});
// --- SSE streaming translation (Chapter 3) -------------------------------
svr.Post("/translate/stream",
[&](const httplib::Request &req, httplib::Response &res) {
auto input = json::parse(req.body, nullptr, false);
if (input.is_discarded()) {
res.status = 400;
res.set_content(json{{"error", "Invalid JSON"}}.dump(),
"application/json");
return;
}
if (!input.contains("text") || !input["text"].is_string() ||
input["text"].get<std::string>().empty()) {
res.status = 400;
res.set_content(json{{"error", "'text' is required"}}.dump(),
"application/json");
return;
}
auto text = input["text"].get<std::string>();
auto target_lang = input.value("target_lang", "ja");
auto prompt = "Translate the following text to " + target_lang +
". Output only the translation, nothing else.\n\n" + text;
res.set_chunked_content_provider(
"text/event-stream",
[&, prompt](size_t, httplib::DataSink &sink) {
std::lock_guard<std::mutex> lock(llm_mutex);
try {
llm.chat(prompt, [&](std::string_view token) {
sink.os << "data: "
<< json(std::string(token)).dump(
-1, ' ', false, json::error_handler_t::replace)
<< "\n\n";
return sink.os.good(); // Abort inference on disconnect
});
sink.os << "data: [DONE]\n\n";
} catch (const std::exception &e) {
sink.os << "data: " << json({{"error", e.what()}}).dump() << "\n\n";
}
sink.done();
return true;
});
});
// --- Model list (Chapter 4) ----------------------------------------------
svr.Get("/models",
[&](const httplib::Request &, httplib::Response &res) {
auto models_dir = get_models_dir();
auto arr = json::array();
for (const auto &m : MODELS) {
auto path = models_dir / m.filename;
arr.push_back({
{"name", m.name},
{"params", m.params},
{"size", m.size},
{"downloaded", std::filesystem::exists(path)},
{"selected", m.filename == selected_model},
});
}
res.set_content(json{{"models", arr}}.dump(), "application/json");
});
// --- Model selection (Chapter 4) -----------------------------------------
svr.Post("/models/select",
[&](const httplib::Request &req, httplib::Response &res) {
auto input = json::parse(req.body, nullptr, false);
if (input.is_discarded() || !input.contains("model")) {
res.status = 400;
res.set_content(json{{"error", "'model' is required"}}.dump(),
"application/json");
return;
}
auto name = input["model"].get<std::string>();
auto it = std::find_if(MODELS.begin(), MODELS.end(),
[&](const ModelInfo &m) { return m.name == name; });
if (it == MODELS.end()) {
res.status = 404;
res.set_content(json{{"error", "Unknown model"}}.dump(),
"application/json");
return;
}
const auto &model = *it;
// Always respond with SSE (same format whether already downloaded or not)
res.set_chunked_content_provider(
"text/event-stream",
[&, model](size_t, httplib::DataSink &sink) {
// SSE event sending helper
auto send = [&](const json &event) {
sink.os << "data: " << event.dump() << "\n\n";
};
// Download if not yet present (report progress via SSE)
auto path = get_models_dir() / model.filename;
if (!std::filesystem::exists(path)) {
bool ok = download_model(model, [&](int pct) {
send({{"status", "downloading"}, {"progress", pct}});
return sink.os.good(); // Abort download on client disconnect
});
if (!ok) {
send({{"status", "error"}, {"message", "Download failed"}});
sink.done();
return true;
}
}
// Load and switch to the model
send({{"status", "loading"}});
{
std::lock_guard<std::mutex> lock(llm_mutex);
llm = llamalib::Llama{path};
selected_model = model.filename;
}
send({{"status", "ready"}});
sink.done();
return true;
});
});
// Allow the server to be stopped with `Ctrl+C` (`SIGINT`) or `kill` (`SIGTERM`)
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
std::cout << "Listening on http://127.0.0.1:8080" << std::endl;
svr.listen("127.0.0.1", 8080);
}
```
</details>
## 4.9 Testing
Since we added OpenSSL configuration to CMakeLists.txt, we need to re-run CMake before building.
```bash
cmake -B build
cmake --build build -j
./build/translate-server
```
### Checking the Model List
```bash
curl http://localhost:8080/models
```
The gemma-2-2b-it model downloaded in Chapter 1 should show `downloaded: true` and `selected: true`.
### Switching to a Different Model
```bash
curl -N -X POST http://localhost:8080/models/select \
-H "Content-Type: application/json" \
-d '{"model": "gemma-2-9b-it"}'
```
Download progress streams via SSE, and `"ready"` appears when it's done.
### Comparing Translations Across Models
Let's translate the same sentence with different models.
```bash
# Translate with gemma-2-9b-it (the model we just switched to)
curl -X POST http://localhost:8080/translate \
-H "Content-Type: application/json" \
-d '{"text": "The quick brown fox jumps over the lazy dog.", "target_lang": "ja"}'
# Switch back to gemma-2-2b-it
curl -N -X POST http://localhost:8080/models/select \
-H "Content-Type: application/json" \
-d '{"model": "gemma-2-2b-it"}'
# Translate the same sentence
curl -X POST http://localhost:8080/translate \
-H "Content-Type: application/json" \
-d '{"text": "The quick brown fox jumps over the lazy dog.", "target_lang": "ja"}'
```
Translation results vary depending on the model, even with the same code and the same prompt. Since cpp-llamalib automatically applies the appropriate chat template for each model, no code changes are needed.
## Next Chapter
The server's main features are now complete: REST API, SSE streaming, and model download and switching. In the next chapter, we'll add static file serving and build a Web UI you can use from a browser.
**Next:** [Adding a Web UI](../ch05-web-ui)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,724 @@
---
title: "6. Turning It into a Desktop App with WebView"
order: 6
---
In Chapter 5, we completed a translation app you can use from a browser. But every time, you have to start the server, open the URL in a browser... Wouldn't it be nice to just double-click and start using it, like a normal app?
In this chapter, we'll do two things:
1. **WebView integration** — Use [webview/webview](https://github.com/webview/webview) to turn it into a desktop app that runs without a browser
2. **Single binary packaging** — Use [cpp-embedlib](https://github.com/yhirose/cpp-embedlib) to embed HTML/CSS/JS into the binary, making the distributable a single file
When finished, you'll be able to just run `./translate-app` to open a window and start translating.
![Desktop App](../app.png#large-center)
The model downloads automatically on first launch, so the only thing you need to give users is the single binary.
## 6.1 Introducing webview/webview
[webview/webview](https://github.com/webview/webview) is a library that lets you use the OS's native WebView component (WKWebView on macOS, WebKitGTK on Linux, WebView2 on Windows) from C/C++. Unlike Electron, it doesn't bundle its own browser, so the impact on binary size is negligible.
We'll fetch it with CMake. Add the following to your `CMakeLists.txt`:
```cmake
# webview/webview
FetchContent_Declare(webview
GIT_REPOSITORY https://github.com/webview/webview
GIT_TAG master
)
FetchContent_MakeAvailable(webview)
```
This makes the `webview::core` CMake target available. When you link it with `target_link_libraries`, it automatically sets up include paths and platform-specific frameworks.
> **macOS**: No additional dependencies are needed. WKWebView is built into the system.
>
> **Linux**: WebKitGTK is required. Install it with `sudo apt install libwebkit2gtk-4.1-dev`.
>
> **Windows**: The WebView2 runtime is required. It comes pre-installed on Windows 11. For Windows 10, download it from the [official Microsoft website](https://developer.microsoft.com/en-us/microsoft-edge/webview2/).
## 6.2 Running the Server on a Background Thread
Up through Chapter 5, the server's `listen()` was blocking the main thread. To use WebView, we need to run the server on a separate thread and run the WebView event loop on the main thread.
```cpp
#include "webview/webview.h"
#include <thread>
int main() {
// ... (server setup is the same as Chapter 5) ...
// Start the server on a background thread
auto port = svr.bind_to_any_port("127.0.0.1");
std::thread server_thread([&]() { svr.listen_after_bind(); });
std::cout << "Listening on http://127.0.0.1:" << port << std::endl;
// Display the UI with WebView
webview::webview w(false, nullptr);
w.set_title("Translate App");
w.set_size(1024, 768, WEBVIEW_HINT_NONE);
w.navigate("http://127.0.0.1:" + std::to_string(port));
w.run(); // Block until the window is closed
// Stop the server when the window is closed
svr.stop();
server_thread.join();
}
```
Let's look at the key points:
- **`bind_to_any_port`** — Instead of `listen("127.0.0.1", 8080)`, we let the OS choose an available port. Since desktop apps can be launched multiple times, using a fixed port would cause conflicts
- **`listen_after_bind`** — Starts accepting requests on the port reserved by `bind_to_any_port`. While `listen()` does bind and listen in one call, we need to know the port number first, so we split the operations
- **Shutdown order** — When the WebView window is closed, we stop the server with `svr.stop()` and wait for the thread to finish with `server_thread.join()`. If we reversed the order, WebView would lose access to the server
The `signal_handler` from Chapter 5 is no longer needed. In a desktop app, closing the window means terminating the application.
## 6.3 Embedding Static Files with cpp-embedlib
In Chapter 5, we served files from the `public/` directory, so you'd need to distribute `public/` alongside the binary. With [cpp-embedlib](https://github.com/yhirose/cpp-embedlib), you can embed HTML, CSS, and JavaScript into the binary, packaging the distributable into a single file.
### CMakeLists.txt
Fetch cpp-embedlib and embed `public/`:
```cmake
# cpp-embedlib
FetchContent_Declare(cpp-embedlib
GIT_REPOSITORY https://github.com/yhirose/cpp-embedlib
GIT_TAG main
)
FetchContent_MakeAvailable(cpp-embedlib)
# Embed the public/ directory into the binary
cpp_embedlib_add(WebAssets
FOLDER ${CMAKE_CURRENT_SOURCE_DIR}/public
NAMESPACE Web
)
target_link_libraries(translate-app PRIVATE
WebAssets # Embedded files
cpp-embedlib-httplib # cpp-httplib integration
)
```
`cpp_embedlib_add` converts the files under `public/` into binary data at compile time and creates a static library called `WebAssets`. When linked, you can access the embedded files through a `Web::FS` object. `cpp-embedlib-httplib` is a helper library that provides the `httplib::mount()` function.
### Replacing set_mount_point with httplib::mount
Simply replace Chapter 5's `set_mount_point` with cpp-embedlib's `httplib::mount`:
```cpp
#include <cpp-embedlib-httplib.h>
#include "WebAssets.h"
// Chapter 5:
// svr.set_mount_point("/", "./public");
// Chapter 6:
httplib::mount(svr, Web::FS);
```
`httplib::mount` registers handlers that serve the files embedded in `Web::FS` over HTTP. MIME types are automatically determined from file extensions, so there's no need to manually set `Content-Type`.
The file contents are directly mapped to the binary's data segment, so no memory copies or heap allocations occur.
## 6.4 macOS: Adding the Edit Menu
If you try to paste text into the input field with `Cmd+V`, you'll find it doesn't work. On macOS, keyboard shortcuts like `Cmd+V` (paste) and `Cmd+C` (copy) are routed through the application's menu bar. Since webview/webview doesn't create one, these shortcuts never reach the WebView. We need to add a macOS Edit menu using the Objective-C runtime:
```cpp
#ifdef __APPLE__
#include <objc/objc-runtime.h>
void setup_macos_edit_menu() {
auto cls = [](const char *n) { return (id)objc_getClass(n); };
auto sel = sel_registerName;
auto msg = reinterpret_cast<id (*)(id, SEL)>(objc_msgSend);
auto msg_s = reinterpret_cast<id (*)(id, SEL, const char *)>(objc_msgSend);
auto msg_id = reinterpret_cast<id (*)(id, SEL, id)>(objc_msgSend);
auto msg_v = reinterpret_cast<void (*)(id, SEL, id)>(objc_msgSend);
auto msg_mi = reinterpret_cast<id (*)(id, SEL, id, SEL, id)>(objc_msgSend);
auto str = [&](const char *s) {
return msg_s(cls("NSString"), sel("stringWithUTF8String:"), s);
};
id app = msg(cls("NSApplication"), sel("sharedApplication"));
id mainMenu = msg(msg(cls("NSMenu"), sel("alloc")), sel("init"));
id editItem = msg(msg(cls("NSMenuItem"), sel("alloc")), sel("init"));
id editMenu = msg_id(msg(cls("NSMenu"), sel("alloc")),
sel("initWithTitle:"), str("Edit"));
struct { const char *title; const char *action; const char *key; } items[] = {
{"Undo", "undo:", "z"},
{"Redo", "redo:", "Z"},
{"Cut", "cut:", "x"},
{"Copy", "copy:", "c"},
{"Paste", "paste:", "v"},
{"Select All", "selectAll:", "a"},
};
for (auto &[title, action, key] : items) {
id mi = msg_mi(msg(cls("NSMenuItem"), sel("alloc")),
sel("initWithTitle:action:keyEquivalent:"),
str(title), sel(action), str(key));
msg_v(editMenu, sel("addItem:"), mi);
}
msg_v(editItem, sel("setSubmenu:"), editMenu);
msg_v(mainMenu, sel("addItem:"), editItem);
msg_v(app, sel("setMainMenu:"), mainMenu);
}
#endif
```
Call this before `w.run()`:
```cpp
#ifdef __APPLE__
setup_macos_edit_menu();
#endif
w.run();
```
On Windows and Linux, keyboard shortcuts are delivered directly to the focused control without going through the menu bar, so this workaround is macOS-specific.
## 6.5 Complete Code
<details>
<summary data-file="CMakeLists.txt">Complete code (CMakeLists.txt)</summary>
```cmake
cmake_minimum_required(VERSION 3.20)
project(translate-app CXX)
set(CMAKE_CXX_STANDARD 20)
include(FetchContent)
# llama.cpp
FetchContent_Declare(llama
GIT_REPOSITORY https://github.com/ggml-org/llama.cpp
GIT_TAG master
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(llama)
# cpp-httplib
FetchContent_Declare(httplib
GIT_REPOSITORY https://github.com/yhirose/cpp-httplib
GIT_TAG master
)
FetchContent_MakeAvailable(httplib)
# nlohmann/json
FetchContent_Declare(json
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
)
FetchContent_MakeAvailable(json)
# cpp-llamalib
FetchContent_Declare(cpp_llamalib
GIT_REPOSITORY https://github.com/yhirose/cpp-llamalib
GIT_TAG main
)
FetchContent_MakeAvailable(cpp_llamalib)
# webview/webview
FetchContent_Declare(webview
GIT_REPOSITORY https://github.com/webview/webview
GIT_TAG master
)
FetchContent_MakeAvailable(webview)
# cpp-embedlib
FetchContent_Declare(cpp-embedlib
GIT_REPOSITORY https://github.com/yhirose/cpp-embedlib
GIT_TAG main
)
FetchContent_MakeAvailable(cpp-embedlib)
# Embed the public/ directory into the binary
cpp_embedlib_add(WebAssets
FOLDER ${CMAKE_CURRENT_SOURCE_DIR}/public
NAMESPACE Web
)
find_package(OpenSSL REQUIRED)
add_executable(translate-app src/main.cpp)
target_link_libraries(translate-app PRIVATE
httplib::httplib
nlohmann_json::nlohmann_json
cpp-llamalib
OpenSSL::SSL OpenSSL::Crypto
WebAssets
cpp-embedlib-httplib
webview::core
)
if(APPLE)
target_link_libraries(translate-app PRIVATE
"-framework CoreFoundation"
"-framework Security"
)
endif()
target_compile_definitions(translate-app PRIVATE
CPPHTTPLIB_OPENSSL_SUPPORT
)
```
</details>
<details>
<summary data-file="main.cpp">Complete code (main.cpp)</summary>
```cpp
#include <httplib.h>
#include <nlohmann/json.hpp>
#include <cpp-llamalib.h>
#include <cpp-embedlib-httplib.h>
#include "WebAssets.h"
#include "webview/webview.h"
#ifdef __APPLE__
#include <objc/objc-runtime.h>
#endif
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <mutex>
#include <thread>
using json = nlohmann::json;
// -------------------------------------------------------------------------
// macOS Edit menu (Cmd+C/V/X/A require an Edit menu on macOS)
// -------------------------------------------------------------------------
#ifdef __APPLE__
void setup_macos_edit_menu() {
auto cls = [](const char *n) { return (id)objc_getClass(n); };
auto sel = sel_registerName;
auto msg = reinterpret_cast<id (*)(id, SEL)>(objc_msgSend);
auto msg_s = reinterpret_cast<id (*)(id, SEL, const char *)>(objc_msgSend);
auto msg_id = reinterpret_cast<id (*)(id, SEL, id)>(objc_msgSend);
auto msg_v = reinterpret_cast<void (*)(id, SEL, id)>(objc_msgSend);
auto msg_mi = reinterpret_cast<id (*)(id, SEL, id, SEL, id)>(objc_msgSend);
auto str = [&](const char *s) {
return msg_s(cls("NSString"), sel("stringWithUTF8String:"), s);
};
id app = msg(cls("NSApplication"), sel("sharedApplication"));
id mainMenu = msg(msg(cls("NSMenu"), sel("alloc")), sel("init"));
id editItem = msg(msg(cls("NSMenuItem"), sel("alloc")), sel("init"));
id editMenu = msg_id(msg(cls("NSMenu"), sel("alloc")),
sel("initWithTitle:"), str("Edit"));
struct { const char *title; const char *action; const char *key; } items[] = {
{"Undo", "undo:", "z"},
{"Redo", "redo:", "Z"},
{"Cut", "cut:", "x"},
{"Copy", "copy:", "c"},
{"Paste", "paste:", "v"},
{"Select All", "selectAll:", "a"},
};
for (auto &[title, action, key] : items) {
id mi = msg_mi(msg(cls("NSMenuItem"), sel("alloc")),
sel("initWithTitle:action:keyEquivalent:"),
str(title), sel(action), str(key));
msg_v(editMenu, sel("addItem:"), mi);
}
msg_v(editItem, sel("setSubmenu:"), editMenu);
msg_v(mainMenu, sel("addItem:"), editItem);
msg_v(app, sel("setMainMenu:"), mainMenu);
}
#endif
// -------------------------------------------------------------------------
// Model definitions
// -------------------------------------------------------------------------
struct ModelInfo {
std::string name;
std::string params;
std::string size;
std::string repo;
std::string filename;
};
const std::vector<ModelInfo> MODELS = {
{
.name = "gemma-2-2b-it",
.params = "2B",
.size = "1.6 GB",
.repo = "bartowski/gemma-2-2b-it-GGUF",
.filename = "gemma-2-2b-it-Q4_K_M.gguf",
},
{
.name = "gemma-2-9b-it",
.params = "9B",
.size = "5.8 GB",
.repo = "bartowski/gemma-2-9b-it-GGUF",
.filename = "gemma-2-9b-it-Q4_K_M.gguf",
},
{
.name = "Llama-3.1-8B-Instruct",
.params = "8B",
.size = "4.9 GB",
.repo = "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF",
.filename = "Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf",
},
};
// -------------------------------------------------------------------------
// Model storage directory
// -------------------------------------------------------------------------
std::filesystem::path get_models_dir() {
#ifdef _WIN32
auto env = std::getenv("APPDATA");
auto base = env ? std::filesystem::path(env) : std::filesystem::path(".");
return base / "translate-app" / "models";
#else
auto env = std::getenv("HOME");
auto base = env ? std::filesystem::path(env) : std::filesystem::path(".");
return base / ".translate-app" / "models";
#endif
}
// -------------------------------------------------------------------------
// Model download
// -------------------------------------------------------------------------
// Abort the download if progress_cb returns false
bool download_model(const ModelInfo &model,
std::function<bool(int)> progress_cb) {
httplib::Client cli("https://huggingface.co");
cli.set_follow_location(true); // Hugging Face redirects to a CDN
cli.set_read_timeout(std::chrono::hours(1)); // Long timeout for large models
auto url = "/" + model.repo + "/resolve/main/" + model.filename;
auto path = get_models_dir() / model.filename;
auto tmp_path = std::filesystem::path(path).concat(".tmp");
std::ofstream ofs(tmp_path, std::ios::binary);
if (!ofs) { return false; }
auto res = cli.Get(url,
// content_receiver: Receive data chunk by chunk and write to file
[&](const char *data, size_t len) {
ofs.write(data, len);
return ofs.good();
},
// progress: Report download progress (return false to abort)
[&, last_pct = -1](size_t current, size_t total) mutable {
int pct = total ? (int)(current * 100 / total) : 0;
if (pct == last_pct) return true; // Skip if the value hasn't changed
last_pct = pct;
return progress_cb(pct);
});
ofs.close();
if (!res || res->status != 200) {
std::filesystem::remove(tmp_path);
return false;
}
// Rename after download completes
std::filesystem::rename(tmp_path, path);
return true;
}
// -------------------------------------------------------------------------
// Server
// -------------------------------------------------------------------------
int main() {
httplib::Server svr;
// Create the model storage directory
auto models_dir = get_models_dir();
std::filesystem::create_directories(models_dir);
// Auto-download the default model if not already present
std::string selected_model = MODELS[0].filename;
auto path = models_dir / selected_model;
if (!std::filesystem::exists(path)) {
std::cout << "Downloading " << selected_model << "..." << std::endl;
if (!download_model(MODELS[0], [](int pct) {
std::cout << "\r" << pct << "%" << std::flush;
return true;
})) {
std::cerr << "\nFailed to download model." << std::endl;
return 1;
}
std::cout << std::endl;
}
auto llm = llamalib::Llama{path};
std::mutex llm_mutex; // Protect access during model switching
// Set a long timeout since LLM inference takes time (default is 5 seconds)
svr.set_read_timeout(300);
svr.set_write_timeout(300);
svr.set_logger([](const auto &req, const auto &res) {
std::cout << req.method << " " << req.path << " -> " << res.status
<< std::endl;
});
svr.Get("/health", [](const httplib::Request &, httplib::Response &res) {
res.set_content(json{{"status", "ok"}}.dump(), "application/json");
});
// --- Translation endpoint (Chapter 2) ------------------------------------
svr.Post("/translate",
[&](const httplib::Request &req, httplib::Response &res) {
auto input = json::parse(req.body, nullptr, false);
if (input.is_discarded()) {
res.status = 400;
res.set_content(json{{"error", "Invalid JSON"}}.dump(),
"application/json");
return;
}
if (!input.contains("text") || !input["text"].is_string() ||
input["text"].get<std::string>().empty()) {
res.status = 400;
res.set_content(json{{"error", "'text' is required"}}.dump(),
"application/json");
return;
}
auto text = input["text"].get<std::string>();
auto target_lang = input.value("target_lang", "ja");
auto prompt = "Translate the following text to " + target_lang +
". Output only the translation, nothing else.\n\n" + text;
try {
std::lock_guard<std::mutex> lock(llm_mutex);
auto translation = llm.chat(prompt);
res.set_content(json{{"translation", translation}}.dump(),
"application/json");
} catch (const std::exception &e) {
res.status = 500;
res.set_content(json{{"error", e.what()}}.dump(), "application/json");
}
});
// --- SSE streaming translation (Chapter 3) -------------------------------
svr.Post("/translate/stream",
[&](const httplib::Request &req, httplib::Response &res) {
auto input = json::parse(req.body, nullptr, false);
if (input.is_discarded()) {
res.status = 400;
res.set_content(json{{"error", "Invalid JSON"}}.dump(),
"application/json");
return;
}
if (!input.contains("text") || !input["text"].is_string() ||
input["text"].get<std::string>().empty()) {
res.status = 400;
res.set_content(json{{"error", "'text' is required"}}.dump(),
"application/json");
return;
}
auto text = input["text"].get<std::string>();
auto target_lang = input.value("target_lang", "ja");
auto prompt = "Translate the following text to " + target_lang +
". Output only the translation, nothing else.\n\n" + text;
res.set_chunked_content_provider(
"text/event-stream",
[&, prompt](size_t, httplib::DataSink &sink) {
std::lock_guard<std::mutex> lock(llm_mutex);
try {
llm.chat(prompt, [&](std::string_view token) {
sink.os << "data: "
<< json(std::string(token)).dump(
-1, ' ', false, json::error_handler_t::replace)
<< "\n\n";
return sink.os.good(); // Abort inference on disconnect
});
sink.os << "data: [DONE]\n\n";
} catch (const std::exception &e) {
sink.os << "data: " << json({{"error", e.what()}}).dump() << "\n\n";
}
sink.done();
return true;
});
});
// --- Model list (Chapter 4) ----------------------------------------------
svr.Get("/models",
[&](const httplib::Request &, httplib::Response &res) {
auto models_dir = get_models_dir();
auto arr = json::array();
for (const auto &m : MODELS) {
auto path = models_dir / m.filename;
arr.push_back({
{"name", m.name},
{"params", m.params},
{"size", m.size},
{"downloaded", std::filesystem::exists(path)},
{"selected", m.filename == selected_model},
});
}
res.set_content(json{{"models", arr}}.dump(), "application/json");
});
// --- Model selection (Chapter 4) -----------------------------------------
svr.Post("/models/select",
[&](const httplib::Request &req, httplib::Response &res) {
auto input = json::parse(req.body, nullptr, false);
if (input.is_discarded() || !input.contains("model")) {
res.status = 400;
res.set_content(json{{"error", "'model' is required"}}.dump(),
"application/json");
return;
}
auto name = input["model"].get<std::string>();
auto it = std::find_if(MODELS.begin(), MODELS.end(),
[&](const ModelInfo &m) { return m.name == name; });
if (it == MODELS.end()) {
res.status = 404;
res.set_content(json{{"error", "Unknown model"}}.dump(),
"application/json");
return;
}
const auto &model = *it;
// Always respond with SSE (same format whether downloaded or not)
res.set_chunked_content_provider(
"text/event-stream",
[&, model](size_t, httplib::DataSink &sink) {
// SSE event sending helper
auto send = [&](const json &event) {
sink.os << "data: " << event.dump() << "\n\n";
};
// Download if not yet downloaded (report progress via SSE)
auto path = get_models_dir() / model.filename;
if (!std::filesystem::exists(path)) {
bool ok = download_model(model, [&](int pct) {
send({{"status", "downloading"}, {"progress", pct}});
return sink.os.good(); // Abort download on client disconnect
});
if (!ok) {
send({{"status", "error"}, {"message", "Download failed"}});
sink.done();
return true;
}
}
// Load and switch to the model
send({{"status", "loading"}});
{
std::lock_guard<std::mutex> lock(llm_mutex);
llm = llamalib::Llama{path};
selected_model = model.filename;
}
send({{"status", "ready"}});
sink.done();
return true;
});
});
// --- Embedded file serving (Chapter 6) ------------------------------------
// Chapter 5: svr.set_mount_point("/", "./public");
httplib::mount(svr, Web::FS);
// Start the server on a background thread
auto port = svr.bind_to_any_port("127.0.0.1");
std::thread server_thread([&]() { svr.listen_after_bind(); });
std::cout << "Listening on http://127.0.0.1:" << port << std::endl;
// Display the UI with WebView
webview::webview w(false, nullptr);
w.set_title("Translate App");
w.set_size(1024, 768, WEBVIEW_HINT_NONE);
w.navigate("http://127.0.0.1:" + std::to_string(port));
#ifdef __APPLE__
setup_macos_edit_menu();
#endif
w.run(); // Block until the window is closed
// Stop the server when the window is closed
svr.stop();
server_thread.join();
}
```
</details>
To summarize the changes from Chapter 5:
- `#include <csignal>` replaced with `#include <thread>`, `<cpp-embedlib-httplib.h>`, `"WebAssets.h"`, `"webview/webview.h"`
- Removed the `signal_handler` function
- `svr.set_mount_point("/", "./public")` replaced with `httplib::mount(svr, Web::FS)`
- `svr.listen("127.0.0.1", 8080)` replaced with `bind_to_any_port` + `listen_after_bind` + WebView event loop
Not a single line of handler code has changed. The REST API, SSE streaming, and model management built through Chapter 5 all work as-is.
## 6.6 Building and Testing
```bash
cmake -B build
cmake --build build -j
```
Launch the app:
```bash
./build/translate-app
```
No browser is needed. A window opens automatically. The same UI from Chapter 5 appears as-is, and translation and model switching all work just the same.
When you close the window, the server shuts down automatically. There's no need for `Ctrl+C`.
### What Needs to Be Distributed
You only need to distribute:
- The single `translate-app` binary
That's it. You don't need the `public/` directory. HTML, CSS, and JavaScript are embedded in the binary. Model files download automatically on first launch, so there's no need to ask users to prepare anything in advance.
## Next Chapter
Congratulations! 🎉
In Chapter 1, `/health` just returned `{"status":"ok"}`. Now we have a desktop app where you type text and translations stream in real time, pick a different model from a dropdown and it downloads automatically, and closing the window cleanly shuts everything down — all in a single distributable binary.
What we changed in this chapter was just the static file serving and the server startup. Not a single line of handler code changed. The REST API, SSE streaming, and model management we built through Chapter 5 all work as a desktop app, as-is.
In the next chapter, we'll shift perspective and read through the code of llama.cpp's own `llama-server`. Let's compare our simple server with a production-quality one and see what design decisions differ and why.
**Next:** [Reading the llama.cpp Server Source Code](../ch07-code-reading)
@@ -0,0 +1,154 @@
---
title: "7. Reading the llama.cpp Server Source Code"
order: 7
---
Over the course of six chapters, we built a translation desktop app from scratch. We have a working product, but it's ultimately a "learning-oriented" implementation. So how does "production-quality" code differ? Let's read the source code of `llama-server`, the official server bundled with llama.cpp, and compare.
`llama-server` is located at `llama.cpp/tools/server/`. It uses the same cpp-httplib, so you can read the code the same way as in the previous chapters.
## 7.1 Source Code Location
```ascii
llama.cpp/tools/server/
├── server.cpp # Main server implementation
├── httplib.h # cpp-httplib (bundled version)
└── ...
```
The code is contained in a single `server.cpp`. It runs to several thousand lines, but once you understand the structure, you can narrow down the parts worth reading.
## 7.2 OpenAI-Compatible API
The biggest difference between the server we built and `llama-server` is the API design.
**Our API:**
```text
POST /translate → {"translation": "..."}
POST /translate/stream → SSE: data: "token"
```
**llama-server's API:**
```text
POST /v1/chat/completions → OpenAI-compatible JSON
POST /v1/completions → OpenAI-compatible JSON
POST /v1/embeddings → Text embedding vectors
```
`llama-server` conforms to [OpenAI's API specification](https://platform.openai.com/docs/api-reference). This means OpenAI's official client libraries (such as the Python `openai` package) work out of the box.
```python
# Example of connecting to llama-server with the OpenAI client
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8080/v1", api_key="dummy")
response = client.chat.completions.create(
model="local-model",
messages=[{"role": "user", "content": "Hello!"}]
)
```
Compatibility with existing tools and libraries is a big design decision. We designed a simple translation-specific API, but if you're building a general-purpose server, OpenAI compatibility has become the de facto standard.
## 7.3 Concurrent Request Handling
Our server processes requests one at a time. If another request arrives while a translation is in progress, it waits until the previous inference finishes. This is fine for a desktop app used by one person, but it becomes a problem for a server shared by multiple users.
`llama-server` handles concurrent requests through a mechanism called **slots**.
![llama-server's slot management](../slots.svg#half)
The key point is that tokens from each slot are not inferred **one by one in sequence**, but rather **all at once in a single batch**. GPUs excel at parallel processing, so processing two users simultaneously takes almost the same time as processing one. This is called "continuous batching."
In our server, cpp-httplib's thread pool assigns one thread per request, but the inference itself runs single-threaded inside `llm.chat()`. `llama-server` consolidates this inference step into a shared batch processing loop.
## 7.4 Differences in SSE Format
The streaming mechanism itself is the same (`set_chunked_content_provider` + SSE), but the data format differs.
**Our format:**
```text
data: "去年の"
data: "春に"
data: [DONE]
```
**llama-server (OpenAI-compatible):**
```text
data: {"id":"chatcmpl-xxx","object":"chat.completion.chunk","choices":[{"delta":{"content":"去年の"}}]}
data: {"id":"chatcmpl-xxx","object":"chat.completion.chunk","choices":[{"delta":{"content":"春に"}}]}
data: [DONE]
```
Our format simply sends the tokens. Because `llama-server` follows the OpenAI specification, even a single token comes wrapped in JSON. It may look verbose, but it includes useful information for clients, like an `id` to identify the request and a `finish_reason` to indicate why generation stopped.
## 7.5 KV Cache Reuse
In our server, we process the entire prompt from scratch on every request. Our translation app's prompt is short ("Translate the following text to ja..." + input text), so this isn't a problem.
`llama-server` reuses the KV cache for the prefix portion when a request shares a common prompt prefix with a previous request.
![KV cache reuse](../kv-cache.svg#half)
For chatbots that send a long system prompt and few-shot examples with every request, this alone dramatically reduces response time. The difference is night and day: processing several thousand tokens of system prompt every time versus reading them from cache in an instant.
For our translation app, where the system prompt is just a single sentence, the benefit is limited. However, it's an optimization worth keeping in mind when applying this to your own applications.
## 7.6 Structured Output
Since our translation API returns plain text, there was no need to constrain the output format. But what if you want the LLM to respond in JSON?
```text
Prompt: Analyze the sentiment of the following text and return it as JSON.
LLM output (expected): {"sentiment": "positive", "score": 0.8}
LLM output (reality): Here are the results of the sentiment analysis. {"sentiment": ...
```
LLMs sometimes ignore instructions and add extraneous text. `llama-server` solves this problem with **grammar constraints**.
```bash
curl http://localhost:8080/v1/chat/completions \
-d '{
"messages": [{"role": "user", "content": "Analyze sentiment..."}],
"json_schema": {
"type": "object",
"properties": {
"sentiment": {"type": "string", "enum": ["positive", "negative", "neutral"]},
"score": {"type": "number"}
},
"required": ["sentiment", "score"]
}
}'
```
When you specify `json_schema`, tokens that don't conform to the grammar are excluded during token generation. This guarantees that the output is always valid JSON, so there's no need to worry about `json::parse` failing.
When embedding LLMs into applications, whether you can reliably parse the output directly impacts reliability. Grammar constraints are unnecessary for free-text output like translation, but they're essential for use cases where you need to return structured data as an API response.
## 7.7 Summary
Let's organize the differences we've covered.
| Aspect | Our Server | llama-server |
|------|-------------|--------------|
| API design | Translation-specific | OpenAI-compatible |
| Concurrent requests | Sequential processing | Slots + continuous batching |
| SSE format | Tokens only | OpenAI-compatible JSON |
| KV cache | Cleared each time | Prefix reuse |
| Structured output | None | JSON Schema / grammar constraints |
| Code size | ~200 lines | Several thousand lines |
Our code is simple because of the assumption that "one person uses it as a desktop app." If you're building a server for multiple users or one that integrates with the existing ecosystem, `llama-server`'s design serves as a valuable reference.
Conversely, even 200 lines of code is enough to make a fully functional translation app. I hope this code reading exercise has also conveyed the value of "building only what you need."
## Next Chapter
In the next chapter, we'll cover the key points for swapping in your own library and customizing the app to make it truly yours.
**Next:** [Making It Your Own](../ch08-customization)
@@ -0,0 +1,120 @@
---
title: "8. Making It Your Own"
order: 8
---
Through Chapter 7, we've built a translation desktop app and studied how production-quality code differs. In this chapter, let's go over the key points for **turning this app into something entirely your own**.
The translation app was just a vehicle. Replace llama.cpp with your own library, and the same architecture works for any application.
## 8.1 Swapping Out the Build Configuration
First, replace the llama.cpp-related `FetchContent` entries in `CMakeLists.txt` with your own library.
```cmake
# Remove: llama.cpp and cpp-llamalib FetchContent
# Add: your own library
FetchContent_Declare(my_lib
GIT_REPOSITORY https://github.com/yourname/my-lib
GIT_TAG main
)
FetchContent_MakeAvailable(my_lib)
target_link_libraries(my-app PRIVATE
httplib::httplib
nlohmann_json::nlohmann_json
my_lib # Your library instead of cpp-llamalib
# ...
)
```
If your library doesn't support CMake, you can place the header and source files directly in `src/` and add them to `add_executable`. Keep cpp-httplib, nlohmann/json, and webview as they are.
## 8.2 Adapting the API to Your Task
Change the translation API's endpoints and parameters to match your task.
| Translation app | Your app (e.g., image processing) |
|---|---|
| `POST /translate` | `POST /process` |
| `{"text": "...", "target_lang": "ja"}` | `{"image": "base64...", "filter": "blur"}` |
| `POST /translate/stream` | `POST /process/stream` |
| `GET /models` | `GET /filters` or `GET /presets` |
Then update each handler's implementation. For example, just replace the `llm.chat()` calls with your own library's API.
```cpp
// Before: LLM translation
auto translation = llm.chat(prompt);
res.set_content(json{{"translation", translation}}.dump(), "application/json");
// After: e.g., an image processing library
auto result = my_lib::process(input_image, options);
res.set_content(json{{"result", result}}.dump(), "application/json");
```
The same goes for SSE streaming. If your library has a function that reports progress via a callback, you can use the exact same pattern from Chapter 3 to send incremental responses. SSE isn't limited to LLMs — it's useful for any time-consuming task: image processing progress, data conversion steps, long-running computations.
## 8.3 Design Considerations
### Libraries with Expensive Initialization
In this book, we load the LLM model at the top of `main()` and keep it in a variable. This is intentional. Loading the model on every request would take several seconds, so we load it once at startup and reuse it. If your library has expensive initialization (loading large data files, acquiring GPU resources, etc.), the same approach works well.
### Thread Safety
cpp-httplib processes requests concurrently using a thread pool. In Chapter 4 we protected the `llm` object with a `std::mutex` to prevent crashes during model switching. The same pattern applies when integrating your own library. If your library isn't thread-safe or you need to swap objects at runtime, protect access with a `std::mutex`.
## 8.4 Customizing the UI
Edit the three files in `public/`.
- **`index.html`** — Change the input form layout. Swap `<textarea>` for `<input type="file">`, add parameter fields, etc.
- **`style.css`** — Adjust the layout and colors. Keep the two-column design or switch to a single column
- **`script.js`** — Update the `fetch()` target URLs, request bodies, and how responses are displayed
Even without changing any server code, just swapping the HTML makes the app look completely different. Since these are static files, you can iterate quickly — just reload the browser without restarting the server.
This book used plain HTML, CSS, and JavaScript, but combining them with a frontend framework like Vue or React, or a CSS framework, would let you build an even more polished app.
## 8.5 Distribution Considerations
### Licenses
Check the licenses of the libraries you're using. cpp-httplib (MIT), nlohmann/json (MIT), and webview (MIT) all allow commercial use. Don't forget to check the license of your own library and its dependencies too.
### Models and Data Files
The download mechanism we built in Chapter 4 isn't limited to LLM models. If your app needs large data files, the same pattern lets you auto-download them on first launch, keeping the binary small while sparing users the manual setup.
If the data is small, you can embed it directly into the binary with cpp-embedlib.
### Cross-Platform Builds
webview supports macOS, Linux, and Windows. When building for each platform:
- **macOS** — No additional dependencies
- **Linux** — Requires `libwebkit2gtk-4.1-dev`
- **Windows** — Requires the WebView2 runtime (pre-installed on Windows 11)
Consider setting up cross-platform builds in CI (e.g., GitHub Actions) too.
## Closing
Thank you so much for reading to the end. 🙏
This book started with `/health` returning `{"status":"ok"}` in Chapter 1. From there we built a REST API, added SSE streaming, downloaded models from Hugging Face, created a browser-based Web UI, and packaged it all into a single-binary desktop app. In Chapter 7 we read through `llama-server`'s code and learned how production-quality servers differ in their design. It's been quite a journey, and I'm truly grateful you stuck with it all the way through.
Looking back, we used several key cpp-httplib features hands-on:
- **Server**: routing, JSON responses, SSE streaming with `set_chunked_content_provider`, static file serving with `set_mount_point`
- **Client**: HTTPS connections, redirect following, large downloads with content receivers, progress callbacks
- **WebView integration**: `bind_to_any_port` + `listen_after_bind` for background threading
cpp-httplib offers many more features beyond what we covered here, including multipart file uploads, authentication, timeout control, compression, and range requests. See [A Tour of cpp-httplib](../../tour/) for details.
These patterns aren't limited to a translation app. If you want to add a web API to your C++ library, give it a browser UI, or ship it as an easy-to-distribute desktop app — I hope this book serves as a useful reference.
Take your own library, build your own app, and have fun with it. Happy hacking! 🚀
+26
View File
@@ -0,0 +1,26 @@
---
title: "Building a Desktop LLM App with cpp-httplib"
order: 0
---
Have you ever wanted to add a web API to your own C++ library, or quickly build an Electron-like desktop app? In Rust you might reach for "Tauri + axum," but in C++ it always seemed out of reach.
With [cpp-httplib](https://github.com/yhirose/cpp-httplib), [webview/webview](https://github.com/webview/webview), and [cpp-embedlib](https://github.com/yhirose/cpp-embedlib), you can take the same approach in pure C++ — and produce a small, easy-to-distribute single binary.
In this tutorial we build an LLM-powered translation app using [llama.cpp](https://github.com/ggml-org/llama.cpp), progressing step by step from "REST API" to "SSE streaming" to "Web UI" to "desktop app." Translation is just the vehicle — replace llama.cpp with your own library and the same architecture works for any application.
![Desktop App](app.png#large-center)
If you know basic C++17 and understand the basics of HTTP / REST APIs, you're ready to start.
## Chapters
1. **[Set up the project](ch01-setup)** — Fetch dependencies, configure the build, write scaffold code
2. **[Embed llama.cpp and create a REST API](ch02-rest-api)** — Return translation results as JSON
3. **[Add token streaming with SSE](ch03-sse-streaming)** — Stream responses token by token
4. **[Add model discovery and management](ch04-model-management)** — Download and switch models from Hugging Face
5. **[Add a Web UI](ch05-web-ui)** — A browser-based translation interface
6. **[Turn it into a desktop app with WebView](ch06-desktop-app)** — A single-binary desktop application
7. **[Reading the llama.cpp server source code](ch07-code-reading)** — Compare with production-quality code
8. **[Making it your own](ch08-customization)** — Swap in your own library and customize
+36
View File
@@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 426 160" font-family="system-ui, sans-serif" font-size="14">
<rect x="0" y="0" width="426" height="160" rx="8" fill="#f5f3ef"/>
<defs>
<marker id="arrowhead" markerWidth="7" markerHeight="5" refX="7" refY="2.5" orient="auto">
<polygon points="0,0 7,2.5 0,5" fill="#198754"/>
</marker>
</defs>
<!-- request 1 (y=16) -->
<text x="94" y="35" fill="#333" font-weight="bold" text-anchor="end">Request 1:</text>
<rect x="106" y="16" width="138" height="30" rx="4" fill="#d1e7dd" stroke="#198754" stroke-width="1"/>
<text x="175" y="36" fill="#333" text-anchor="middle">System prompt</text>
<text x="256" y="36" fill="#333" text-anchor="middle">+</text>
<rect x="270" y="16" width="138" height="30" rx="4" fill="#cfe2ff" stroke="#0d6efd" stroke-width="1"/>
<text x="339" y="36" fill="#333" text-anchor="middle">User question A</text>
<!-- annotation: cache save -->
<text x="175" y="64" fill="#198754" font-size="11" text-anchor="middle">Saved to KV cache</text>
<!-- arrow -->
<line x1="175" y1="70" x2="175" y2="90" stroke="#198754" stroke-width="1.2" marker-end="url(#arrowhead)"/>
<text x="188" y="85" fill="#198754" font-size="11">Reuse</text>
<!-- request 2 (y=96) -->
<text x="94" y="115" fill="#333" font-weight="bold" text-anchor="end">Request 2:</text>
<rect x="106" y="96" width="138" height="30" rx="4" fill="#d1e7dd" stroke="#198754" stroke-width="1" stroke-dasharray="6,3"/>
<text x="175" y="116" fill="#333" text-anchor="middle">System prompt</text>
<text x="256" y="116" fill="#333" text-anchor="middle">+</text>
<rect x="270" y="96" width="138" height="30" rx="4" fill="#cfe2ff" stroke="#0d6efd" stroke-width="1"/>
<text x="339" y="116" fill="#333" text-anchor="middle">User question B</text>
<!-- bottom labels -->
<text x="175" y="144" fill="#198754" font-size="11" text-anchor="middle">No recomputation</text>
<text x="339" y="144" fill="#0d6efd" font-size="11" text-anchor="middle">Only this is computed</text>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+24
View File
@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 440 240" font-family="system-ui, sans-serif" font-size="14">
<!-- outer box -->
<rect x="0" y="0" width="440" height="240" rx="8" fill="#f5f3ef"/>
<text x="20" y="28" font-weight="bold" font-size="16" fill="#333">llama-server</text>
<!-- slot 0 -->
<rect x="20" y="46" width="400" height="32" rx="4" fill="#d1e7dd" stroke="#198754" stroke-width="1"/>
<text x="32" y="67" fill="#333">Slot 0: User A's request</text>
<!-- slot 1 -->
<rect x="20" y="86" width="400" height="32" rx="4" fill="#d1e7dd" stroke="#198754" stroke-width="1"/>
<text x="32" y="107" fill="#333">Slot 1: User B's request</text>
<!-- slot 2 -->
<rect x="20" y="126" width="400" height="32" rx="4" fill="#e9ecef" stroke="#adb5bd" stroke-width="1"/>
<text x="32" y="147" fill="#999">Slot 2: (idle)</text>
<!-- slot 3 -->
<rect x="20" y="166" width="400" height="32" rx="4" fill="#e9ecef" stroke="#adb5bd" stroke-width="1"/>
<text x="32" y="187" fill="#999">Slot 3: (idle)</text>
<!-- arrow + label -->
<text x="20" y="224" fill="#333" font-size="13">→ Active slots are inferred together in a single batch</text>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Some files were not shown because too many files have changed in this diff Show More