Compare commits

...

50 Commits

Author SHA1 Message Date
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
18 changed files with 1046 additions and 514 deletions

69
.github/workflows/abidiff.yaml vendored Normal file
View File

@ -0,0 +1,69 @@
# 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
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

View File

@ -1,5 +1,11 @@
name: CIFuzz name: CIFuzz
on: [pull_request] on: [pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: true
jobs: jobs:
Fuzzing: Fuzzing:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -1,10 +1,52 @@
name: test 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: jobs:
style-check:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
continue-on-error: true
steps:
- name: checkout
uses: actions/checkout@v4
- name: run style check
run: |
clang-format --version
cd test && make style_check
ubuntu: ubuntu:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: >
(github.event_name == 'push') ||
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -17,6 +59,11 @@ jobs:
macos: macos:
runs-on: macos-latest 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')
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -27,6 +74,19 @@ jobs:
windows: windows:
runs-on: windows-latest 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
name: without SSL
- with_ssl: true
name: with SSL
name: windows ${{ matrix.config.name }}
steps: steps:
- name: Prepare Git for Checkout on Windows - name: Prepare Git for Checkout on Windows
run: | run: |
@ -42,24 +102,25 @@ jobs:
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
- name: Setup msbuild on windows - name: Setup msbuild on windows
uses: microsoft/setup-msbuild@v2 uses: microsoft/setup-msbuild@v2
- name: Install libraries - name: Install vcpkg dependencies
run: | run: vcpkg install gtest curl zlib brotli
vcpkg install gtest curl zlib brotli - name: Install OpenSSL
choco install openssl if: ${{ matrix.config.with_ssl }}
run: choco install openssl
- name: Configure CMake with SSL - 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_REQUIRE_OPENSSL=ON -DHTTPLIB_REQUIRE_ZLIB=ON -DHTTPLIB_REQUIRE_BROTLI=ON run: >
- name: Build with with SSL cmake -B build -S .
run: cmake --build build --config Release -DCMAKE_BUILD_TYPE=Release
- name: Run tests with SSL -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake
-DHTTPLIB_TEST=ON
-DHTTPLIB_REQUIRE_ZLIB=ON
-DHTTPLIB_REQUIRE_BROTLI=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 }}
run: ctest --output-on-failure --test-dir build -C Release run: ctest --output-on-failure --test-dir build -C Release
- name: Configure CMake without SSL
run: cmake -B build-no-ssl -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake -DHTTPLIB_TEST=ON -DHTTPLIB_REQUIRE_OPENSSL=ON -DHTTPLIB_REQUIRE_ZLIB=ON -DHTTPLIB_REQUIRE_BROTLI=ON
- name: Build without SSL
run: cmake --build build-no-ssl --config Release
- name: Run tests without SSL
run: ctest --output-on-failure --test-dir build-no-ssl -C Release
env: env:
VCPKG_ROOT: "C:/vcpkg" VCPKG_ROOT: "C:/vcpkg"
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"

1
.gitignore vendored
View File

@ -23,6 +23,7 @@ test/test.xcodeproj/*/xcuser*
test/*.o test/*.o
test/*.pem test/*.pem
test/*.srl test/*.srl
test/_build_*
work/ work/
benchmark/server* benchmark/server*

View File

@ -2,7 +2,7 @@ FROM yhirose4dockerhub/ubuntu-builder AS builder
WORKDIR /build WORKDIR /build
COPY httplib.h . COPY httplib.h .
COPY docker/main.cc . COPY docker/main.cc .
RUN g++ -std=c++23 -static -o server -O2 -I. -DCPPHTTPLIB_USE_POLL main.cc && strip server RUN g++ -std=c++23 -static -o server -O2 -I. main.cc && strip server
FROM scratch FROM scratch
COPY --from=builder /build/server /server COPY --from=builder /build/server /server

View File

@ -39,10 +39,10 @@ svr.listen("0.0.0.0", 8080);
#include "path/to/httplib.h" #include "path/to/httplib.h"
// HTTP // HTTP
httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co"); httplib::Client cli("http://yhirose.github.io");
// HTTPS // HTTPS
httplib::Client cli("https://cpp-httplib-server.yhirose.repl.co"); httplib::Client cli("https://yhirose.github.io");
auto res = cli.Get("/hi"); auto res = cli.Get("/hi");
res->status; res->status;
@ -406,11 +406,11 @@ svr.Get("/chunked", [&](const Request& req, Response& res) {
```cpp ```cpp
svr.Get("/content", [&](const Request &req, Response &res) { svr.Get("/content", [&](const Request &req, Response &res) {
res.set_file_content("./path/to/conent.html"); res.set_file_content("./path/to/content.html");
}); });
svr.Get("/content", [&](const Request &req, Response &res) { svr.Get("/content", [&](const Request &req, Response &res) {
res.set_file_content("./path/to/conent", "text/html"); res.set_file_content("./path/to/content", "text/html");
}); });
``` ```
@ -462,7 +462,7 @@ Please see [Server example](https://github.com/yhirose/cpp-httplib/blob/master/e
### Default thread pool support ### Default thread pool support
`ThreadPool` is used as a **default** task queue, and the default thread count is 8, or `std::thread::hardware_concurrency()`. You can change it with `CPPHTTPLIB_THREAD_POOL_COUNT`. `ThreadPool` is used as the **default** task queue, with a default thread count of 8 or `std::thread::hardware_concurrency() - 1`, whichever is greater. You can change it with `CPPHTTPLIB_THREAD_POOL_COUNT`.
If you want to set the thread count at runtime, there is no convenient way... But here is how. If you want to set the thread count at runtime, there is no convenient way... But here is how.
@ -654,6 +654,9 @@ res = cli.Options("/resource/foo");
cli.set_connection_timeout(0, 300000); // 300 milliseconds cli.set_connection_timeout(0, 300000); // 300 milliseconds
cli.set_read_timeout(5, 0); // 5 seconds cli.set_read_timeout(5, 0); // 5 seconds
cli.set_write_timeout(5, 0); // 5 seconds cli.set_write_timeout(5, 0); // 5 seconds
// This method works the same as curl's `--max-timeout` option
svr.set_max_timeout(5000); // 5 seconds
``` ```
### Receive content with a content receiver ### Receive content with a content receiver
@ -840,7 +843,7 @@ Please see https://github.com/google/brotli for more detail.
### Default `Accept-Encoding` value ### Default `Accept-Encoding` value
The default `Acdcept-Encoding` value contains all possible compression types. So, the following two examples are same. The default `Accept-Encoding` value contains all possible compression types. So, the following two examples are same.
```c++ ```c++
res = cli.Get("/resource/foo"); res = cli.Get("/resource/foo");
@ -869,11 +872,6 @@ res->body; // Compressed data
``` ```
Use `poll` instead of `select`
------------------------------
`select` system call is used as default since it's more widely supported. If you want to let cpp-httplib use `poll` instead, you can do so with `CPPHTTPLIB_USE_POLL`.
Unix Domain Socket Support Unix Domain Socket Support
-------------------------- --------------------------
@ -881,7 +879,7 @@ Unix Domain Socket support is available on Linux and macOS.
```c++ ```c++
// Server // Server
httplib::Server svr("./my-socket.sock"); httplib::Server svr;
svr.set_address_family(AF_UNIX).listen("./my-socket.sock", 80); svr.set_address_family(AF_UNIX).listen("./my-socket.sock", 80);
// Client // Client
@ -931,9 +929,6 @@ From Docker Hub
```bash ```bash
> docker run --rm -it -p 8080:80 -v ./docker/html:/html yhirose4dockerhub/cpp-httplib-server > docker run --rm -it -p 8080:80 -v ./docker/html:/html yhirose4dockerhub/cpp-httplib-server
...
> docker run --init --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server
Serving HTTP on 0.0.0.0 port 80 ... Serving HTTP on 0.0.0.0 port 80 ...
192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1" 192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"
192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..." 192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..."
@ -966,12 +961,12 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
> cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance. > cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance.
> [!NOTE] > [!NOTE]
> Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin and MSYS2 including MinGW are neither supported nor tested. > Windows 8 or lower, Visual Studio 2015 or lower, and Cygwin and MSYS2 including MinGW are neither supported nor tested.
License License
------- -------
MIT license (© 2024 Yuji Hirose) MIT license (© 2025 Yuji Hirose)
Special Thanks To Special Thanks To
----------------- -----------------

View File

@ -1,7 +1,7 @@
// //
// httplib.h // httplib.h
// //
// Copyright (c) 2024 Yuji Hirose. All rights reserved. // Copyright (c) 2025 Yuji Hirose. All rights reserved.
// MIT License // MIT License
// //

View File

@ -1,7 +1,7 @@
// //
// main.cc // main.cc
// //
// Copyright (c) 2024 Yuji Hirose. All rights reserved. // Copyright (c) 2025 Yuji Hirose. All rights reserved.
// MIT License // MIT License
// //
@ -41,7 +41,7 @@ std::string log(auto &req, auto &res) {
auto http_referer = "-"; // TODO: auto http_referer = "-"; // TODO:
auto http_user_agent = req.get_header_value("User-Agent", "-"); auto http_user_agent = req.get_header_value("User-Agent", "-");
// NOTE: From NGINX defualt access log format // NOTE: From NGINX default access log format
// log_format combined '$remote_addr - $remote_user [$time_local] ' // log_format combined '$remote_addr - $remote_user [$time_local] '
// '"$request" $status $body_bytes_sent ' // '"$request" $status $body_bytes_sent '
// '"$http_referer" "$http_user_agent"'; // '"$http_referer" "$http_user_agent"';

View File

@ -1,7 +1,7 @@
// //
// server_and_client.cc // server_and_client.cc
// //
// Copyright (c) 2024 Yuji Hirose. All rights reserved. // Copyright (c) 2025 Yuji Hirose. All rights reserved.
// MIT License // MIT License
// //

View File

@ -12,8 +12,7 @@ using namespace std;
class EventDispatcher { class EventDispatcher {
public: public:
EventDispatcher() { EventDispatcher() {}
}
void wait_event(DataSink *sink) { void wait_event(DataSink *sink) {
unique_lock<mutex> lk(m_); unique_lock<mutex> lk(m_);

922
httplib.h

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,7 @@ endif()
find_package(CURL REQUIRED) find_package(CURL REQUIRED)
add_executable(httplib-test test.cc) add_executable(httplib-test test.cc include_httplib.cc $<$<BOOL:${WIN32}>:include_windows_h.cc>)
target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8;/bigobj>") target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8;/bigobj>")
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main CURL::libcurl) target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main CURL::libcurl)
gtest_discover_tests(httplib-test) gtest_discover_tests(httplib-test)

View File

@ -28,6 +28,11 @@ TEST_ARGS = gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUP
# OSS-Fuzz will define its own value for LIB_FUZZING_ENGINE. # OSS-Fuzz will define its own value for LIB_FUZZING_ENGINE.
LIB_FUZZING_ENGINE ?= standalone_fuzz_target_runner.o LIB_FUZZING_ENGINE ?= standalone_fuzz_target_runner.o
CLANG_FORMAT = clang-format
REALPATH = $(shell which grealpath 2>/dev/null || which realpath 2>/dev/null)
STYLE_CHECK_FILES = $(filter-out httplib.h httplib.cc, \
$(wildcard example/*.h example/*.cc fuzzing/*.h fuzzing/*.cc *.h *.cc ../httplib.h))
all : test test_split all : test test_split
./test ./test
@ -42,6 +47,31 @@ test : test.cc include_httplib.cc ../httplib.h Makefile cert.pem
test_split : test.cc ../httplib.h httplib.cc Makefile cert.pem test_split : test.cc ../httplib.h httplib.cc Makefile cert.pem
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS) $(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS)
check_abi:
@./check-shared-library-abi-compatibility.sh
.PHONY: style_check
style_check: $(STYLE_CHECK_FILES)
@for file in $(STYLE_CHECK_FILES); do \
$(CLANG_FORMAT) $$file > $$file.formatted; \
if ! diff -u $$file $$file.formatted; then \
file2=$$($(REALPATH) --relative-to=.. $$file); \
printf "\n%*s\n" 80 | tr ' ' '#'; \
printf "##%*s##\n" 76; \
printf "## %-70s ##\n" "$$file2 not properly formatted. Please run clang-format."; \
printf "##%*s##\n" 76; \
printf "%*s\n\n" 80 | tr ' ' '#'; \
failed=1; \
fi; \
rm -f $$file.formatted; \
done; \
if [ -n "$$failed" ]; then \
echo "Style check failed for one or more files. See above for details."; \
false; \
else \
echo "All files are properly formatted."; \
fi
test_proxy : test_proxy.cc ../httplib.h Makefile cert.pem test_proxy : test_proxy.cc ../httplib.h Makefile cert.pem
$(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS) $(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS)
@ -66,5 +96,5 @@ cert.pem:
./gen-certs.sh ./gen-certs.sh
clean: clean:
rm -f test test_split test_proxy server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc rm -rf test test_split test_proxy server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc _build* *.dSYM

View File

@ -25,7 +25,9 @@ public:
bool is_readable() const override { return true; } bool is_readable() const override { return true; }
bool is_writable() const override { return true; } bool wait_readable() const override { return true; }
bool wait_writable() const override { return true; }
void get_remote_ip_and_port(std::string &ip, int &port) const override { void get_remote_ip_and_port(std::string &ip, int &port) const override {
ip = "127.0.0.1"; ip = "127.0.0.1";
@ -39,6 +41,8 @@ public:
socket_t socket() const override { return 0; } socket_t socket() const override { return 0; }
time_t duration() const override { return 0; };
private: private:
const uint8_t *data_; const uint8_t *data_;
size_t size_; size_t size_;

View File

@ -0,0 +1,6 @@
// Test if including windows.h conflicts with httplib.h
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <httplib.h>

28
test/make-shared-library.sh Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env bash
if [ "$#" -ne 1 ]; then
echo "Usage: $0 build_dir"
exit 1
fi
BUILD_DIR=$1
# Make the build directory
rm -rf $BUILD_DIR
mkdir -p $BUILD_DIR/out
cd $BUILD_DIR
# Build the version
git checkout $BUILD_DIR -q
cmake \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_FLAGS="-g -Og" \
-DBUILD_SHARED_LIBS=ON \
-DHTTPLIB_COMPILE=ON \
-DCMAKE_INSTALL_PREFIX=./out \
../..
cmake --build . --target install
cmake --build . --target clean

View File

@ -156,27 +156,28 @@ TEST_F(UnixSocketTest, abstract) {
} }
#endif #endif
TEST(SocketStream, is_writable_UNIX) { TEST(SocketStream, wait_writable_UNIX) {
int fds[2]; int fds[2];
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds)); ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
const auto asSocketStream = [&](socket_t fd, const auto asSocketStream = [&](socket_t fd,
std::function<bool(Stream &)> func) { std::function<bool(Stream &)> func) {
return detail::process_client_socket(fd, 0, 0, 0, 0, func); return detail::process_client_socket(
fd, 0, 0, 0, 0, 0, std::chrono::steady_clock::time_point::min(), func);
}; };
asSocketStream(fds[0], [&](Stream &s0) { asSocketStream(fds[0], [&](Stream &s0) {
EXPECT_EQ(s0.socket(), fds[0]); EXPECT_EQ(s0.socket(), fds[0]);
EXPECT_TRUE(s0.is_writable()); EXPECT_TRUE(s0.wait_writable());
EXPECT_EQ(0, close(fds[1])); EXPECT_EQ(0, close(fds[1]));
EXPECT_FALSE(s0.is_writable()); EXPECT_FALSE(s0.wait_writable());
return true; return true;
}); });
EXPECT_EQ(0, close(fds[0])); EXPECT_EQ(0, close(fds[0]));
} }
TEST(SocketStream, is_writable_INET) { TEST(SocketStream, wait_writable_INET) {
sockaddr_in addr; sockaddr_in addr;
memset(&addr, 0, sizeof(addr)); memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; addr.sin_family = AF_INET;
@ -206,11 +207,12 @@ TEST(SocketStream, is_writable_INET) {
const auto asSocketStream = [&](socket_t fd, const auto asSocketStream = [&](socket_t fd,
std::function<bool(Stream &)> func) { std::function<bool(Stream &)> func) {
return detail::process_client_socket(fd, 0, 0, 0, 0, func); return detail::process_client_socket(
fd, 0, 0, 0, 0, 0, std::chrono::steady_clock::time_point::min(), func);
}; };
asSocketStream(disconnected_svr_sock, [&](Stream &ss) { asSocketStream(disconnected_svr_sock, [&](Stream &ss) {
EXPECT_EQ(ss.socket(), disconnected_svr_sock); EXPECT_EQ(ss.socket(), disconnected_svr_sock);
EXPECT_FALSE(ss.is_writable()); EXPECT_FALSE(ss.wait_writable());
return true; return true;
}); });
@ -1863,6 +1865,32 @@ TEST(PathUrlEncodeTest, PathUrlEncode) {
} }
} }
TEST(PathUrlEncodeTest, IncludePercentEncodingLF) {
Server svr;
svr.Get("/", [](const Request &req, Response &) {
EXPECT_EQ("\x0A", req.get_param_value("something"));
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
{
Client cli(HOST, PORT);
cli.set_url_encode(false);
auto res = cli.Get("/?something=%0A");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
}
}
TEST(BindServerTest, DISABLED_BindDualStack) { TEST(BindServerTest, DISABLED_BindDualStack) {
Server svr; Server svr;
@ -2951,6 +2979,11 @@ protected:
res.status = 401; res.status = 401;
res.set_header("WWW-Authenticate", "Basic realm=123456"); res.set_header("WWW-Authenticate", "Basic realm=123456");
}) })
.Delete("/issue609",
[](const httplib::Request &, httplib::Response &res,
const httplib::ContentReader &) {
res.set_content("ok", "text/plain");
})
#if defined(CPPHTTPLIB_ZLIB_SUPPORT) || defined(CPPHTTPLIB_BROTLI_SUPPORT) #if defined(CPPHTTPLIB_ZLIB_SUPPORT) || defined(CPPHTTPLIB_BROTLI_SUPPORT)
.Get("/compress", .Get("/compress",
[&](const Request & /*req*/, Response &res) { [&](const Request & /*req*/, Response &res) {
@ -3508,7 +3541,7 @@ TEST_F(ServerTest, LongRequest) {
TEST_F(ServerTest, TooLongRequest) { TEST_F(ServerTest, TooLongRequest) {
std::string request; std::string request;
for (size_t i = 0; i < 545; i++) { for (size_t i = 0; i < 546; i++) {
request += "/TooLongRequest"; request += "/TooLongRequest";
} }
request += "_NG"; request += "_NG";
@ -3519,6 +3552,19 @@ TEST_F(ServerTest, TooLongRequest) {
EXPECT_EQ(StatusCode::UriTooLong_414, res->status); EXPECT_EQ(StatusCode::UriTooLong_414, res->status);
} }
TEST_F(ServerTest, AlmostTooLongRequest) {
// test for #2046 - URI length check shouldn't include other content on req
// line URI is max URI length, minus 14 other chars in req line (GET, space,
// leading /, space, HTTP/1.1)
std::string request =
"/" + string(CPPHTTPLIB_REQUEST_URI_MAX_LENGTH - 14, 'A');
auto res = cli_.Get(request.c_str());
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::NotFound_404, res->status);
}
TEST_F(ServerTest, LongHeader) { TEST_F(ServerTest, LongHeader) {
Request req; Request req;
req.method = "GET"; req.method = "GET";
@ -4022,6 +4068,13 @@ TEST_F(ServerTest, Issue1772) {
EXPECT_EQ(StatusCode::Unauthorized_401, res->status); EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
} }
TEST_F(ServerTest, Issue609) {
auto res = cli_.Delete("/issue609");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ(std::string("ok"), res->body);
}
TEST_F(ServerTest, GetStreamedChunked) { TEST_F(ServerTest, GetStreamedChunked) {
auto res = cli_.Get("/streamed-chunked"); auto res = cli_.Get("/streamed-chunked");
ASSERT_TRUE(res); ASSERT_TRUE(res);
@ -4866,7 +4919,8 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
if (client_sock == INVALID_SOCKET) { return false; } if (client_sock == INVALID_SOCKET) { return false; }
auto ret = detail::process_client_socket( auto ret = detail::process_client_socket(
client_sock, read_timeout_sec, 0, 0, 0, [&](Stream &strm) { client_sock, read_timeout_sec, 0, 0, 0, 0,
std::chrono::steady_clock::time_point::min(), [&](Stream &strm) {
if (req.size() != if (req.size() !=
static_cast<size_t>(strm.write(req.data(), req.size()))) { static_cast<size_t>(strm.write(req.data(), req.size()))) {
return false; return false;
@ -4910,8 +4964,10 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
"Connection: close\r\n" "Connection: close\r\n"
"\r\n"; "\r\n";
ASSERT_TRUE(send_request(5, req)); std::string res;
EXPECT_EQ(header_value, "\v bar \x1B"); ASSERT_TRUE(send_request(5, req, &res));
EXPECT_EQ(header_value, "");
EXPECT_EQ("HTTP/1.1 400 Bad Request", res.substr(0, 24));
} }
// Sends a raw request and verifies that there isn't a crash or exception. // Sends a raw request and verifies that there isn't a crash or exception.
@ -5069,6 +5125,14 @@ TEST(ServerRequestParsingTest, InvalidFieldValueContains_CR_LF_NUL) {
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24)); EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
} }
TEST(ServerRequestParsingTest, InvalidFieldValueContains_LF) {
std::string out;
std::string request(
"GET /header_field_value_check HTTP/1.1\r\nTest: [\n\n\n]\r\n\r\n", 55);
test_raw_request(request, &out);
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
}
TEST(ServerRequestParsingTest, EmptyFieldValue) { TEST(ServerRequestParsingTest, EmptyFieldValue) {
std::string out; std::string out;
@ -6153,7 +6217,7 @@ TEST(SSLClientTest, WildcardHostNameMatch_Online) {
ASSERT_EQ(StatusCode::OK_200, res->status); ASSERT_EQ(StatusCode::OK_200, res->status);
} }
TEST(SSLClientTest, Issue2004) { TEST(SSLClientTest, Issue2004_Online) {
Client client("https://google.com"); Client client("https://google.com");
client.set_follow_location(true); client.set_follow_location(true);
@ -7958,7 +8022,7 @@ TEST(InvalidHeaderCharsTest, is_field_value) {
EXPECT_FALSE(detail::fields::is_field_value(" example_token")); EXPECT_FALSE(detail::fields::is_field_value(" example_token"));
EXPECT_FALSE(detail::fields::is_field_value("example_token ")); EXPECT_FALSE(detail::fields::is_field_value("example_token "));
EXPECT_TRUE(detail::fields::is_field_value("token@123")); EXPECT_TRUE(detail::fields::is_field_value("token@123"));
EXPECT_FALSE(detail::fields::is_field_value("")); EXPECT_TRUE(detail::fields::is_field_value(""));
EXPECT_FALSE(detail::fields::is_field_value("example\rtoken")); EXPECT_FALSE(detail::fields::is_field_value("example\rtoken"));
EXPECT_FALSE(detail::fields::is_field_value("example\ntoken")); EXPECT_FALSE(detail::fields::is_field_value("example\ntoken"));
EXPECT_FALSE(detail::fields::is_field_value(std::string("\0", 1))); EXPECT_FALSE(detail::fields::is_field_value(std::string("\0", 1)));
@ -8142,3 +8206,231 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
} }
} }
#endif #endif
template <typename S, typename C>
inline void max_timeout_test(S &svr, C &cli, time_t timeout, time_t threshold) {
svr.Get("/stream", [&](const Request &, Response &res) {
auto data = new std::string("01234567890123456789");
res.set_content_provider(
data->size(), "text/plain",
[&, data](size_t offset, size_t length, DataSink &sink) {
const size_t DATA_CHUNK_SIZE = 4;
const auto &d = *data;
std::this_thread::sleep_for(std::chrono::seconds(1));
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
return true;
},
[data](bool success) {
EXPECT_FALSE(success);
delete data;
});
});
svr.Get("/stream_without_length", [&](const Request &, Response &res) {
auto i = new size_t(0);
res.set_content_provider(
"text/plain",
[i](size_t, DataSink &sink) {
if (*i < 5) {
std::this_thread::sleep_for(std::chrono::seconds(1));
sink.write("abcd", 4);
(*i)++;
} else {
sink.done();
}
return true;
},
[i](bool success) {
EXPECT_FALSE(success);
delete i;
});
});
svr.Get("/chunked", [&](const Request &, Response &res) {
auto i = new size_t(0);
res.set_chunked_content_provider(
"text/plain",
[i](size_t, DataSink &sink) {
if (*i < 5) {
std::this_thread::sleep_for(std::chrono::seconds(1));
sink.os << "abcd";
(*i)++;
} else {
sink.done();
}
return true;
},
[i](bool success) {
EXPECT_FALSE(success);
delete i;
});
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
listen_thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
cli.set_max_timeout(std::chrono::milliseconds(timeout));
{
auto start = std::chrono::steady_clock::now();
auto res = cli.Get("/stream");
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start)
.count();
ASSERT_FALSE(res);
EXPECT_EQ(Error::Read, res.error());
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold)
<< "Timeout exceeded by " << (elapsed - timeout) << "ms";
}
{
auto start = std::chrono::steady_clock::now();
auto res = cli.Get("/stream_without_length");
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start)
.count();
ASSERT_FALSE(res);
EXPECT_EQ(Error::Read, res.error());
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold)
<< "Timeout exceeded by " << (elapsed - timeout) << "ms";
}
{
auto start = std::chrono::steady_clock::now();
auto res = cli.Get("/chunked", [&](const char *data, size_t data_length) {
EXPECT_EQ("abcd", string(data, data_length));
return true;
});
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start)
.count();
ASSERT_FALSE(res);
EXPECT_EQ(Error::Read, res.error());
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold)
<< "Timeout exceeded by " << (elapsed - timeout) << "ms";
}
}
TEST(MaxTimeoutTest, ContentStream) {
time_t timeout = 2000;
time_t threshold = 200;
Server svr;
Client cli("localhost", PORT);
max_timeout_test(svr, cli, timeout, threshold);
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(MaxTimeoutTest, ContentStreamSSL) {
time_t timeout = 2000;
time_t threshold = 500; // SSL_shutdown is slow on some operating systems.
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
SSLClient cli("localhost", PORT);
cli.enable_server_certificate_verification(false);
max_timeout_test(svr, cli, timeout, threshold);
}
#endif
class EventDispatcher {
public:
EventDispatcher() {}
void wait_event(DataSink *sink) {
unique_lock<mutex> lk(m_);
int id = id_;
cv_.wait(lk, [&] { return cid_ == id; });
sink->write(message_.data(), message_.size());
}
void send_event(const string &message) {
lock_guard<mutex> lk(m_);
cid_ = id_++;
message_ = message;
cv_.notify_all();
}
private:
mutex m_;
condition_variable cv_;
atomic_int id_{0};
atomic_int cid_{-1};
string message_;
};
TEST(ClientInThreadTest, Issue2068) {
EventDispatcher ed;
Server svr;
svr.Get("/event1", [&](const Request & /*req*/, Response &res) {
res.set_chunked_content_provider("text/event-stream",
[&](size_t /*offset*/, DataSink &sink) {
ed.wait_event(&sink);
return true;
});
});
auto listen_thread = std::thread([&svr]() { svr.listen(HOST, PORT); });
svr.wait_until_ready();
thread event_thread([&] {
int id = 0;
while (svr.is_running()) {
this_thread::sleep_for(chrono::milliseconds(500));
std::stringstream ss;
ss << "data: " << id << "\n\n";
ed.send_event(ss.str());
id++;
}
});
auto se = detail::scope_exit([&] {
svr.stop();
listen_thread.join();
event_thread.join();
ASSERT_FALSE(svr.is_running());
});
{
auto client = detail::make_unique<Client>(HOST, PORT);
client->set_read_timeout(std::chrono::minutes(10));
std::atomic<bool> stop{false};
std::thread t([&] {
client->Get("/event1",
[&](const char *, size_t) -> bool { return !stop; });
});
std::this_thread::sleep_for(std::chrono::seconds(2));
stop = true;
client->stop();
client.reset();
t.join();
}
}

View File

@ -5,8 +5,7 @@
using namespace std; using namespace std;
using namespace httplib; using namespace httplib;
template <typename T> template <typename T> void ProxyTest(T &cli, bool basic) {
void ProxyTest(T& cli, bool basic) {
cli.set_proxy("localhost", basic ? 3128 : 3129); cli.set_proxy("localhost", basic ? 3128 : 3129);
auto res = cli.Get("/httpbin/get"); auto res = cli.Get("/httpbin/get");
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
@ -38,7 +37,7 @@ TEST(ProxyTest, SSLDigest) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
template <typename T> template <typename T>
void RedirectProxyText(T& cli, const char *path, bool basic) { void RedirectProxyText(T &cli, const char *path, bool basic) {
cli.set_proxy("localhost", basic ? 3128 : 3129); cli.set_proxy("localhost", basic ? 3128 : 3129);
if (basic) { if (basic) {
cli.set_proxy_basic_auth("hello", "world"); cli.set_proxy_basic_auth("hello", "world");
@ -100,8 +99,7 @@ TEST(RedirectTest, YouTubeSSLDigest) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
template <typename T> template <typename T> void BaseAuthTestFromHTTPWatch(T &cli) {
void BaseAuthTestFromHTTPWatch(T& cli) {
cli.set_proxy("localhost", 3128); cli.set_proxy("localhost", 3128);
cli.set_proxy_basic_auth("hello", "world"); cli.set_proxy_basic_auth("hello", "world");
@ -112,11 +110,11 @@ void BaseAuthTestFromHTTPWatch(T& cli) {
} }
{ {
auto res = auto res = cli.Get("/basic-auth/hello/world",
cli.Get("/basic-auth/hello/world", {make_basic_authentication_header("hello", "world")});
{make_basic_authentication_header("hello", "world")});
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body); EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
@ -124,7 +122,8 @@ void BaseAuthTestFromHTTPWatch(T& cli) {
cli.set_basic_auth("hello", "world"); cli.set_basic_auth("hello", "world");
auto res = cli.Get("/basic-auth/hello/world"); auto res = cli.Get("/basic-auth/hello/world");
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body); EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
@ -158,8 +157,7 @@ TEST(BaseAuthTest, SSL) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
template <typename T> template <typename T> void DigestAuthTestFromHTTPWatch(T &cli) {
void DigestAuthTestFromHTTPWatch(T& cli) {
cli.set_proxy("localhost", 3129); cli.set_proxy("localhost", 3129);
cli.set_proxy_digest_auth("hello", "world"); cli.set_proxy_digest_auth("hello", "world");
@ -181,7 +179,8 @@ void DigestAuthTestFromHTTPWatch(T& cli) {
for (auto path : paths) { for (auto path : paths) {
auto res = cli.Get(path.c_str()); auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body); EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
@ -216,8 +215,7 @@ TEST(DigestAuthTest, NoSSL) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
template <typename T> template <typename T> void KeepAliveTest(T &cli, bool basic) {
void KeepAliveTest(T& cli, bool basic) {
cli.set_proxy("localhost", basic ? 3128 : 3129); cli.set_proxy("localhost", basic ? 3128 : 3129);
if (basic) { if (basic) {
cli.set_proxy_basic_auth("hello", "world"); cli.set_proxy_basic_auth("hello", "world");
@ -249,9 +247,10 @@ void KeepAliveTest(T& cli, bool basic) {
"/httpbin/digest-auth/auth-int/hello/world/MD5", "/httpbin/digest-auth/auth-int/hello/world/MD5",
}; };
for (auto path: paths) { for (auto path : paths) {
auto res = cli.Get(path.c_str()); auto res = cli.Get(path.c_str());
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body); EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
} }