mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25d72bf881 | |||
| 9d5b5297cc | |||
| 462884bebb | |||
| b1cc24b795 | |||
| f0eb55b327 | |||
| 6dc285b5ca | |||
| 07e614eef7 | |||
| 916b2a8fd3 | |||
| 869f5bb279 | |||
| 3e21338f82 | |||
| 37bb3c6a77 | |||
| d4ab2fa0e6 | |||
| 72d3f4896a | |||
| 5e6f973b99 | |||
| 7ed77b02ad | |||
| 127a64d5a0 | |||
| caa31aafda | |||
| dae318495f | |||
| 305a7abcb9 | |||
| 219d13b718 | |||
| df20c27696 | |||
| a5a62768c0 | |||
| 4001637beb | |||
| 47044c05a8 | |||
| a449d82723 | |||
| fee8e97b4e | |||
| 72d9ed4056 | |||
| 1be1b3a86d | |||
| 9452c0a4b6 | |||
| 307b729549 | |||
| 696239d6e1 | |||
| 6929d90353 | |||
| 348d032029 | |||
| d1d3fcdfd5 | |||
| abf3a67dd0 | |||
| d87abeecf0 | |||
| 4e28e4f741 | |||
| 80a55cedeb | |||
| d05c343602 | |||
| 33f67386fe | |||
| 56d8168dc4 | |||
| 5d87cc0558 | |||
| cb41947eb4 | |||
| 0857eba17b | |||
| 020b0db090 | |||
| bf0760fde4 | |||
| bb8e45383e | |||
| a1df576e4f | |||
| 7fb0254794 | |||
| c82d1e52cc | |||
| 846151b605 | |||
| e44e31dd5b | |||
| f7b9501662 | |||
| e12fe4cbbb | |||
| 49d2e1f135 | |||
| 8191fd8e6c |
+12
-18
@@ -60,10 +60,10 @@
|
||||
]]
|
||||
cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR)
|
||||
|
||||
# Get the user agent and use it as a version
|
||||
# This gets the string with the user agent from the header.
|
||||
# Get the CPPHTTPLIB_VERSION value and use it as a version
|
||||
# This gets the string with the CPPHTTPLIB_VERSION value from the header.
|
||||
# This is so the maintainer doesn't actually need to update this manually.
|
||||
file(STRINGS httplib.h _raw_version_string REGEX "User\-Agent.*cpp\-httplib/([0-9]+\.?)+")
|
||||
file(STRINGS httplib.h _raw_version_string REGEX "CPPHTTPLIB_VERSION \"([0-9]+\\.[0-9]+\\.[0-9]+)\"")
|
||||
|
||||
# Needed since git tags have "v" prefixing them.
|
||||
# Also used if the fallback to user agent string is being used.
|
||||
@@ -100,6 +100,7 @@ if (BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
|
||||
endif()
|
||||
|
||||
# Threads needed for <thread> on some systems, and for <pthread.h> on Linux
|
||||
set(THREADS_PREFER_PTHREAD_FLAG true)
|
||||
find_package(Threads REQUIRED)
|
||||
# Since Cmake v3.11, Crypto & SSL became optional when not specified as COMPONENTS.
|
||||
if(HTTPLIB_REQUIRE_OPENSSL)
|
||||
@@ -160,7 +161,7 @@ if(HTTPLIB_COMPILE)
|
||||
ERROR_VARIABLE _httplib_split_error
|
||||
)
|
||||
if(_httplib_split_error)
|
||||
message(FATAL_ERROR "Failed when trying to split Cpp-httplib with the Python script.\n${_httplib_split_error}")
|
||||
message(FATAL_ERROR "Failed when trying to split cpp-httplib with the Python script.\n${_httplib_split_error}")
|
||||
endif()
|
||||
|
||||
# split.py puts output in "out"
|
||||
@@ -172,6 +173,11 @@ if(HTTPLIB_COMPILE)
|
||||
$<BUILD_INTERFACE:${_httplib_build_includedir}/httplib.h>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/httplib.h>
|
||||
)
|
||||
set_target_properties(${PROJECT_NAME}
|
||||
PROPERTIES
|
||||
VERSION ${${PROJECT_NAME}_VERSION}
|
||||
SOVERSION ${${PROJECT_NAME}_VERSION_MAJOR}
|
||||
)
|
||||
else()
|
||||
# This is for header-only.
|
||||
set(_INTERFACE_OR_PUBLIC INTERFACE)
|
||||
@@ -182,19 +188,9 @@ endif()
|
||||
# Only useful if building in-tree, versus using it from an installation.
|
||||
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
||||
|
||||
# Might be missing some, but this list is somewhat comprehensive
|
||||
# Require C++11
|
||||
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
cxx_std_11
|
||||
cxx_nullptr
|
||||
cxx_lambdas
|
||||
cxx_override
|
||||
cxx_defaulted_functions
|
||||
cxx_attribute_deprecated
|
||||
cxx_auto_type
|
||||
cxx_decltype
|
||||
cxx_deleted_functions
|
||||
cxx_range_for
|
||||
cxx_sizeof_member
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
@@ -269,11 +265,9 @@ endif()
|
||||
# and linkage information (doesn't find deps though).
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
EXPORT httplibTargets
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
)
|
||||
|
||||
install(FILES "${_httplib_build_includedir}/httplib.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
|
||||
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
||||
|
||||
@@ -48,17 +48,12 @@ res->status;
|
||||
res->body;
|
||||
```
|
||||
|
||||
### Try out the examples on Repl.it!
|
||||
|
||||
1. Run server at https://repl.it/@yhirose/cpp-httplib-server
|
||||
2. Run client at https://repl.it/@yhirose/cpp-httplib-client
|
||||
|
||||
SSL Support
|
||||
-----------
|
||||
|
||||
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
|
||||
|
||||
NOTE: cpp-httplib currently supports only version 1.1.1.
|
||||
NOTE: cpp-httplib currently supports only version 1.1.1 and 3.0.
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
@@ -183,6 +178,15 @@ The followings are built-in mappings:
|
||||
| webm | video/webm | zip | application/zip |
|
||||
| mp3 | audio/mp3 | wasm | application/wasm |
|
||||
|
||||
### File request handler
|
||||
|
||||
```cpp
|
||||
// The handler is called right before the response is sent to a client
|
||||
svr.set_file_request_handler([](const Request &req, Response &res) {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
NOTE: These static file server methods are not thread-safe.
|
||||
|
||||
### Logging
|
||||
@@ -208,15 +212,23 @@ svr.set_error_handler([](const auto& req, auto& res) {
|
||||
The exception handler gets called if a user routing handler throws an error.
|
||||
|
||||
```cpp
|
||||
svr.set_exception_handler([](const auto& req, auto& res, std::exception &e) {
|
||||
res.status = 500;
|
||||
svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep) {
|
||||
auto fmt = "<h1>Error 500</h1><p>%s</p>";
|
||||
char buf[BUFSIZ];
|
||||
snprintf(buf, sizeof(buf), fmt, e.what());
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (std::exception &e) {
|
||||
snprintf(buf, sizeof(buf), fmt, e.what());
|
||||
} catch (...) { // See the following NOTE
|
||||
snprintf(buf, sizeof(buf), fmt, "Unknown Exception");
|
||||
}
|
||||
res.set_content(buf, "text/html");
|
||||
res.status = 500;
|
||||
});
|
||||
```
|
||||
|
||||
NOTE: if you don't provide the `catch (...)` block for a rethrown exception pointer, an uncaught exception will end up causing the server crash. Be careful!
|
||||
|
||||
### Pre routing handler
|
||||
|
||||
```cpp
|
||||
@@ -431,7 +443,7 @@ int main(void)
|
||||
}
|
||||
} else {
|
||||
auto err = res.error();
|
||||
...
|
||||
std::cout << "HTTP error: " << httplib::to_string(err) << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -464,7 +476,9 @@ enum Error {
|
||||
SSLConnection,
|
||||
SSLLoadingCerts,
|
||||
SSLServerVerification,
|
||||
UnsupportedMultipartBoundaryChars
|
||||
UnsupportedMultipartBoundaryChars,
|
||||
Compression,
|
||||
ConnectionTimeout,
|
||||
};
|
||||
```
|
||||
|
||||
@@ -795,12 +809,12 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
|
||||
|
||||
Note: cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance.
|
||||
|
||||
Note: Windows 8 or lower and Cygwin on Windows are not supported.
|
||||
Note: Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin on Windows are not supported.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
MIT license (© 2021 Yuji Hirose)
|
||||
MIT license (© 2022 Yuji Hirose)
|
||||
|
||||
Special Thanks To
|
||||
-----------------
|
||||
|
||||
+8
-10
@@ -19,22 +19,18 @@ project(
|
||||
# Check just in case downstream decides to edit the source
|
||||
# and add a project version
|
||||
version = meson.project_version()
|
||||
python3 = find_program('python3')
|
||||
if version == 'undefined'
|
||||
# Meson doesn't have regular expressions, but since it is implemented
|
||||
# in python we can be sure we can use it to parse the file manually
|
||||
version = run_command(
|
||||
python3, '-c', 'import re; raw_version = re.search("User\-Agent.*cpp\-httplib/([0-9]+\.?)+", open("httplib.h").read()).group(0); print(re.search("([0-9]+\\.?)+", raw_version).group(0))',
|
||||
check: true
|
||||
).stdout().strip()
|
||||
cxx = meson.get_compiler('cpp')
|
||||
version = cxx.get_define('CPPHTTPLIB_VERSION',
|
||||
prefix: '#include <httplib.h>',
|
||||
include_directories: include_directories('.')).strip('"')
|
||||
assert(version != '', 'failed to get version from httplib.h')
|
||||
endif
|
||||
|
||||
message('cpp-httplib version ' + version)
|
||||
|
||||
deps = [dependency('threads')]
|
||||
args = []
|
||||
|
||||
openssl_dep = dependency('openssl', version: ['>=1.1.1', '<1.1.2'], required: get_option('cpp-httplib_openssl'))
|
||||
openssl_dep = dependency('openssl', version: '>=1.1.1', required: get_option('cpp-httplib_openssl'))
|
||||
if openssl_dep.found()
|
||||
deps += openssl_dep
|
||||
args += '-DCPPHTTPLIB_OPENSSL_SUPPORT'
|
||||
@@ -65,6 +61,8 @@ endif
|
||||
cpp_httplib_dep = dependency('', required: false)
|
||||
|
||||
if get_option('cpp-httplib_compile')
|
||||
python3 = find_program('python3')
|
||||
|
||||
httplib_ch = custom_target(
|
||||
'split',
|
||||
input: 'httplib.h',
|
||||
|
||||
+1
-1
@@ -5,5 +5,5 @@
|
||||
option('cpp-httplib_openssl', type: 'feature', value: 'auto', description: 'Enable OpenSSL support')
|
||||
option('cpp-httplib_zlib', type: 'feature', value: 'auto', description: 'Enable zlib support')
|
||||
option('cpp-httplib_brotli', type: 'feature', value: 'auto', description: 'Enable Brotli support')
|
||||
option('cpp-httplib_compile', type: 'boolean', value: false, description: 'Split the header into a compilable header & source file')
|
||||
option('cpp-httplib_compile', type: 'boolean', value: false, description: 'Split the header into a compilable header & source file (requires python3)')
|
||||
option('cpp-httplib_test', type: 'boolean', value: false, description: 'Build tests')
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
CXX = clang++
|
||||
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion # -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
|
||||
|
||||
PREFIX = /usr/local
|
||||
#PREFIX = $(shell brew --prefix)
|
||||
@@ -65,6 +65,8 @@ cert.pem:
|
||||
openssl req -x509 -new -batch -config test.rootCA.conf -key rootCA.key.pem -days 1024 > rootCA.cert.pem
|
||||
openssl genrsa 2048 > client.key.pem
|
||||
openssl req -new -batch -config test.conf -key client.key.pem | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client.cert.pem
|
||||
openssl genrsa -passout pass:test123! 2048 > key_encrypted.pem
|
||||
openssl req -new -batch -config test.conf -key key_encrypted.pem | openssl x509 -days 3650 -req -signkey key_encrypted.pem > cert_encrypted.pem
|
||||
#c_rehash .
|
||||
|
||||
clean:
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -33,6 +33,19 @@ cert2_pem = custom_target(
|
||||
command: [openssl, 'req', '-x509', '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
|
||||
)
|
||||
|
||||
key_encrypted_pem = custom_target(
|
||||
'key_encrypted_pem',
|
||||
output: 'key_encrypted.pem',
|
||||
command: [openssl, 'genrsa', '-passout', 'pass:test123!', '-out', '@OUTPUT@', '2048']
|
||||
)
|
||||
|
||||
cert_encrypted_pem = custom_target(
|
||||
'cert_encrypted_pem',
|
||||
input: key_encrypted_pem,
|
||||
output: 'cert_encrypted.pem',
|
||||
command: [openssl, 'req', '-x509', '-config', test_conf, '-key', '@INPUT@', '-sha256', '-days', '3650', '-nodes', '-out', '@OUTPUT@', '-extensions', 'SAN']
|
||||
)
|
||||
|
||||
rootca_key_pem = custom_target(
|
||||
'rootca_key_pem',
|
||||
output: 'rootCA.key.pem',
|
||||
@@ -87,6 +100,8 @@ test(
|
||||
key_pem,
|
||||
cert_pem,
|
||||
cert2_pem,
|
||||
key_encrypted_pem,
|
||||
cert_encrypted_pem,
|
||||
rootca_key_pem,
|
||||
rootca_cert_pem,
|
||||
client_key_pem,
|
||||
|
||||
+265
-15
@@ -18,6 +18,9 @@
|
||||
#define CLIENT_CA_CERT_DIR "."
|
||||
#define CLIENT_CERT_FILE "./client.cert.pem"
|
||||
#define CLIENT_PRIVATE_KEY_FILE "./client.key.pem"
|
||||
#define SERVER_ENCRYPTED_CERT_FILE "./cert_encrypted.pem"
|
||||
#define SERVER_ENCRYPTED_PRIVATE_KEY_FILE "./key_encrypted.pem"
|
||||
#define SERVER_ENCRYPTED_PRIVATE_KEY_PASS "test123!"
|
||||
|
||||
using namespace std;
|
||||
using namespace httplib;
|
||||
@@ -106,11 +109,11 @@ TEST(SplitTest, ParseQueryString) {
|
||||
detail::split(s.c_str(), s.c_str() + s.size(), '&',
|
||||
[&](const char *b, const char *e) {
|
||||
string key, val;
|
||||
detail::split(b, e, '=', [&](const char *b, const char *e) {
|
||||
detail::split(b, e, '=', [&](const char *b2, const char *e2) {
|
||||
if (key.empty()) {
|
||||
key.assign(b, e);
|
||||
key.assign(b2, e2);
|
||||
} else {
|
||||
val.assign(b, e);
|
||||
val.assign(b2, e2);
|
||||
}
|
||||
});
|
||||
dic.emplace(key, val);
|
||||
@@ -607,7 +610,8 @@ TEST(ConnectionErrorTest, InvalidPort) {
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(!res);
|
||||
EXPECT_EQ(Error::Connection, res.error());
|
||||
EXPECT_TRUE(Error::Connection == res.error() ||
|
||||
Error::ConnectionTimeout == res.error());
|
||||
}
|
||||
|
||||
TEST(ConnectionErrorTest, Timeout_Online) {
|
||||
@@ -1194,6 +1198,17 @@ TEST(BindServerTest, BindAndListenSeparatelySSL) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(BindServerTest, BindAndListenSeparatelySSLEncryptedKey) {
|
||||
SSLServer svr(SERVER_ENCRYPTED_CERT_FILE, SERVER_ENCRYPTED_PRIVATE_KEY_FILE,
|
||||
nullptr, nullptr, SERVER_ENCRYPTED_PRIVATE_KEY_PASS);
|
||||
int port = svr.bind_to_any_port("0.0.0.0");
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
ASSERT_TRUE(port > 0);
|
||||
svr.stop();
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(ErrorHandlerTest, ContentLength) {
|
||||
Server svr;
|
||||
|
||||
@@ -1234,8 +1249,11 @@ TEST(ExceptionHandlerTest, ContentLength) {
|
||||
Server svr;
|
||||
|
||||
svr.set_exception_handler([](const Request & /*req*/, Response &res,
|
||||
std::exception &e) {
|
||||
EXPECT_EQ("abc", std::string(e.what()));
|
||||
std::exception_ptr ep) {
|
||||
EXPECT_FALSE(ep == nullptr);
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (std::exception &e) { EXPECT_EQ("abc", std::string(e.what())); }
|
||||
res.status = 500;
|
||||
res.set_content("abcdefghijklmnopqrstuvwxyz",
|
||||
"text/html"); // <= Content-Length still 13 at this point
|
||||
@@ -1401,6 +1419,34 @@ TEST(InvalidFormatTest, StatusCode) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(URLFragmentTest, WithFragment) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [](const Request &req, Response & /*res*/) {
|
||||
EXPECT_TRUE(req.target == "/hi");
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Get("/hi#key1=val1=key2=val2");
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
|
||||
res = cli.Get("/hi%23key1=val1=key2=val2");
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_EQ(404, res->status);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
class ServerTest : public ::testing::Test {
|
||||
protected:
|
||||
ServerTest()
|
||||
@@ -1669,10 +1715,10 @@ protected:
|
||||
res.set_content("empty-no-content-type", "text/plain");
|
||||
})
|
||||
.Post("/post-large",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, LARGE_DATA);
|
||||
res.set_content(req.body, "text/plain");
|
||||
})
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, LARGE_DATA);
|
||||
res.set_content(req.body, "text/plain");
|
||||
})
|
||||
.Put("/empty-no-content-type",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "");
|
||||
@@ -2972,8 +3018,10 @@ TEST(GzipDecompressor, ChunkedDecompression) {
|
||||
httplib::detail::gzip_compressor compressor;
|
||||
bool result = compressor.compress(
|
||||
data.data(), data.size(),
|
||||
/*last=*/true, [&](const char *data, size_t size) {
|
||||
compressed_data.insert(compressed_data.size(), data, size);
|
||||
/*last=*/true,
|
||||
[&](const char *compressed_data_chunk, size_t compressed_data_size) {
|
||||
compressed_data.insert(compressed_data.size(), compressed_data_chunk,
|
||||
compressed_data_size);
|
||||
return true;
|
||||
});
|
||||
ASSERT_TRUE(result);
|
||||
@@ -2992,8 +3040,11 @@ TEST(GzipDecompressor, ChunkedDecompression) {
|
||||
std::min(compressed_data.size() - chunk_begin, chunk_size);
|
||||
bool result = decompressor.decompress(
|
||||
compressed_data.data() + chunk_begin, current_chunk_size,
|
||||
[&](const char *data, size_t size) {
|
||||
decompressed_data.insert(decompressed_data.size(), data, size);
|
||||
[&](const char *decompressed_data_chunk,
|
||||
size_t decompressed_data_chunk_size) {
|
||||
decompressed_data.insert(decompressed_data.size(),
|
||||
decompressed_data_chunk,
|
||||
decompressed_data_chunk_size);
|
||||
return true;
|
||||
});
|
||||
ASSERT_TRUE(result);
|
||||
@@ -4129,7 +4180,7 @@ protected:
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
t_ = thread([&]() { ASSERT_TRUE(svr_.listen(nullptr, PORT, AI_PASSIVE)); });
|
||||
t_ = thread([&]() { ASSERT_TRUE(svr_.listen(std::string(), PORT, AI_PASSIVE)); });
|
||||
|
||||
while (!svr_.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
@@ -4325,6 +4376,20 @@ TEST(SSLClientTest, WildcardHostNameMatch_Online) {
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST(SSLClientTest, SetInterfaceWithINET6) {
|
||||
auto cli = std::make_shared<httplib::Client>("https://httpbin.org");
|
||||
ASSERT_TRUE(cli != nullptr);
|
||||
|
||||
cli->set_address_family(AF_INET6);
|
||||
cli->set_interface("en0");
|
||||
|
||||
auto res = cli->Get("/get");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(SSLClientServerTest, ClientCertPresent) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
|
||||
CLIENT_CA_CERT_DIR);
|
||||
@@ -4603,6 +4668,50 @@ TEST(SSLClientServerTest, CustomizeServerSSLCtx) {
|
||||
|
||||
t.join();
|
||||
}
|
||||
|
||||
// Disabled due to the out-of-memory problem on GitHub Actions Workflows
|
||||
TEST(SSLClientServerTest, DISABLED_LargeDataTransfer) {
|
||||
|
||||
// prepare large data
|
||||
std::random_device seed_gen;
|
||||
std::mt19937 random(seed_gen());
|
||||
constexpr auto large_size_byte = 2147483648UL + 1048576UL; // 2GiB + 1MiB
|
||||
std::vector<std::uint32_t> binary(large_size_byte / sizeof(std::uint32_t));
|
||||
std::generate(binary.begin(), binary.end(), [&random]() { return random(); });
|
||||
|
||||
// server
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
|
||||
svr.Post("/binary", [&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(large_size_byte, req.body.size());
|
||||
EXPECT_EQ(0, std::memcmp(binary.data(), req.body.data(), large_size_byte));
|
||||
res.set_content(req.body, "application/octet-stream");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
// client POST
|
||||
SSLClient cli("localhost", PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
cli.set_read_timeout(std::chrono::seconds(100));
|
||||
cli.set_write_timeout(std::chrono::seconds(100));
|
||||
auto res = cli.Post("/binary", reinterpret_cast<char *>(binary.data()),
|
||||
large_size_byte, "application/octet-stream");
|
||||
|
||||
// compare
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(large_size_byte, res->body.size());
|
||||
EXPECT_EQ(0, std::memcmp(binary.data(), res->body.data(), large_size_byte));
|
||||
|
||||
// cleanup
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -4641,6 +4750,40 @@ TEST(SendAPI, SimpleInterface_Online) {
|
||||
EXPECT_EQ(301, res->status);
|
||||
}
|
||||
|
||||
// Disabled due to out-of-memory problem on GitHub Actions
|
||||
#ifdef _WIN64
|
||||
TEST(ServerLargeContentTest, DISABLED_SendLargeContent) {
|
||||
// allocate content size larger than 2GB in memory
|
||||
const size_t content_size = 2LL * 1024LL * 1024LL * 1024LL + 1LL;
|
||||
char *content = (char *)malloc(content_size);
|
||||
ASSERT_TRUE(content);
|
||||
|
||||
Server svr;
|
||||
svr.Get("/foo", [=](const httplib::Request &req, httplib::Response &resp) {
|
||||
resp.set_content(content, content_size, "application/octet-stream");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen(HOST, PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
auto res = cli.Get("/foo");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(content_size, res->body.length());
|
||||
|
||||
free(content);
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(YahooRedirectTest2, SimpleInterface_Online) {
|
||||
Client cli("http://yahoo.com");
|
||||
@@ -4776,4 +4919,111 @@ TEST(HttpToHttpsRedirectTest, CertFile) {
|
||||
t.join();
|
||||
t2.join();
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, LargeData) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
|
||||
svr.Post("/post", [&](const Request &req, Response & /*res*/,
|
||||
const ContentReader &content_reader) {
|
||||
if (req.is_multipart_form_data()) {
|
||||
MultipartFormDataItems files;
|
||||
content_reader(
|
||||
[&](const MultipartFormData &file) {
|
||||
files.push_back(file);
|
||||
return true;
|
||||
},
|
||||
[&](const char *data, size_t data_length) {
|
||||
files.back().content.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
EXPECT_TRUE(std::string(files[0].name) == "document");
|
||||
EXPECT_EQ(size_t(1024 * 1024 * 2), files[0].content.size());
|
||||
EXPECT_TRUE(files[0].filename == "2MB_data");
|
||||
EXPECT_TRUE(files[0].content_type == "application/octet-stream");
|
||||
|
||||
EXPECT_TRUE(files[1].name == "hello");
|
||||
EXPECT_TRUE(files[1].content == "world");
|
||||
EXPECT_TRUE(files[1].filename == "");
|
||||
EXPECT_TRUE(files[1].content_type == "");
|
||||
} else {
|
||||
std::string body;
|
||||
content_reader([&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
auto t = std::thread([&]() { svr.listen("localhost", 8080); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
std::string data(1024 * 1024 * 2, '.');
|
||||
std::stringstream buffer;
|
||||
buffer << data;
|
||||
|
||||
Client cli("https://localhost:8080");
|
||||
cli.enable_server_certificate_verification(false);
|
||||
|
||||
MultipartFormDataItems items{
|
||||
{"document", buffer.str(), "2MB_data", "application/octet-stream"},
|
||||
{"hello", "world", "", ""},
|
||||
};
|
||||
|
||||
auto res = cli.Post("/post", items);
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, WithPreamble) {
|
||||
Server svr;
|
||||
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
|
||||
thread t = thread([&] { svr.listen(HOST, PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
const std::string body =
|
||||
"This is the preamble. It is to be ignored, though it\r\n"
|
||||
"is a handy place for composition agents to include an\r\n"
|
||||
"explanatory note to non-MIME conformant readers.\r\n"
|
||||
"\r\n"
|
||||
"\r\n"
|
||||
"--simple boundary\r\n"
|
||||
"Content-Disposition: form-data; name=\"field1\"\r\n"
|
||||
"\r\n"
|
||||
"value1\r\n"
|
||||
"--simple boundary\r\n"
|
||||
"Content-Disposition: form-data; name=\"field2\"; "
|
||||
"filename=\"example.txt\"\r\n"
|
||||
"\r\n"
|
||||
"value2\r\n"
|
||||
"--simple boundary--\r\n"
|
||||
"This is the epilogue. It is also to be ignored.\r\n";
|
||||
|
||||
std::string content_type =
|
||||
R"(multipart/form-data; boundary="simple boundary")";
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
auto res = cli.Post("/post", body, content_type.c_str());
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
<AdditionalUsingDirectories>
|
||||
</AdditionalUsingDirectories>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
@@ -158,6 +159,7 @@
|
||||
<AdditionalUsingDirectories>
|
||||
</AdditionalUsingDirectories>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ TEST(RedirectTest, YouTubeNoSSLBasic) {
|
||||
RedirectProxyText(cli, "/", true);
|
||||
}
|
||||
|
||||
TEST(RedirectTest, YouTubeNoSSLDigest) {
|
||||
TEST(RedirectTest, DISABLED_YouTubeNoSSLDigest) {
|
||||
Client cli("youtube.com");
|
||||
RedirectProxyText(cli, "/", false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user