Compare commits

..

10 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
10 changed files with 796 additions and 582 deletions

View File

@ -5,6 +5,10 @@ name: abidiff
on: [push, pull_request] on: [push, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: true
defaults: defaults:
run: run:
shell: sh shell: sh

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

@ -20,6 +20,10 @@ on:
type: boolean type: boolean
default: true default: true
concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: true
env: env:
GTEST_FILTER: ${{ github.event.inputs.gtest_filter || '*' }} GTEST_FILTER: ${{ github.event.inputs.gtest_filter || '*' }}
@ -43,21 +47,14 @@ jobs:
(github.event_name == 'pull_request' && (github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) || 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') (github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
strategy:
matrix:
select_impl: ['select', 'poll']
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: install libraries - name: install libraries
run: sudo apt-get update && sudo apt-get install -y libbrotli-dev libcurl4-openssl-dev run: sudo apt-get update && sudo apt-get install -y libbrotli-dev libcurl4-openssl-dev
- name: build and run tests - name: build and run tests
env:
SELECT_IMPL: ${{ matrix.select_impl }}
run: cd test && make run: cd test && make
- name: run fuzz test target - name: run fuzz test target
env:
SELECT_IMPL: ${{ matrix.select_impl }}
run: cd test && make fuzz_test run: cd test && make fuzz_test
macos: macos:
@ -67,19 +64,12 @@ jobs:
(github.event_name == 'pull_request' && (github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) || 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') (github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true')
strategy:
matrix:
select_impl: ['select', 'poll']
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: build and run tests - name: build and run tests
env:
SELECT_IMPL: ${{ matrix.select_impl }}
run: cd test && make run: cd test && make
- name: run fuzz test target - name: run fuzz test target
env:
SELECT_IMPL: ${{ matrix.select_impl }}
run: cd test && make fuzz_test run: cd test && make fuzz_test
windows: windows:
@ -91,7 +81,12 @@ jobs:
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_windows == 'true') (github.event_name == 'workflow_dispatch' && github.event.inputs.test_windows == 'true')
strategy: strategy:
matrix: matrix:
select_impl: ['select', 'poll'] 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: |
@ -107,41 +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: > run: >
cmake -B build -S . cmake -B build -S .
-DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_TYPE=Release
-DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake
-DHTTPLIB_TEST=ON -DHTTPLIB_TEST=ON
-DHTTPLIB_REQUIRE_OPENSSL=ON
-DHTTPLIB_REQUIRE_ZLIB=ON -DHTTPLIB_REQUIRE_ZLIB=ON
-DHTTPLIB_REQUIRE_BROTLI=ON -DHTTPLIB_REQUIRE_BROTLI=ON
-DHTTPLIB_USE_SELECT=${{ matrix.select_impl == 'select' && 'ON' || 'OFF' }} -DHTTPLIB_REQUIRE_OPENSSL=${{ matrix.config.with_ssl && 'ON' || 'OFF' }}
- name: Build with with SSL - name: Build ${{ matrix.config.name }}
run: cmake --build build --config Release -- /v:m /clp:ShowCommandLine /nologo run: cmake --build build --config Release -- /v:m /clp:ShowCommandLine
- name: Run tests with SSL - 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
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
-DHTTPLIB_TEST=ON
-DHTTPLIB_REQUIRE_OPENSSL=OFF
-DHTTPLIB_REQUIRE_ZLIB=ON
-DHTTPLIB_REQUIRE_BROTLI=ON
-DHTTPLIB_USE_SELECT=${{ matrix.select_impl == 'select' && 'ON' || 'OFF' }}
- name: Build without SSL
run: cmake --build build-no-ssl --config Release -- /v:m /clp:ShowCommandLine /nologo
- 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"

View File

@ -8,7 +8,6 @@
* HTTPLIB_REQUIRE_ZLIB (default off) * HTTPLIB_REQUIRE_ZLIB (default off)
* HTTPLIB_REQUIRE_BROTLI (default off) * HTTPLIB_REQUIRE_BROTLI (default off)
* HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on) * HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on)
* HTTPLIB_USE_SELECT (default off) choose between select() and poll()
* HTTPLIB_COMPILE (default off) * HTTPLIB_COMPILE (default off)
* HTTPLIB_INSTALL (default on) * HTTPLIB_INSTALL (default on)
* HTTPLIB_TEST (default off) * HTTPLIB_TEST (default off)
@ -47,7 +46,6 @@
* HTTPLIB_IS_USING_ZLIB - a bool for if ZLIB 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_BROTLI - a bool for if Brotli support is enabled.
* HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN - a bool for if support of loading system certs from the Apple Keychain is enabled. * HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN - a bool for if support of loading system certs from the Apple Keychain is enabled.
* HTTPLIB_IS_USING_SELECT - a bool for if select() is used instead of poll().
* HTTPLIB_IS_COMPILED - a bool for if the library is compiled, or otherwise header-only. * 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_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_LIBRARY - the full path to the library if compiled (e.g. /usr/lib/libhttplib.so).
@ -103,7 +101,6 @@ 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_REQUIRE_BROTLI "Requires Brotli to be found & linked, or fails build." OFF)
option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON) option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON)
option(HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN "Enable feature to load system certs from the Apple Keychain." ON) option(HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN "Enable feature to load system certs from the Apple Keychain." ON)
option(HTTPLIB_USE_SELECT "Uses select() instead of poll()." OFF)
# Defaults to static library # Defaults to static library
option(BUILD_SHARED_LIBS "Build the library as a shared library instead of static. Has no effect if using header-only." OFF) option(BUILD_SHARED_LIBS "Build the library as a shared library instead of static. Has no effect if using header-only." OFF)
if (BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE) if (BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
@ -115,7 +112,6 @@ endif()
# Set some variables that are used in-tree and while building based on our options # Set some variables that are used in-tree and while building based on our options
set(HTTPLIB_IS_COMPILED ${HTTPLIB_COMPILE}) set(HTTPLIB_IS_COMPILED ${HTTPLIB_COMPILE})
set(HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN ${HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN}) set(HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN ${HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN})
set(HTTPLIB_IS_USING_SELECT ${HTTPLIB_USE_SELECT})
# Threads needed for <thread> on some systems, and for <pthread.h> on Linux # Threads needed for <thread> on some systems, and for <pthread.h> on Linux
set(THREADS_PREFER_PTHREAD_FLAG TRUE) set(THREADS_PREFER_PTHREAD_FLAG TRUE)
@ -242,7 +238,6 @@ target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:CPPHTTPLIB_ZLIB_SUPPORT> $<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:CPPHTTPLIB_ZLIB_SUPPORT>
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT> $<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>,$<BOOL:${HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN}>>:CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN> $<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>,$<BOOL:${HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN}>>:CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN>
$<$<BOOL:${HTTPLIB_IS_USING_SELECT}>:CPPHTTPLIB_USE_SELECT>
) )
# CMake configuration files installation directory # CMake configuration files installation directory

View File

@ -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.
@ -578,7 +578,7 @@ auto res = cli.Get("/hi", headers);
``` ```
or or
```c++ ```c++
auto res = cli.Get("/hi", httplib::Headers{{"Hello", "World!"}}); auto res = cli.Get("/hi", {{"Hello", "World!"}});
``` ```
or or
```c++ ```c++
@ -675,7 +675,7 @@ auto res = cli.Get("/large-data",
std::string body; std::string body;
auto res = cli.Get( auto res = cli.Get(
"/stream", "/stream", Headers(),
[&](const Response &response) { [&](const Response &response) {
EXPECT_EQ(StatusCode::OK_200, response.status); EXPECT_EQ(StatusCode::OK_200, response.status);
return true; // return 'false' if you want to cancel the request. return true; // return 'false' if you want to cancel the request.
@ -847,13 +847,13 @@ The default `Accept-Encoding` value contains all possible compression types. So,
```c++ ```c++
res = cli.Get("/resource/foo"); res = cli.Get("/resource/foo");
res = cli.Get("/resource/foo", httplib::Headers{{"Accept-Encoding", "gzip, deflate, br"}}); res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
``` ```
If we don't want a response without compression, we have to set `Accept-Encoding` to an empty string. This behavior is similar to curl. If we don't want a response without compression, we have to set `Accept-Encoding` to an empty string. This behavior is similar to curl.
```c++ ```c++
res = cli.Get("/resource/foo", httplib::Headers{{"Accept-Encoding", ""}}); res = cli.Get("/resource/foo", {{"Accept-Encoding", ""}});
``` ```
### Compress request body on client ### Compress request body on client
@ -872,11 +872,6 @@ res->body; // Compressed data
``` ```
Use `select()` instead of `poll()`
----------------------------------
cpp-httplib defaults to the widely supported `poll()` system call. If your OS lacks support for `poll()`, define `CPPHTTPLIB_USE_SELECT` to use `select()` instead.
Unix Domain Socket Support Unix Domain Socket Support
-------------------------- --------------------------
@ -884,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

View File

@ -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"';

780
httplib.h

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,6 @@
CXX = clang++ CXX = clang++
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS -fsanitize=address CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS -fsanitize=address
ifeq ($(SELECT_IMPL),select)
CXXFLAGS += -DCPPHTTPLIB_USE_SELECT
endif
PREFIX ?= $(shell brew --prefix) PREFIX ?= $(shell brew --prefix)
OPENSSL_DIR = $(PREFIX)/opt/openssl@3 OPENSSL_DIR = $(PREFIX)/opt/openssl@3

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";

View File

@ -156,7 +156,7 @@ 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));
@ -167,17 +167,17 @@ TEST(SocketStream, is_writable_UNIX) {
}; };
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;
@ -212,7 +212,7 @@ TEST(SocketStream, is_writable_INET) {
}; };
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;
}); });
@ -524,37 +524,37 @@ TEST(GetHeaderValueTest, RegularInvalidValueInt) {
TEST(GetHeaderValueTest, Range) { TEST(GetHeaderValueTest, Range) {
{ {
auto headers = Headers{make_range_header({{1, -1}})}; Headers headers = {make_range_header({{1, -1}})};
auto val = detail::get_header_value(headers, "Range", 0, 0); auto val = detail::get_header_value(headers, "Range", 0, 0);
EXPECT_STREQ("bytes=1-", val); EXPECT_STREQ("bytes=1-", val);
} }
{ {
auto headers = Headers{make_range_header({{-1, 1}})}; Headers headers = {make_range_header({{-1, 1}})};
auto val = detail::get_header_value(headers, "Range", 0, 0); auto val = detail::get_header_value(headers, "Range", 0, 0);
EXPECT_STREQ("bytes=-1", val); EXPECT_STREQ("bytes=-1", val);
} }
{ {
auto headers = Headers{make_range_header({{1, 10}})}; Headers headers = {make_range_header({{1, 10}})};
auto val = detail::get_header_value(headers, "Range", 0, 0); auto val = detail::get_header_value(headers, "Range", 0, 0);
EXPECT_STREQ("bytes=1-10", val); EXPECT_STREQ("bytes=1-10", val);
} }
{ {
auto headers = Headers{make_range_header({{1, 10}, {100, -1}})}; Headers headers = {make_range_header({{1, 10}, {100, -1}})};
auto val = detail::get_header_value(headers, "Range", 0, 0); auto val = detail::get_header_value(headers, "Range", 0, 0);
EXPECT_STREQ("bytes=1-10, 100-", val); EXPECT_STREQ("bytes=1-10, 100-", val);
} }
{ {
auto headers = Headers{make_range_header({{1, 10}, {100, 200}})}; Headers headers = {make_range_header({{1, 10}, {100, 200}})};
auto val = detail::get_header_value(headers, "Range", 0, 0); auto val = detail::get_header_value(headers, "Range", 0, 0);
EXPECT_STREQ("bytes=1-10, 100-200", val); EXPECT_STREQ("bytes=1-10, 100-200", val);
} }
{ {
auto headers = Headers{make_range_header({{0, 0}, {-1, 1}})}; Headers headers = {make_range_header({{0, 0}, {-1, 1}})};
auto val = detail::get_header_value(headers, "Range", 0, 0); auto val = detail::get_header_value(headers, "Range", 0, 0);
EXPECT_STREQ("bytes=0-0, -1", val); EXPECT_STREQ("bytes=0-0, -1", val);
} }
@ -800,10 +800,9 @@ TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver_Online) {
#endif #endif
cli.set_connection_timeout(2); cli.set_connection_timeout(2);
{
std::string body; std::string body;
auto res = cli.Get( auto res = cli.Get(
"/httpgallery/chunked/chunkedimage.aspx", "/httpgallery/chunked/chunkedimage.aspx?0.4153841143030137",
[&](const Response &response) { [&](const Response &response) {
EXPECT_EQ(StatusCode::OK_200, response.status); EXPECT_EQ(StatusCode::OK_200, response.status);
return true; return true;
@ -821,28 +820,6 @@ TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver_Online) {
EXPECT_EQ(out, body); EXPECT_EQ(out, body);
} }
{
std::string body;
auto res = cli.Get(
"/httpgallery/chunked/chunkedimage.aspx", Params{},
[&](const Response &response) {
EXPECT_EQ(StatusCode::OK_200, response.status);
return true;
},
[&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
ASSERT_TRUE(res);
std::string out;
detail::read_file("./image.jpg", out);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ(out, body);
}
}
TEST(RangeTest, FromHTTPBin_Online) { TEST(RangeTest, FromHTTPBin_Online) {
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN #ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
auto host = "httpbin.org"; auto host = "httpbin.org";
@ -869,7 +846,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
} }
{ {
auto headers = Headers{make_range_header({{1, -1}})}; Headers headers = {make_range_header({{1, -1}})};
auto res = cli.Get(path, headers); auto res = cli.Get(path, headers);
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ("bcdefghijklmnopqrstuvwxyzabcdef", res->body); EXPECT_EQ("bcdefghijklmnopqrstuvwxyzabcdef", res->body);
@ -877,7 +854,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
} }
{ {
auto headers = Headers{make_range_header({{1, 10}})}; Headers headers = {make_range_header({{1, 10}})};
auto res = cli.Get(path, headers); auto res = cli.Get(path, headers);
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ("bcdefghijk", res->body); EXPECT_EQ("bcdefghijk", res->body);
@ -885,7 +862,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
} }
{ {
auto headers = Headers{make_range_header({{0, 31}})}; Headers headers = {make_range_header({{0, 31}})};
auto res = cli.Get(path, headers); auto res = cli.Get(path, headers);
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body); EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
@ -893,7 +870,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
} }
{ {
auto headers = Headers{make_range_header({{0, -1}})}; Headers headers = {make_range_header({{0, -1}})};
auto res = cli.Get(path, headers); auto res = cli.Get(path, headers);
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body); EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
@ -901,7 +878,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
} }
{ {
auto headers = Headers{make_range_header({{0, 32}})}; Headers headers = {make_range_header({{0, 32}})};
auto res = cli.Get(path, headers); auto res = cli.Get(path, headers);
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
@ -1087,8 +1064,9 @@ TEST(CancelTest, NoCancelPost) {
Client cli(HOST, PORT); Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5)); cli.set_connection_timeout(std::chrono::seconds(5));
auto res = cli.Post("/", JSON_DATA, "application/json", auto res =
[](uint64_t, uint64_t) { return true; }); cli.Post("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return true; });
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ("Hello World!", res->body); EXPECT_EQ("Hello World!", res->body);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
@ -1113,8 +1091,9 @@ TEST(CancelTest, WithCancelSmallPayloadPost) {
Client cli(HOST, PORT); Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5)); cli.set_connection_timeout(std::chrono::seconds(5));
auto res = cli.Post("/", JSON_DATA, "application/json", auto res =
[](uint64_t, uint64_t) { return false; }); cli.Post("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return false; });
ASSERT_TRUE(!res); ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error()); EXPECT_EQ(Error::Canceled, res.error());
} }
@ -1138,8 +1117,9 @@ TEST(CancelTest, WithCancelLargePayloadPost) {
Client cli(HOST, PORT); Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5)); cli.set_connection_timeout(std::chrono::seconds(5));
auto res = cli.Post("/", JSON_DATA, "application/json", auto res =
[](uint64_t, uint64_t) { return false; }); cli.Post("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return false; });
ASSERT_TRUE(!res); ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error()); EXPECT_EQ(Error::Canceled, res.error());
} }
@ -1163,8 +1143,9 @@ TEST(CancelTest, NoCancelPut) {
Client cli(HOST, PORT); Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5)); cli.set_connection_timeout(std::chrono::seconds(5));
auto res = cli.Put("/", JSON_DATA, "application/json", auto res =
[](uint64_t, uint64_t) { return true; }); cli.Put("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return true; });
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ("Hello World!", res->body); EXPECT_EQ("Hello World!", res->body);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
@ -1189,8 +1170,9 @@ TEST(CancelTest, WithCancelSmallPayloadPut) {
Client cli(HOST, PORT); Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5)); cli.set_connection_timeout(std::chrono::seconds(5));
auto res = cli.Put("/", JSON_DATA, "application/json", auto res =
[](uint64_t, uint64_t) { return false; }); cli.Put("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return false; });
ASSERT_TRUE(!res); ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error()); EXPECT_EQ(Error::Canceled, res.error());
} }
@ -1214,8 +1196,9 @@ TEST(CancelTest, WithCancelLargePayloadPut) {
Client cli(HOST, PORT); Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5)); cli.set_connection_timeout(std::chrono::seconds(5));
auto res = cli.Put("/", JSON_DATA, "application/json", auto res =
[](uint64_t, uint64_t) { return false; }); cli.Put("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return false; });
ASSERT_TRUE(!res); ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error()); EXPECT_EQ(Error::Canceled, res.error());
} }
@ -1239,8 +1222,9 @@ TEST(CancelTest, NoCancelPatch) {
Client cli(HOST, PORT); Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5)); cli.set_connection_timeout(std::chrono::seconds(5));
auto res = cli.Patch("/", JSON_DATA, "application/json", auto res =
[](uint64_t, uint64_t) { return true; }); cli.Patch("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return true; });
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ("Hello World!", res->body); EXPECT_EQ("Hello World!", res->body);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
@ -1265,8 +1249,9 @@ TEST(CancelTest, WithCancelSmallPayloadPatch) {
Client cli(HOST, PORT); Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5)); cli.set_connection_timeout(std::chrono::seconds(5));
auto res = cli.Patch("/", JSON_DATA, "application/json", auto res =
[](uint64_t, uint64_t) { return false; }); cli.Patch("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return false; });
ASSERT_TRUE(!res); ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error()); EXPECT_EQ(Error::Canceled, res.error());
} }
@ -1290,8 +1275,9 @@ TEST(CancelTest, WithCancelLargePayloadPatch) {
Client cli(HOST, PORT); Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5)); cli.set_connection_timeout(std::chrono::seconds(5));
auto res = cli.Patch("/", JSON_DATA, "application/json", auto res =
[](uint64_t, uint64_t) { return false; }); cli.Patch("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return false; });
ASSERT_TRUE(!res); ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error()); EXPECT_EQ(Error::Canceled, res.error());
} }
@ -1315,8 +1301,9 @@ TEST(CancelTest, NoCancelDelete) {
Client cli(HOST, PORT); Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5)); cli.set_connection_timeout(std::chrono::seconds(5));
auto res = cli.Delete("/", JSON_DATA, "application/json", auto res =
[](uint64_t, uint64_t) { return true; }); cli.Delete("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return true; });
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ("Hello World!", res->body); EXPECT_EQ("Hello World!", res->body);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
@ -1341,8 +1328,9 @@ TEST(CancelTest, WithCancelSmallPayloadDelete) {
Client cli(HOST, PORT); Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5)); cli.set_connection_timeout(std::chrono::seconds(5));
auto res = cli.Delete("/", JSON_DATA, "application/json", auto res =
[](uint64_t, uint64_t) { return false; }); cli.Delete("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return false; });
ASSERT_TRUE(!res); ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error()); EXPECT_EQ(Error::Canceled, res.error());
} }
@ -1366,8 +1354,9 @@ TEST(CancelTest, WithCancelLargePayloadDelete) {
Client cli(HOST, PORT); Client cli(HOST, PORT);
cli.set_connection_timeout(std::chrono::seconds(5)); cli.set_connection_timeout(std::chrono::seconds(5));
auto res = cli.Delete("/", JSON_DATA, "application/json", auto res =
[](uint64_t, uint64_t) { return false; }); cli.Delete("/", Headers(), JSON_DATA.data(), JSON_DATA.size(),
"application/json", [](uint64_t, uint64_t) { return false; });
ASSERT_TRUE(!res); ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error()); EXPECT_EQ(Error::Canceled, res.error());
} }
@ -1396,8 +1385,8 @@ TEST(BaseAuthTest, FromHTTPWatch_Online) {
} }
{ {
auto res = cli.Get( auto res =
path, Headers{make_basic_authentication_header("hello", "world")}); cli.Get(path, {make_basic_authentication_header("hello", "world")});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body); res->body);
@ -1614,7 +1603,7 @@ TEST(HttpsToHttpRedirectTest2, Redirect_Online) {
params.emplace("url", "http://www.google.com"); params.emplace("url", "http://www.google.com");
params.emplace("status_code", "302"); params.emplace("status_code", "302");
auto res = cli.Get("/httpbin/redirect-to", params); auto res = cli.Get("/httpbin/redirect-to", params, Headers{});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
@ -1626,7 +1615,7 @@ TEST(HttpsToHttpRedirectTest3, Redirect_Online) {
Params params; Params params;
params.emplace("url", "http://www.google.com"); params.emplace("url", "http://www.google.com");
auto res = cli.Get("/httpbin/redirect-to?status_code=302", params); auto res = cli.Get("/httpbin/redirect-to?status_code=302", params, Headers{});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
@ -2052,7 +2041,7 @@ TEST(ErrorHandlerTest, ContentLength) {
{ {
Client cli(HOST, PORT); Client cli(HOST, PORT);
auto res = cli.Get("/hi", Headers{{"Accept-Encoding", ""}}); auto res = cli.Get("/hi", {{"Accept-Encoding", ""}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type")); EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
@ -2135,7 +2124,7 @@ TEST(ExceptionTest, WithExceptionHandler) {
Client cli(HOST, PORT); Client cli(HOST, PORT);
for (size_t j = 0; j < 100; j++) { for (size_t j = 0; j < 100; j++) {
auto res = cli.Get("/hi", Headers{{"Accept-Encoding", ""}}); auto res = cli.Get("/hi", {{"Accept-Encoding", ""}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::InternalServerError_500, res->status); EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type")); EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
@ -2146,7 +2135,7 @@ TEST(ExceptionTest, WithExceptionHandler) {
cli.set_keep_alive(true); cli.set_keep_alive(true);
for (size_t j = 0; j < 100; j++) { for (size_t j = 0; j < 100; j++) {
auto res = cli.Get("/hi", Headers{{"Accept-Encoding", ""}}); auto res = cli.Get("/hi", {{"Accept-Encoding", ""}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::InternalServerError_500, res->status); EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type")); EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
@ -3090,7 +3079,7 @@ TEST_F(ServerTest, GetFileContent) {
} }
TEST_F(ServerTest, GetFileContentWithRange) { TEST_F(ServerTest, GetFileContentWithRange) {
auto res = cli_.Get("/file_content", Headers{{make_range_header({{1, 3}})}}); auto res = cli_.Get("/file_content", {{make_range_header({{1, 3}})}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type")); EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
@ -3412,7 +3401,7 @@ TEST_F(ServerTest, UserDefinedMIMETypeMapping) {
} }
TEST_F(ServerTest, StaticFileRange) { TEST_F(ServerTest, StaticFileRange) {
auto res = cli_.Get("/dir/test.abcde", Headers{make_range_header({{2, 3}})}); auto res = cli_.Get("/dir/test.abcde", {{make_range_header({{2, 3}})}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("text/abcde", res->get_header_value("Content-Type")); EXPECT_EQ("text/abcde", res->get_header_value("Content-Type"));
@ -3423,8 +3412,8 @@ TEST_F(ServerTest, StaticFileRange) {
} }
TEST_F(ServerTest, StaticFileRanges) { TEST_F(ServerTest, StaticFileRanges) {
auto res = cli_.Get("/dir/test.abcde", auto res =
Headers{make_range_header({{1, 2}, {4, -1}})}); cli_.Get("/dir/test.abcde", {{make_range_header({{1, 2}, {4, -1}})}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_TRUE( EXPECT_TRUE(
@ -3436,7 +3425,7 @@ TEST_F(ServerTest, StaticFileRanges) {
} }
TEST_F(ServerTest, StaticFileRangeHead) { TEST_F(ServerTest, StaticFileRangeHead) {
auto res = cli_.Head("/dir/test.abcde", Headers{make_range_header({{2, 3}})}); auto res = cli_.Head("/dir/test.abcde", {{make_range_header({{2, 3}})}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("text/abcde", res->get_header_value("Content-Type")); EXPECT_EQ("text/abcde", res->get_header_value("Content-Type"));
@ -3446,7 +3435,7 @@ TEST_F(ServerTest, StaticFileRangeHead) {
} }
TEST_F(ServerTest, StaticFileRangeBigFile) { TEST_F(ServerTest, StaticFileRangeBigFile) {
auto res = cli_.Get("/dir/1MB.txt", Headers{make_range_header({{-1, 5}})}); auto res = cli_.Get("/dir/1MB.txt", {{make_range_header({{-1, 5}})}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type")); EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
@ -3458,7 +3447,7 @@ TEST_F(ServerTest, StaticFileRangeBigFile) {
} }
TEST_F(ServerTest, StaticFileRangeBigFile2) { TEST_F(ServerTest, StaticFileRangeBigFile2) {
auto res = cli_.Get("/dir/1MB.txt", Headers{make_range_header({{1, 4097}})}); auto res = cli_.Get("/dir/1MB.txt", {{make_range_header({{1, 4097}})}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type")); EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
@ -3800,7 +3789,7 @@ TEST_F(ServerTest, CaseInsensitiveTransferEncoding) {
} }
TEST_F(ServerTest, GetStreamed2) { TEST_F(ServerTest, GetStreamed2) {
auto res = cli_.Get("/streamed", Headers{make_range_header({{2, 3}})}); auto res = cli_.Get("/streamed", {{make_range_header({{2, 3}})}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("2", res->get_header_value("Content-Length")); EXPECT_EQ("2", res->get_header_value("Content-Length"));
@ -3818,8 +3807,7 @@ TEST_F(ServerTest, GetStreamed) {
} }
TEST_F(ServerTest, GetStreamedWithRange1) { TEST_F(ServerTest, GetStreamedWithRange1) {
auto res = auto res = cli_.Get("/streamed-with-range", {{make_range_header({{3, 5}})}});
cli_.Get("/streamed-with-range", Headers{make_range_header({{3, 5}})});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("3", res->get_header_value("Content-Length")); EXPECT_EQ("3", res->get_header_value("Content-Length"));
@ -3829,8 +3817,7 @@ TEST_F(ServerTest, GetStreamedWithRange1) {
} }
TEST_F(ServerTest, GetStreamedWithRange2) { TEST_F(ServerTest, GetStreamedWithRange2) {
auto res = auto res = cli_.Get("/streamed-with-range", {{make_range_header({{1, -1}})}});
cli_.Get("/streamed-with-range", Headers{make_range_header({{1, -1}})});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("6", res->get_header_value("Content-Length")); EXPECT_EQ("6", res->get_header_value("Content-Length"));
@ -3840,7 +3827,7 @@ TEST_F(ServerTest, GetStreamedWithRange2) {
} }
TEST_F(ServerTest, GetStreamedWithRangeSuffix1) { TEST_F(ServerTest, GetStreamedWithRangeSuffix1) {
auto res = cli_.Get("/streamed-with-range", Headers{{"Range", "bytes=-3"}}); auto res = cli_.Get("/streamed-with-range", {{"Range", "bytes=-3"}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("3", res->get_header_value("Content-Length")); EXPECT_EQ("3", res->get_header_value("Content-Length"));
@ -3850,8 +3837,7 @@ TEST_F(ServerTest, GetStreamedWithRangeSuffix1) {
} }
TEST_F(ServerTest, GetStreamedWithRangeSuffix2) { TEST_F(ServerTest, GetStreamedWithRangeSuffix2) {
auto res = auto res = cli_.Get("/streamed-with-range?error", {{"Range", "bytes=-9999"}});
cli_.Get("/streamed-with-range?error", Headers{{"Range", "bytes=-9999"}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
EXPECT_EQ("0", res->get_header_value("Content-Length")); EXPECT_EQ("0", res->get_header_value("Content-Length"));
@ -3860,9 +3846,8 @@ TEST_F(ServerTest, GetStreamedWithRangeSuffix2) {
} }
TEST_F(ServerTest, GetStreamedWithRangeError) { TEST_F(ServerTest, GetStreamedWithRangeError) {
auto res = auto res = cli_.Get("/streamed-with-range",
cli_.Get("/streamed-with-range", {{"Range", "bytes=92233720368547758079223372036854775806-"
Headers{{"Range", "bytes=92233720368547758079223372036854775806-"
"92233720368547758079223372036854775807"}}); "92233720368547758079223372036854775807"}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
@ -3874,8 +3859,7 @@ TEST_F(ServerTest, GetStreamedWithRangeError) {
TEST_F(ServerTest, GetRangeWithMaxLongLength) { TEST_F(ServerTest, GetRangeWithMaxLongLength) {
auto res = cli_.Get( auto res = cli_.Get(
"/with-range", "/with-range",
Headers{{"Range", {{"Range", "bytes=0-" + std::to_string(std::numeric_limits<long>::max())},
"bytes=0-" + std::to_string(std::numeric_limits<long>::max())},
{"Accept-Encoding", ""}}); {"Accept-Encoding", ""}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ(StatusCode::PartialContent_206, res->status);
@ -3886,7 +3870,7 @@ TEST_F(ServerTest, GetRangeWithMaxLongLength) {
} }
TEST_F(ServerTest, GetRangeWithZeroToInfinite) { TEST_F(ServerTest, GetRangeWithZeroToInfinite) {
auto res = cli_.Get("/with-range", Headers{ auto res = cli_.Get("/with-range", {
{"Range", "bytes=0-"}, {"Range", "bytes=0-"},
{"Accept-Encoding", ""}, {"Accept-Encoding", ""},
}); });
@ -3899,8 +3883,8 @@ TEST_F(ServerTest, GetRangeWithZeroToInfinite) {
} }
TEST_F(ServerTest, GetStreamedWithRangeMultipart) { TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
auto res = cli_.Get("/streamed-with-range", auto res =
Headers{make_range_header({{1, 2}, {4, 5}})}); cli_.Get("/streamed-with-range", {{make_range_header({{1, 2}, {4, 5}})}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("267", res->get_header_value("Content-Length")); EXPECT_EQ("267", res->get_header_value("Content-Length"));
@ -3914,8 +3898,8 @@ TEST_F(ServerTest, GetStreamedWithTooManyRanges) {
ranges.emplace_back(0, -1); ranges.emplace_back(0, -1);
} }
auto res = cli_.Get("/streamed-with-range?error", auto res =
Headers{make_range_header(ranges)}); cli_.Get("/streamed-with-range?error", {{make_range_header(ranges)}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
EXPECT_EQ("0", res->get_header_value("Content-Length")); EXPECT_EQ("0", res->get_header_value("Content-Length"));
@ -3925,7 +3909,7 @@ TEST_F(ServerTest, GetStreamedWithTooManyRanges) {
TEST_F(ServerTest, GetStreamedWithNonAscendingRanges) { TEST_F(ServerTest, GetStreamedWithNonAscendingRanges) {
auto res = cli_.Get("/streamed-with-range?error", auto res = cli_.Get("/streamed-with-range?error",
Headers{make_range_header({{0, -1}, {0, -1}})}); {{make_range_header({{0, -1}, {0, -1}})}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
EXPECT_EQ("0", res->get_header_value("Content-Length")); EXPECT_EQ("0", res->get_header_value("Content-Length"));
@ -3934,9 +3918,8 @@ TEST_F(ServerTest, GetStreamedWithNonAscendingRanges) {
} }
TEST_F(ServerTest, GetStreamedWithRangesMoreThanTwoOverwrapping) { TEST_F(ServerTest, GetStreamedWithRangesMoreThanTwoOverwrapping) {
auto res = auto res = cli_.Get("/streamed-with-range?error",
cli_.Get("/streamed-with-range?error", {{make_range_header({{0, 1}, {1, 2}, {2, 3}, {3, 4}})}});
Headers{make_range_header({{0, 1}, {1, 2}, {2, 3}, {3, 4}})});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
EXPECT_EQ("0", res->get_header_value("Content-Length")); EXPECT_EQ("0", res->get_header_value("Content-Length"));
@ -3986,7 +3969,7 @@ TEST_F(ServerTest, ClientStop) {
} }
TEST_F(ServerTest, GetWithRange1) { TEST_F(ServerTest, GetWithRange1) {
auto res = cli_.Get("/with-range", Headers{ auto res = cli_.Get("/with-range", {
make_range_header({{3, 5}}), make_range_header({{3, 5}}),
{"Accept-Encoding", ""}, {"Accept-Encoding", ""},
}); });
@ -3999,7 +3982,7 @@ TEST_F(ServerTest, GetWithRange1) {
} }
TEST_F(ServerTest, GetWithRange2) { TEST_F(ServerTest, GetWithRange2) {
auto res = cli_.Get("/with-range", Headers{ auto res = cli_.Get("/with-range", {
make_range_header({{1, -1}}), make_range_header({{1, -1}}),
{"Accept-Encoding", ""}, {"Accept-Encoding", ""},
}); });
@ -4012,7 +3995,7 @@ TEST_F(ServerTest, GetWithRange2) {
} }
TEST_F(ServerTest, GetWithRange3) { TEST_F(ServerTest, GetWithRange3) {
auto res = cli_.Get("/with-range", Headers{ auto res = cli_.Get("/with-range", {
make_range_header({{0, 0}}), make_range_header({{0, 0}}),
{"Accept-Encoding", ""}, {"Accept-Encoding", ""},
}); });
@ -4025,7 +4008,7 @@ TEST_F(ServerTest, GetWithRange3) {
} }
TEST_F(ServerTest, GetWithRange4) { TEST_F(ServerTest, GetWithRange4) {
auto res = cli_.Get("/with-range", Headers{ auto res = cli_.Get("/with-range", {
make_range_header({{-1, 2}}), make_range_header({{-1, 2}}),
{"Accept-Encoding", ""}, {"Accept-Encoding", ""},
}); });
@ -4038,15 +4021,13 @@ TEST_F(ServerTest, GetWithRange4) {
} }
TEST_F(ServerTest, GetWithRangeOffsetGreaterThanContent) { TEST_F(ServerTest, GetWithRangeOffsetGreaterThanContent) {
auto res = auto res = cli_.Get("/with-range", {{make_range_header({{10000, 20000}})}});
cli_.Get("/with-range", Headers{make_range_header({{10000, 20000}})});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
} }
TEST_F(ServerTest, GetWithRangeMultipart) { TEST_F(ServerTest, GetWithRangeMultipart) {
auto res = auto res = cli_.Get("/with-range", {{make_range_header({{1, 2}, {4, 5}})}});
cli_.Get("/with-range", Headers{make_range_header({{1, 2}, {4, 5}})});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("267", res->get_header_value("Content-Length")); EXPECT_EQ("267", res->get_header_value("Content-Length"));
@ -4055,15 +4036,15 @@ TEST_F(ServerTest, GetWithRangeMultipart) {
} }
TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) { TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) {
auto res = cli_.Get("/with-range", auto res =
Headers{make_range_header({{-1, 2}, {10000, 30000}})}); cli_.Get("/with-range", {{make_range_header({{-1, 2}, {10000, 30000}})}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status); EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
} }
TEST_F(ServerTest, GetWithRangeCustomizedResponse) { TEST_F(ServerTest, GetWithRangeCustomizedResponse) {
auto res = cli_.Get("/with-range-customized-response", auto res = cli_.Get("/with-range-customized-response",
Headers{make_range_header({{1, 2}})}); {{make_range_header({{1, 2}})}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::BadRequest_400, res->status); EXPECT_EQ(StatusCode::BadRequest_400, res->status);
EXPECT_EQ(true, res->has_header("Content-Length")); EXPECT_EQ(true, res->has_header("Content-Length"));
@ -4073,7 +4054,7 @@ TEST_F(ServerTest, GetWithRangeCustomizedResponse) {
TEST_F(ServerTest, GetWithRangeMultipartCustomizedResponseMultipleRange) { TEST_F(ServerTest, GetWithRangeMultipartCustomizedResponseMultipleRange) {
auto res = cli_.Get("/with-range-customized-response", auto res = cli_.Get("/with-range-customized-response",
Headers{make_range_header({{1, 2}, {4, 5}})}); {{make_range_header({{1, 2}, {4, 5}})}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::BadRequest_400, res->status); EXPECT_EQ(StatusCode::BadRequest_400, res->status);
EXPECT_EQ(true, res->has_header("Content-Length")); EXPECT_EQ(true, res->has_header("Content-Length"));
@ -4082,7 +4063,7 @@ TEST_F(ServerTest, GetWithRangeMultipartCustomizedResponseMultipleRange) {
} }
TEST_F(ServerTest, Issue1772) { TEST_F(ServerTest, Issue1772) {
auto res = cli_.Get("/issue1772", Headers{make_range_header({{1000, -1}})}); auto res = cli_.Get("/issue1772", {{make_range_header({{1000, -1}})}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::Unauthorized_401, res->status); EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
} }
@ -5785,7 +5766,7 @@ TEST(GetWithParametersTest, GetWithParameters) {
params.emplace("hello", "world"); params.emplace("hello", "world");
params.emplace("hello2", "world2"); params.emplace("hello2", "world2");
params.emplace("hello3", "world3"); params.emplace("hello3", "world3");
auto res = cli.Get("/", params); auto res = cli.Get("/", params, Headers{});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
@ -5842,7 +5823,8 @@ TEST(GetWithParametersTest, GetWithParameters2) {
params.emplace("hello", "world"); params.emplace("hello", "world");
std::string body; std::string body;
auto res = cli.Get("/", params, [&](const char *data, size_t data_length) { auto res = cli.Get("/", params, Headers{},
[&](const char *data, size_t data_length) {
body.append(data, data_length); body.append(data, data_length);
return true; return true;
}); });
@ -5867,7 +5849,7 @@ TEST(ClientDefaultHeadersTest, DefaultHeaders_Online) {
Client cli(host); Client cli(host);
#endif #endif
cli.set_default_headers(Headers{make_range_header({{1, 10}})}); cli.set_default_headers({make_range_header({{1, 10}})});
cli.set_connection_timeout(5); cli.set_connection_timeout(5);
{ {
@ -6687,7 +6669,7 @@ TEST(SendAPI, WithParamsInRequest) {
ASSERT_TRUE(res); ASSERT_TRUE(res);
} }
{ {
auto res = cli.Get("/", Params{{"test", "test_value"}}); auto res = cli.Get("/", {{"test", "test_value"}}, Headers{});
ASSERT_TRUE(res); ASSERT_TRUE(res);
} }
} }
@ -6816,8 +6798,8 @@ TEST(YahooRedirectTest3, NewResultInterface_Online) {
#ifdef CPPHTTPLIB_BROTLI_SUPPORT #ifdef CPPHTTPLIB_BROTLI_SUPPORT
TEST(DecodeWithChunkedEncoding, BrotliEncoding_Online) { TEST(DecodeWithChunkedEncoding, BrotliEncoding_Online) {
Client cli("https://cdnjs.cloudflare.com"); Client cli("https://cdnjs.cloudflare.com");
auto res = cli.Get("/ajax/libs/jquery/3.5.1/jquery.js", auto res =
Headers{{"Accept-Encoding", "br"}}); cli.Get("/ajax/libs/jquery/3.5.1/jquery.js", {{"Accept-Encoding", "br"}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
@ -6846,7 +6828,7 @@ TEST(HttpsToHttpRedirectTest2, SimpleInterface_Online) {
params.emplace("url", "http://www.google.com"); params.emplace("url", "http://www.google.com");
params.emplace("status_code", "302"); params.emplace("status_code", "302");
auto res = cli.Get("/httpbin/redirect-to", params); auto res = cli.Get("/httpbin/redirect-to", params, Headers{});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
@ -6858,7 +6840,7 @@ TEST(HttpsToHttpRedirectTest3, SimpleInterface_Online) {
Params params; Params params;
params.emplace("url", "http://www.google.com"); params.emplace("url", "http://www.google.com");
auto res = cli.Get("/httpbin/redirect-to?status_code=302", params); auto res = cli.Get("/httpbin/redirect-to?status_code=302", params, Headers{});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
@ -8012,7 +7994,7 @@ TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) {
svr.wait_until_ready(); svr.wait_until_ready();
Client cli(HOST, PORT); Client cli(HOST, PORT);
cli.Get("/test", Headers{{"Test", "_\n\r_\n\r_"}}); cli.Get("/test", {{"Test", "_\n\r_\n\r_"}});
} }
TEST(InvalidHeaderCharsTest, is_field_name) { TEST(InvalidHeaderCharsTest, is_field_name) {
@ -8225,9 +8207,8 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
} }
#endif #endif
TEST(MaxTimeoutTest, ContentStream) { template <typename S, typename C>
Server svr; inline void max_timeout_test(S &svr, C &cli, time_t timeout, time_t threshold) {
svr.Get("/stream", [&](const Request &, Response &res) { svr.Get("/stream", [&](const Request &, Response &res) {
auto data = new std::string("01234567890123456789"); auto data = new std::string("01234567890123456789");
@ -8297,10 +8278,6 @@ TEST(MaxTimeoutTest, ContentStream) {
svr.wait_until_ready(); svr.wait_until_ready();
const time_t timeout = 2000;
const time_t threshold = 200;
Client cli("localhost", PORT);
cli.set_max_timeout(std::chrono::milliseconds(timeout)); cli.set_max_timeout(std::chrono::milliseconds(timeout));
{ {
@ -8352,132 +8329,108 @@ TEST(MaxTimeoutTest, ContentStream) {
} }
} }
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 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(MaxTimeoutTest, ContentStreamSSL) { 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); SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
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();
const time_t timeout = 2000;
const time_t threshold = 1000; // SSL_shutdown is slow...
SSLClient cli("localhost", PORT); SSLClient cli("localhost", PORT);
cli.enable_server_certificate_verification(false); cli.enable_server_certificate_verification(false);
cli.set_max_timeout(std::chrono::milliseconds(timeout));
{ max_timeout_test(svr, cli, timeout, threshold);
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";
}
} }
#endif #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();
}
}