Compare commits

..

2 Commits

Author SHA1 Message Date
yhirose
01d9579dbf WIP 2025-02-18 06:57:25 -05:00
yhirose
335246bc7d Cleaner API (breaking change) 2025-02-18 06:57:20 -05:00
10 changed files with 566 additions and 780 deletions

View File

@ -5,10 +5,6 @@ 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,11 +1,5 @@
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,10 +20,6 @@ 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 || '*' }}
@ -47,14 +43,21 @@ 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:
@ -64,12 +67,19 @@ 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:
@ -81,12 +91,7 @@ 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:
config: select_impl: ['select', 'poll']
- 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: |
@ -102,25 +107,41 @@ 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 vcpkg dependencies - name: Install libraries
run: vcpkg install gtest curl zlib brotli run: |
- name: Install OpenSSL vcpkg install gtest curl zlib brotli
if: ${{ matrix.config.with_ssl }} choco install openssl
run: choco install openssl
- name: Configure CMake ${{ matrix.config.name }} - name: Configure CMake with SSL
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_REQUIRE_OPENSSL=${{ matrix.config.with_ssl && 'ON' || 'OFF' }} -DHTTPLIB_USE_SELECT=${{ matrix.select_impl == 'select' && 'ON' || 'OFF' }}
- name: Build ${{ matrix.config.name }} - name: Build with with SSL
run: cmake --build build --config Release -- /v:m /clp:ShowCommandLine run: cmake --build build --config Release -- /v:m /clp:ShowCommandLine /nologo
- name: Run tests ${{ matrix.config.name }} - name: Run tests with SSL
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,6 +8,7 @@
* 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)
@ -46,6 +47,7 @@
* 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).
@ -101,6 +103,7 @@ 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)
@ -112,6 +115,7 @@ 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)
@ -238,6 +242,7 @@ 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 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`. `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`.
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", {{"Hello", "World!"}}); auto res = cli.Get("/hi", httplib::Headers{{"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", Headers(), "/stream",
[&](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", {{"Accept-Encoding", "gzip, deflate, br"}}); res = cli.Get("/resource/foo", httplib::Headers{{"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", {{"Accept-Encoding", ""}}); res = cli.Get("/resource/foo", httplib::Headers{{"Accept-Encoding", ""}});
``` ```
### Compress request body on client ### Compress request body on client
@ -872,6 +872,11 @@ 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
-------------------------- --------------------------
@ -879,7 +884,7 @@ Unix Domain Socket support is available on Linux and macOS.
```c++ ```c++
// Server // Server
httplib::Server svr; httplib::Server svr("./my-socket.sock");
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 default access log format // NOTE: From NGINX defualt 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"';

784
httplib.h

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,10 @@
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,9 +25,7 @@ public:
bool is_readable() const override { return true; } bool is_readable() const override { return true; }
bool wait_readable() const override { return true; } bool is_writable() 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, wait_writable_UNIX) { TEST(SocketStream, is_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, wait_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.wait_writable()); EXPECT_TRUE(s0.is_writable());
EXPECT_EQ(0, close(fds[1])); EXPECT_EQ(0, close(fds[1]));
EXPECT_FALSE(s0.wait_writable()); EXPECT_FALSE(s0.is_writable());
return true; return true;
}); });
EXPECT_EQ(0, close(fds[0])); EXPECT_EQ(0, close(fds[0]));
} }
TEST(SocketStream, wait_writable_INET) { TEST(SocketStream, is_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, wait_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.wait_writable()); EXPECT_FALSE(ss.is_writable());
return true; return true;
}); });
@ -524,37 +524,37 @@ TEST(GetHeaderValueTest, RegularInvalidValueInt) {
TEST(GetHeaderValueTest, Range) { TEST(GetHeaderValueTest, Range) {
{ {
Headers headers = {make_range_header({{1, -1}})}; auto 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);
} }
{ {
Headers headers = {make_range_header({{-1, 1}})}; auto 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);
} }
{ {
Headers headers = {make_range_header({{1, 10}})}; auto 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);
} }
{ {
Headers headers = {make_range_header({{1, 10}, {100, -1}})}; auto 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);
} }
{ {
Headers headers = {make_range_header({{1, 10}, {100, 200}})}; auto 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);
} }
{ {
Headers headers = {make_range_header({{0, 0}, {-1, 1}})}; auto 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,24 +800,47 @@ TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver_Online) {
#endif #endif
cli.set_connection_timeout(2); cli.set_connection_timeout(2);
std::string body; {
auto res = cli.Get( std::string body;
"/httpgallery/chunked/chunkedimage.aspx?0.4153841143030137", auto res = cli.Get(
[&](const Response &response) { "/httpgallery/chunked/chunkedimage.aspx",
EXPECT_EQ(StatusCode::OK_200, response.status); [&](const Response &response) {
return true; EXPECT_EQ(StatusCode::OK_200, response.status);
}, return true;
[&](const char *data, size_t data_length) { },
body.append(data, data_length); [&](const char *data, size_t data_length) {
return true; body.append(data, data_length);
}); return true;
ASSERT_TRUE(res); });
ASSERT_TRUE(res);
std::string out; std::string out;
detail::read_file("./image.jpg", out); detail::read_file("./image.jpg", out);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
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) {
@ -846,7 +869,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
} }
{ {
Headers headers = {make_range_header({{1, -1}})}; auto 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);
@ -854,7 +877,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
} }
{ {
Headers headers = {make_range_header({{1, 10}})}; auto 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);
@ -862,7 +885,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
} }
{ {
Headers headers = {make_range_header({{0, 31}})}; auto 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);
@ -870,7 +893,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
} }
{ {
Headers headers = {make_range_header({{0, -1}})}; auto 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);
@ -878,7 +901,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
} }
{ {
Headers headers = {make_range_header({{0, 32}})}; auto 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);
@ -1064,9 +1087,8 @@ 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 = auto res = cli.Post("/", JSON_DATA, "application/json",
cli.Post("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), [](uint64_t, uint64_t) { return true; });
"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);
@ -1091,9 +1113,8 @@ 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 = auto res = cli.Post("/", JSON_DATA, "application/json",
cli.Post("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), [](uint64_t, uint64_t) { return false; });
"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());
} }
@ -1117,9 +1138,8 @@ 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 = auto res = cli.Post("/", JSON_DATA, "application/json",
cli.Post("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), [](uint64_t, uint64_t) { return false; });
"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());
} }
@ -1143,9 +1163,8 @@ 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 = auto res = cli.Put("/", JSON_DATA, "application/json",
cli.Put("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), [](uint64_t, uint64_t) { return true; });
"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);
@ -1170,9 +1189,8 @@ 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 = auto res = cli.Put("/", JSON_DATA, "application/json",
cli.Put("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), [](uint64_t, uint64_t) { return false; });
"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());
} }
@ -1196,9 +1214,8 @@ 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 = auto res = cli.Put("/", JSON_DATA, "application/json",
cli.Put("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), [](uint64_t, uint64_t) { return false; });
"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());
} }
@ -1222,9 +1239,8 @@ 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 = auto res = cli.Patch("/", JSON_DATA, "application/json",
cli.Patch("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), [](uint64_t, uint64_t) { return true; });
"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);
@ -1249,9 +1265,8 @@ 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 = auto res = cli.Patch("/", JSON_DATA, "application/json",
cli.Patch("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), [](uint64_t, uint64_t) { return false; });
"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());
} }
@ -1275,9 +1290,8 @@ 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 = auto res = cli.Patch("/", JSON_DATA, "application/json",
cli.Patch("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), [](uint64_t, uint64_t) { return false; });
"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());
} }
@ -1301,9 +1315,8 @@ 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 = auto res = cli.Delete("/", JSON_DATA, "application/json",
cli.Delete("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), [](uint64_t, uint64_t) { return true; });
"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);
@ -1328,9 +1341,8 @@ 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 = auto res = cli.Delete("/", JSON_DATA, "application/json",
cli.Delete("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), [](uint64_t, uint64_t) { return false; });
"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());
} }
@ -1354,9 +1366,8 @@ 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 = auto res = cli.Delete("/", JSON_DATA, "application/json",
cli.Delete("/", Headers(), JSON_DATA.data(), JSON_DATA.size(), [](uint64_t, uint64_t) { return false; });
"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());
} }
@ -1385,8 +1396,8 @@ TEST(BaseAuthTest, FromHTTPWatch_Online) {
} }
{ {
auto res = auto res = cli.Get(
cli.Get(path, {make_basic_authentication_header("hello", "world")}); path, Headers{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);
@ -1603,7 +1614,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, Headers{}); auto res = cli.Get("/httpbin/redirect-to", params);
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
@ -1615,7 +1626,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, Headers{}); auto res = cli.Get("/httpbin/redirect-to?status_code=302", params);
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
@ -2041,7 +2052,7 @@ TEST(ErrorHandlerTest, ContentLength) {
{ {
Client cli(HOST, PORT); Client cli(HOST, PORT);
auto res = cli.Get("/hi", {{"Accept-Encoding", ""}}); auto res = cli.Get("/hi", Headers{{"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"));
@ -2124,7 +2135,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", {{"Accept-Encoding", ""}}); auto res = cli.Get("/hi", Headers{{"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"));
@ -2135,7 +2146,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", {{"Accept-Encoding", ""}}); auto res = cli.Get("/hi", Headers{{"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"));
@ -3079,7 +3090,7 @@ TEST_F(ServerTest, GetFileContent) {
} }
TEST_F(ServerTest, GetFileContentWithRange) { TEST_F(ServerTest, GetFileContentWithRange) {
auto res = cli_.Get("/file_content", {{make_range_header({{1, 3}})}}); auto res = cli_.Get("/file_content", Headers{{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"));
@ -3401,7 +3412,7 @@ TEST_F(ServerTest, UserDefinedMIMETypeMapping) {
} }
TEST_F(ServerTest, StaticFileRange) { TEST_F(ServerTest, StaticFileRange) {
auto res = cli_.Get("/dir/test.abcde", {{make_range_header({{2, 3}})}}); auto res = cli_.Get("/dir/test.abcde", Headers{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"));
@ -3412,8 +3423,8 @@ TEST_F(ServerTest, StaticFileRange) {
} }
TEST_F(ServerTest, StaticFileRanges) { TEST_F(ServerTest, StaticFileRanges) {
auto res = auto res = cli_.Get("/dir/test.abcde",
cli_.Get("/dir/test.abcde", {{make_range_header({{1, 2}, {4, -1}})}}); Headers{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(
@ -3425,7 +3436,7 @@ TEST_F(ServerTest, StaticFileRanges) {
} }
TEST_F(ServerTest, StaticFileRangeHead) { TEST_F(ServerTest, StaticFileRangeHead) {
auto res = cli_.Head("/dir/test.abcde", {{make_range_header({{2, 3}})}}); auto res = cli_.Head("/dir/test.abcde", Headers{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"));
@ -3435,7 +3446,7 @@ TEST_F(ServerTest, StaticFileRangeHead) {
} }
TEST_F(ServerTest, StaticFileRangeBigFile) { TEST_F(ServerTest, StaticFileRangeBigFile) {
auto res = cli_.Get("/dir/1MB.txt", {{make_range_header({{-1, 5}})}}); auto res = cli_.Get("/dir/1MB.txt", Headers{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"));
@ -3447,7 +3458,7 @@ TEST_F(ServerTest, StaticFileRangeBigFile) {
} }
TEST_F(ServerTest, StaticFileRangeBigFile2) { TEST_F(ServerTest, StaticFileRangeBigFile2) {
auto res = cli_.Get("/dir/1MB.txt", {{make_range_header({{1, 4097}})}}); auto res = cli_.Get("/dir/1MB.txt", Headers{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"));
@ -3789,7 +3800,7 @@ TEST_F(ServerTest, CaseInsensitiveTransferEncoding) {
} }
TEST_F(ServerTest, GetStreamed2) { TEST_F(ServerTest, GetStreamed2) {
auto res = cli_.Get("/streamed", {{make_range_header({{2, 3}})}}); auto res = cli_.Get("/streamed", Headers{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"));
@ -3807,7 +3818,8 @@ TEST_F(ServerTest, GetStreamed) {
} }
TEST_F(ServerTest, GetStreamedWithRange1) { TEST_F(ServerTest, GetStreamedWithRange1) {
auto res = cli_.Get("/streamed-with-range", {{make_range_header({{3, 5}})}}); auto res =
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"));
@ -3817,7 +3829,8 @@ TEST_F(ServerTest, GetStreamedWithRange1) {
} }
TEST_F(ServerTest, GetStreamedWithRange2) { TEST_F(ServerTest, GetStreamedWithRange2) {
auto res = cli_.Get("/streamed-with-range", {{make_range_header({{1, -1}})}}); auto res =
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"));
@ -3827,7 +3840,7 @@ TEST_F(ServerTest, GetStreamedWithRange2) {
} }
TEST_F(ServerTest, GetStreamedWithRangeSuffix1) { TEST_F(ServerTest, GetStreamedWithRangeSuffix1) {
auto res = cli_.Get("/streamed-with-range", {{"Range", "bytes=-3"}}); auto res = cli_.Get("/streamed-with-range", Headers{{"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"));
@ -3837,7 +3850,8 @@ TEST_F(ServerTest, GetStreamedWithRangeSuffix1) {
} }
TEST_F(ServerTest, GetStreamedWithRangeSuffix2) { TEST_F(ServerTest, GetStreamedWithRangeSuffix2) {
auto res = cli_.Get("/streamed-with-range?error", {{"Range", "bytes=-9999"}}); auto res =
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"));
@ -3846,8 +3860,9 @@ TEST_F(ServerTest, GetStreamedWithRangeSuffix2) {
} }
TEST_F(ServerTest, GetStreamedWithRangeError) { TEST_F(ServerTest, GetStreamedWithRangeError) {
auto res = cli_.Get("/streamed-with-range", auto res =
{{"Range", "bytes=92233720368547758079223372036854775806-" cli_.Get("/streamed-with-range",
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);
@ -3859,8 +3874,9 @@ TEST_F(ServerTest, GetStreamedWithRangeError) {
TEST_F(ServerTest, GetRangeWithMaxLongLength) { TEST_F(ServerTest, GetRangeWithMaxLongLength) {
auto res = cli_.Get( auto res = cli_.Get(
"/with-range", "/with-range",
{{"Range", "bytes=0-" + std::to_string(std::numeric_limits<long>::max())}, Headers{{"Range",
{"Accept-Encoding", ""}}); "bytes=0-" + std::to_string(std::numeric_limits<long>::max())},
{"Accept-Encoding", ""}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::PartialContent_206, res->status); EXPECT_EQ(StatusCode::PartialContent_206, res->status);
EXPECT_EQ("7", res->get_header_value("Content-Length")); EXPECT_EQ("7", res->get_header_value("Content-Length"));
@ -3870,7 +3886,7 @@ TEST_F(ServerTest, GetRangeWithMaxLongLength) {
} }
TEST_F(ServerTest, GetRangeWithZeroToInfinite) { TEST_F(ServerTest, GetRangeWithZeroToInfinite) {
auto res = cli_.Get("/with-range", { auto res = cli_.Get("/with-range", Headers{
{"Range", "bytes=0-"}, {"Range", "bytes=0-"},
{"Accept-Encoding", ""}, {"Accept-Encoding", ""},
}); });
@ -3883,8 +3899,8 @@ TEST_F(ServerTest, GetRangeWithZeroToInfinite) {
} }
TEST_F(ServerTest, GetStreamedWithRangeMultipart) { TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
auto res = auto res = cli_.Get("/streamed-with-range",
cli_.Get("/streamed-with-range", {{make_range_header({{1, 2}, {4, 5}})}}); 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"));
@ -3898,8 +3914,8 @@ TEST_F(ServerTest, GetStreamedWithTooManyRanges) {
ranges.emplace_back(0, -1); ranges.emplace_back(0, -1);
} }
auto res = auto res = cli_.Get("/streamed-with-range?error",
cli_.Get("/streamed-with-range?error", {{make_range_header(ranges)}}); Headers{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"));
@ -3909,7 +3925,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",
{{make_range_header({{0, -1}, {0, -1}})}}); Headers{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"));
@ -3918,8 +3934,9 @@ TEST_F(ServerTest, GetStreamedWithNonAscendingRanges) {
} }
TEST_F(ServerTest, GetStreamedWithRangesMoreThanTwoOverwrapping) { TEST_F(ServerTest, GetStreamedWithRangesMoreThanTwoOverwrapping) {
auto res = cli_.Get("/streamed-with-range?error", auto res =
{{make_range_header({{0, 1}, {1, 2}, {2, 3}, {3, 4}})}}); cli_.Get("/streamed-with-range?error",
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"));
@ -3969,7 +3986,7 @@ TEST_F(ServerTest, ClientStop) {
} }
TEST_F(ServerTest, GetWithRange1) { TEST_F(ServerTest, GetWithRange1) {
auto res = cli_.Get("/with-range", { auto res = cli_.Get("/with-range", Headers{
make_range_header({{3, 5}}), make_range_header({{3, 5}}),
{"Accept-Encoding", ""}, {"Accept-Encoding", ""},
}); });
@ -3982,7 +3999,7 @@ TEST_F(ServerTest, GetWithRange1) {
} }
TEST_F(ServerTest, GetWithRange2) { TEST_F(ServerTest, GetWithRange2) {
auto res = cli_.Get("/with-range", { auto res = cli_.Get("/with-range", Headers{
make_range_header({{1, -1}}), make_range_header({{1, -1}}),
{"Accept-Encoding", ""}, {"Accept-Encoding", ""},
}); });
@ -3995,7 +4012,7 @@ TEST_F(ServerTest, GetWithRange2) {
} }
TEST_F(ServerTest, GetWithRange3) { TEST_F(ServerTest, GetWithRange3) {
auto res = cli_.Get("/with-range", { auto res = cli_.Get("/with-range", Headers{
make_range_header({{0, 0}}), make_range_header({{0, 0}}),
{"Accept-Encoding", ""}, {"Accept-Encoding", ""},
}); });
@ -4008,7 +4025,7 @@ TEST_F(ServerTest, GetWithRange3) {
} }
TEST_F(ServerTest, GetWithRange4) { TEST_F(ServerTest, GetWithRange4) {
auto res = cli_.Get("/with-range", { auto res = cli_.Get("/with-range", Headers{
make_range_header({{-1, 2}}), make_range_header({{-1, 2}}),
{"Accept-Encoding", ""}, {"Accept-Encoding", ""},
}); });
@ -4021,13 +4038,15 @@ TEST_F(ServerTest, GetWithRange4) {
} }
TEST_F(ServerTest, GetWithRangeOffsetGreaterThanContent) { TEST_F(ServerTest, GetWithRangeOffsetGreaterThanContent) {
auto res = cli_.Get("/with-range", {{make_range_header({{10000, 20000}})}}); auto res =
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 = cli_.Get("/with-range", {{make_range_header({{1, 2}, {4, 5}})}}); auto res =
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"));
@ -4036,15 +4055,15 @@ TEST_F(ServerTest, GetWithRangeMultipart) {
} }
TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) { TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) {
auto res = auto res = cli_.Get("/with-range",
cli_.Get("/with-range", {{make_range_header({{-1, 2}, {10000, 30000}})}}); Headers{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",
{{make_range_header({{1, 2}})}}); Headers{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"));
@ -4054,7 +4073,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",
{{make_range_header({{1, 2}, {4, 5}})}}); Headers{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"));
@ -4063,7 +4082,7 @@ TEST_F(ServerTest, GetWithRangeMultipartCustomizedResponseMultipleRange) {
} }
TEST_F(ServerTest, Issue1772) { TEST_F(ServerTest, Issue1772) {
auto res = cli_.Get("/issue1772", {{make_range_header({{1000, -1}})}}); auto res = cli_.Get("/issue1772", Headers{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);
} }
@ -5766,7 +5785,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, Headers{}); auto res = cli.Get("/", params);
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
@ -5823,11 +5842,10 @@ TEST(GetWithParametersTest, GetWithParameters2) {
params.emplace("hello", "world"); params.emplace("hello", "world");
std::string body; std::string body;
auto res = cli.Get("/", params, Headers{}, auto res = cli.Get("/", params, [&](const char *data, size_t data_length) {
[&](const char *data, size_t data_length) { body.append(data, data_length);
body.append(data, data_length); return true;
return true; });
});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
@ -5849,7 +5867,7 @@ TEST(ClientDefaultHeadersTest, DefaultHeaders_Online) {
Client cli(host); Client cli(host);
#endif #endif
cli.set_default_headers({make_range_header({{1, 10}})}); cli.set_default_headers(Headers{make_range_header({{1, 10}})});
cli.set_connection_timeout(5); cli.set_connection_timeout(5);
{ {
@ -6669,7 +6687,7 @@ TEST(SendAPI, WithParamsInRequest) {
ASSERT_TRUE(res); ASSERT_TRUE(res);
} }
{ {
auto res = cli.Get("/", {{"test", "test_value"}}, Headers{}); auto res = cli.Get("/", Params{{"test", "test_value"}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
} }
} }
@ -6798,8 +6816,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 = auto res = cli.Get("/ajax/libs/jquery/3.5.1/jquery.js",
cli.Get("/ajax/libs/jquery/3.5.1/jquery.js", {{"Accept-Encoding", "br"}}); Headers{{"Accept-Encoding", "br"}});
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
@ -6828,7 +6846,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, Headers{}); auto res = cli.Get("/httpbin/redirect-to", params);
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
@ -6840,7 +6858,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, Headers{}); auto res = cli.Get("/httpbin/redirect-to?status_code=302", params);
ASSERT_TRUE(res); ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
@ -7994,7 +8012,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", {{"Test", "_\n\r_\n\r_"}}); cli.Get("/test", Headers{{"Test", "_\n\r_\n\r_"}});
} }
TEST(InvalidHeaderCharsTest, is_field_name) { TEST(InvalidHeaderCharsTest, is_field_name) {
@ -8207,8 +8225,9 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
} }
#endif #endif
template <typename S, typename C> TEST(MaxTimeoutTest, ContentStream) {
inline void max_timeout_test(S &svr, C &cli, time_t timeout, time_t threshold) { Server svr;
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");
@ -8278,6 +8297,10 @@ inline void max_timeout_test(S &svr, C &cli, time_t timeout, time_t threshold) {
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));
{ {
@ -8329,108 +8352,132 @@ inline void max_timeout_test(S &svr, C &cli, time_t timeout, time_t threshold) {
} }
} }
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);
SSLClient cli("localhost", PORT); svr.Get("/stream", [&](const Request &, Response &res) {
cli.enable_server_certificate_verification(false); auto data = new std::string("01234567890123456789");
max_timeout_test(svr, cli, timeout, threshold); res.set_content_provider(
} data->size(), "text/plain",
#endif [&, data](size_t offset, size_t length, DataSink &sink) {
const size_t DATA_CHUNK_SIZE = 4;
class EventDispatcher { const auto &d = *data;
public: std::this_thread::sleep_for(std::chrono::seconds(1));
EventDispatcher() {} sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
return true;
void wait_event(DataSink *sink) { },
unique_lock<mutex> lk(m_); [data](bool success) {
int id = id_; EXPECT_FALSE(success);
cv_.wait(lk, [&] { return cid_ == id; }); delete data;
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.Get("/stream_without_length", [&](const Request &, Response &res) {
auto i = new size_t(0);
svr.wait_until_ready(); res.set_content_provider(
"text/plain",
thread event_thread([&] { [i](size_t, DataSink &sink) {
int id = 0; if (*i < 5) {
while (svr.is_running()) { std::this_thread::sleep_for(std::chrono::seconds(1));
this_thread::sleep_for(chrono::milliseconds(500)); sink.write("abcd", 4);
(*i)++;
std::stringstream ss; } else {
ss << "data: " << id << "\n\n"; sink.done();
ed.send_event(ss.str()); }
id++; 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([&] { auto se = detail::scope_exit([&] {
svr.stop(); svr.stop();
listen_thread.join(); listen_thread.join();
event_thread.join();
ASSERT_FALSE(svr.is_running()); 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);
cli.enable_server_certificate_verification(false);
cli.set_max_timeout(std::chrono::milliseconds(timeout));
{ {
auto client = detail::make_unique<Client>(HOST, PORT); auto start = std::chrono::steady_clock::now();
client->set_read_timeout(std::chrono::minutes(10));
std::atomic<bool> stop{false}; auto res = cli.Get("/stream");
std::thread t([&] { auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
client->Get("/event1", std::chrono::steady_clock::now() - start)
[&](const char *, size_t) -> bool { return !stop; }); .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;
}); });
std::this_thread::sleep_for(std::chrono::seconds(2)); auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
stop = true; std::chrono::steady_clock::now() - start)
client->stop(); .count();
client.reset();
t.join(); ASSERT_FALSE(res);
EXPECT_EQ(Error::Read, res.error());
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold)
<< "Timeout exceeded by " << (elapsed - timeout) << "ms";
} }
} }
#endif