mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0b461a3b7 | |||
| 74fe5a5029 | |||
| 9d0a9d4e23 | |||
| 5758769ad3 | |||
| e7eadc3605 | |||
| 07c6e58951 | |||
| 87994811a1 | |||
| 42feb7e8be | |||
| 26196b70af | |||
| 93a51979c4 | |||
| ad7edc7b27 | |||
| 27cd4e6ffe | |||
| cae5a8be1c | |||
| 8e10d4e8e7 | |||
| b57f79f438 | |||
| a9cf097951 | |||
| 5c3624e1af | |||
| cba9ef8c0b | |||
| 4f8407a3a7 | |||
| 656e936f49 | |||
| d92c314466 | |||
| b747fb111d | |||
| 7e0a0b2d0c | |||
| 362d064afa | |||
| 1bd88de2e5 | |||
| 0b541ffebc | |||
| 106be19c3e | |||
| 25d72bf881 | |||
| 9d5b5297cc | |||
| 462884bebb | |||
| b1cc24b795 | |||
| f0eb55b327 | |||
| 6dc285b5ca | |||
| 07e614eef7 | |||
| 916b2a8fd3 | |||
| 869f5bb279 | |||
| 3e21338f82 | |||
| 37bb3c6a77 | |||
| d4ab2fa0e6 | |||
| 72d3f4896a | |||
| 5e6f973b99 | |||
| 7ed77b02ad | |||
| 127a64d5a0 | |||
| caa31aafda | |||
| dae318495f | |||
| 305a7abcb9 |
+6
-13
@@ -176,7 +176,7 @@ if(HTTPLIB_COMPILE)
|
||||
set_target_properties(${PROJECT_NAME}
|
||||
PROPERTIES
|
||||
VERSION ${${PROJECT_NAME}_VERSION}
|
||||
SOVERSION ${${PROJECT_NAME}_VERSION_MAJOR}
|
||||
SOVERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}"
|
||||
)
|
||||
else()
|
||||
# This is for header-only.
|
||||
@@ -193,7 +193,7 @@ target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
cxx_std_11
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
target_include_directories(${PROJECT_NAME} SYSTEM ${_INTERFACE_OR_PUBLIC}
|
||||
$<BUILD_INTERFACE:${_httplib_build_includedir}>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
)
|
||||
@@ -221,15 +221,8 @@ target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
|
||||
)
|
||||
|
||||
# Cmake's find_package search path is different based on the system
|
||||
# See https://cmake.org/cmake/help/latest/command/find_package.html for the list
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_PREFIX}/cmake/${PROJECT_NAME}")
|
||||
else()
|
||||
# On Non-Windows, it should be /usr/lib/cmake/<name>/<name>Config.cmake
|
||||
# NOTE: This may or may not work for macOS...
|
||||
set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
|
||||
endif()
|
||||
# CMake configuration files installation directory
|
||||
set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
@@ -247,13 +240,13 @@ if(HTTPLIB_COMPILE)
|
||||
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
|
||||
# Example: if you find_package(httplib 0.5.4)
|
||||
# then anything >= 0.5 and <= 1.0 is accepted
|
||||
COMPATIBILITY SameMajorVersion
|
||||
COMPATIBILITY SameMinorVersion
|
||||
)
|
||||
else()
|
||||
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
|
||||
# Example: if you find_package(httplib 0.5.4)
|
||||
# then anything >= 0.5 and <= 1.0 is accepted
|
||||
COMPATIBILITY SameMajorVersion
|
||||
COMPATIBILITY SameMinorVersion
|
||||
# Tells Cmake that it's a header-only lib
|
||||
# Mildly useful for end-users :)
|
||||
ARCH_INDEPENDENT
|
||||
|
||||
@@ -7,12 +7,12 @@ A C++11 single-file header-only cross platform HTTP/HTTPS library.
|
||||
|
||||
It's extremely easy to setup. Just include the **httplib.h** file in your code!
|
||||
|
||||
NOTE: This is a multi-threaded 'blocking' HTTP library. If you are looking for a 'non-blocking' library, this is not the one that you want.
|
||||
NOTE: This library uses 'blocking' socket I/O. If you are looking for a library with 'non-blocking' socket I/O, this is not the one that you want.
|
||||
|
||||
Simple examples
|
||||
---------------
|
||||
|
||||
#### Server
|
||||
#### Server (Multi-threaded)
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
@@ -65,6 +65,7 @@ httplib::SSLServer svr("./cert.pem", "./key.pem");
|
||||
// Client
|
||||
httplib::Client cli("https://localhost:1234"); // scheme + host
|
||||
httplib::SSLClient cli("localhost:1234"); // host
|
||||
httplib::SSLClient cli("localhost", 1234); // host, port
|
||||
|
||||
// Use your CA bundle
|
||||
cli.set_ca_cert_path("./ca-bundle.crt");
|
||||
@@ -212,15 +213,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
|
||||
@@ -435,7 +444,7 @@ int main(void)
|
||||
}
|
||||
} else {
|
||||
auto err = res.error();
|
||||
...
|
||||
std::cout << "HTTP error: " << httplib::to_string(err) << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -468,7 +477,9 @@ enum Error {
|
||||
SSLConnection,
|
||||
SSLLoadingCerts,
|
||||
SSLServerVerification,
|
||||
UnsupportedMultipartBoundaryChars
|
||||
UnsupportedMultipartBoundaryChars,
|
||||
Compression,
|
||||
ConnectionTimeout,
|
||||
};
|
||||
```
|
||||
|
||||
@@ -799,12 +810,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
|
||||
-----------------
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#/usr/bin/env bash
|
||||
for i in {1..10000}
|
||||
for i in {1..1000000}
|
||||
do
|
||||
echo "#### $i ####"
|
||||
curl -X POST -F image_file=@$1 http://localhost:1234/post > /dev/null
|
||||
|
||||
@@ -77,6 +77,7 @@ if get_option('cpp-httplib_compile')
|
||||
dependencies: deps,
|
||||
cpp_args: args,
|
||||
version: version,
|
||||
soversion: version.split('.')[0] + '.' + version.split('.')[1],
|
||||
install: true
|
||||
)
|
||||
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, link_with: lib, sources: httplib_ch[1])
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+541
-15
@@ -1249,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
|
||||
@@ -1640,7 +1643,8 @@ protected:
|
||||
res.set_content_provider(
|
||||
size_t(-1), "text/plain",
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
if (!sink.is_writable()) return false;
|
||||
|
||||
sink.os << "data_chunk";
|
||||
return true;
|
||||
});
|
||||
@@ -1711,6 +1715,22 @@ protected:
|
||||
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
||||
res.set_content("empty-no-content-type", "text/plain");
|
||||
})
|
||||
.Post("/path-only",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "");
|
||||
EXPECT_EQ("", req.get_header_value("Content-Type"));
|
||||
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
||||
res.set_content("path-only", "text/plain");
|
||||
})
|
||||
.Post("/path-headers-only",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "");
|
||||
EXPECT_EQ("", req.get_header_value("Content-Type"));
|
||||
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
||||
EXPECT_EQ("world", req.get_header_value("hello"));
|
||||
EXPECT_EQ("world2", req.get_header_value("hello2"));
|
||||
res.set_content("path-headers-only", "text/plain");
|
||||
})
|
||||
.Post("/post-large",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, LARGE_DATA);
|
||||
@@ -2121,6 +2141,21 @@ TEST_F(ServerTest, PostEmptyContentWithNoContentType) {
|
||||
ASSERT_EQ("empty-no-content-type", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostPathOnly) {
|
||||
auto res = cli_.Post("/path-only");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ("path-only", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostPathAndHeadersOnly) {
|
||||
auto res = cli_.Post("/path-headers-only",
|
||||
Headers({{"hello", "world"}, {"hello2", "world2"}}));
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ("path-headers-only", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostLarge) {
|
||||
auto res = cli_.Post("/post-large", LARGE_DATA, "text/plain");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -3015,8 +3050,10 @@ TEST(GzipDecompressor, ChunkedDecompression) {
|
||||
httplib::detail::gzip_compressor compressor;
|
||||
bool result = compressor.compress(
|
||||
data.data(), 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);
|
||||
/*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);
|
||||
@@ -3035,8 +3072,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 *decompressed_data_chunk, size_t decompressed_data_chunk_size) {
|
||||
decompressed_data.insert(decompressed_data.size(), decompressed_data_chunk, decompressed_data_chunk_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);
|
||||
@@ -3799,9 +3839,12 @@ TEST(MountTest, Unmount) {
|
||||
TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response & /*res*/) {
|
||||
svr.Get("/exception", [&](const Request & /*req*/, Response & /*res*/) {
|
||||
throw std::runtime_error("exception...");
|
||||
// res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.Get("/unknown", [&](const Request & /*req*/, Response & /*res*/) {
|
||||
throw std::runtime_error("exception\r\n...");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
@@ -3814,11 +3857,21 @@ TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(500, res->status);
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
{
|
||||
auto res = cli.Get("/exception");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(500, res->status);
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/unknown");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(500, res->status);
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception\\r\\n...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
@@ -4172,7 +4225,8 @@ 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));
|
||||
@@ -4742,6 +4796,43 @@ TEST(SendAPI, SimpleInterface_Online) {
|
||||
EXPECT_EQ(301, res->status);
|
||||
}
|
||||
|
||||
TEST(ClientImplMethods, GetSocketTest) {
|
||||
httplib::Server svr;
|
||||
svr.Get("/", [&](const httplib::Request & /*req*/, httplib::Response &res) {
|
||||
res.status = 200;
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen("127.0.0.1", 3333); });
|
||||
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
|
||||
{
|
||||
httplib::Client cli("http://127.0.0.1:3333");
|
||||
cli.set_keep_alive(true);
|
||||
|
||||
// Use the behavior of cpp-httplib of opening the connection
|
||||
// only when the first request happens. If that changes,
|
||||
// this test would be obsolete.
|
||||
|
||||
EXPECT_EQ(cli.socket(), INVALID_SOCKET);
|
||||
|
||||
// This also implicitly tests the server. But other tests would fail much
|
||||
// earlier than this one to be considered.
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
|
||||
EXPECT_EQ(200, res->status);
|
||||
ASSERT_TRUE(cli.socket() != INVALID_SOCKET);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
// Disabled due to out-of-memory problem on GitHub Actions
|
||||
#ifdef _WIN64
|
||||
TEST(ServerLargeContentTest, DISABLED_SendLargeContent) {
|
||||
@@ -4974,5 +5065,440 @@ TEST(MultipartFormDataTest, LargeData) {
|
||||
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();
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, PostCustomBoundary) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
|
||||
svr.Post("/post_customboundary", [&](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_customboundary", {}, items, "abc-abc");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, PostInvalidBoundaryChars) {
|
||||
|
||||
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");
|
||||
|
||||
MultipartFormDataItems items{
|
||||
{"document", buffer.str(), "2MB_data", "application/octet-stream"},
|
||||
{"hello", "world", "", ""},
|
||||
};
|
||||
|
||||
for (const char &c : " \t\r\n") {
|
||||
auto res =
|
||||
cli.Post("/invalid_boundary", {}, items, string("abc123").append(1, c));
|
||||
ASSERT_EQ(Error::UnsupportedMultipartBoundaryChars, res.error());
|
||||
ASSERT_FALSE(res);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, PutFormData) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
|
||||
svr.Put("/put", [&](const Request &req, const 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.Put("/put", items);
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, PutFormDataCustomBoundary) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
|
||||
svr.Put("/put_customboundary",
|
||||
[&](const Request &req, const 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.Put("/put_customboundary", {}, items, "abc-abc_");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, PutInvalidBoundaryChars) {
|
||||
|
||||
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", "", ""},
|
||||
};
|
||||
|
||||
for (const char &c : " \t\r\n") {
|
||||
auto res = cli.Put("/put", {}, items, string("abc123").append(1, c));
|
||||
ASSERT_EQ(Error::UnsupportedMultipartBoundaryChars, res.error());
|
||||
ASSERT_FALSE(res);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
class UnixSocketTest : public ::testing::Test {
|
||||
protected:
|
||||
void TearDown() override { std::remove(pathname_.c_str()); }
|
||||
|
||||
void client_GET(const std::string &addr) {
|
||||
httplib::Client cli{addr};
|
||||
cli.set_address_family(AF_UNIX);
|
||||
ASSERT_TRUE(cli.is_valid());
|
||||
|
||||
const auto &result = cli.Get(pattern_);
|
||||
ASSERT_TRUE(result) << "error: " << result.error();
|
||||
|
||||
const auto &resp = result.value();
|
||||
EXPECT_EQ(resp.status, 200);
|
||||
EXPECT_EQ(resp.body, content_);
|
||||
}
|
||||
|
||||
const std::string pathname_{"./httplib-server.sock"};
|
||||
const std::string pattern_{"/hi"};
|
||||
const std::string content_{"Hello World!"};
|
||||
};
|
||||
|
||||
TEST_F(UnixSocketTest, pathname) {
|
||||
httplib::Server svr;
|
||||
svr.Get(pattern_, [&](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content(content_, "text/plain");
|
||||
});
|
||||
|
||||
std::thread t{[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80));
|
||||
}};
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(pathname_);
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
#if defined(__linux__) || \
|
||||
/* __APPLE__ */ (defined(SOL_LOCAL) && defined(SO_PEERPID))
|
||||
TEST_F(UnixSocketTest, PeerPid) {
|
||||
httplib::Server svr;
|
||||
std::string remote_port_val;
|
||||
svr.Get(pattern_, [&](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_content(content_, "text/plain");
|
||||
remote_port_val = req.get_header_value("REMOTE_PORT");
|
||||
});
|
||||
|
||||
std::thread t{[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80));
|
||||
}};
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(pathname_);
|
||||
EXPECT_EQ(std::to_string(getpid()), remote_port_val);
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
TEST_F(UnixSocketTest, abstract) {
|
||||
constexpr char svr_path[]{"\x00httplib-server.sock"};
|
||||
const std::string abstract_addr{svr_path, sizeof(svr_path) - 1};
|
||||
|
||||
httplib::Server svr;
|
||||
svr.Get(pattern_, [&](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content(content_, "text/plain");
|
||||
});
|
||||
|
||||
std::thread t{[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(abstract_addr, 80));
|
||||
}};
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(abstract_addr);
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(SocketStream, is_writable_UNIX) {
|
||||
int fds[2];
|
||||
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
|
||||
|
||||
const auto asSocketStream = [&](socket_t fd,
|
||||
std::function<bool(Stream &)> func) {
|
||||
return detail::process_client_socket(fd, 0, 0, 0, 0, func);
|
||||
};
|
||||
asSocketStream(fds[0], [&](Stream &s0) {
|
||||
EXPECT_EQ(s0.socket(), fds[0]);
|
||||
EXPECT_TRUE(s0.is_writable());
|
||||
|
||||
EXPECT_EQ(0, close(fds[1]));
|
||||
EXPECT_FALSE(s0.is_writable());
|
||||
|
||||
return true;
|
||||
});
|
||||
EXPECT_EQ(0, close(fds[0]));
|
||||
}
|
||||
|
||||
TEST(SocketStream, is_writable_INET) {
|
||||
sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(PORT + 1);
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
|
||||
int disconnected_svr_sock = -1;
|
||||
std::thread svr{[&] {
|
||||
const int s = socket(AF_INET, SOCK_STREAM, 0);
|
||||
ASSERT_LE(0, s);
|
||||
ASSERT_EQ(0, ::bind(s, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
|
||||
ASSERT_EQ(0, listen(s, 1));
|
||||
ASSERT_LE(0, disconnected_svr_sock = accept(s, nullptr, nullptr));
|
||||
ASSERT_EQ(0, close(s));
|
||||
}};
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
std::thread cli{[&] {
|
||||
const int s = socket(AF_INET, SOCK_STREAM, 0);
|
||||
ASSERT_LE(0, s);
|
||||
ASSERT_EQ(0, connect(s, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
|
||||
ASSERT_EQ(0, close(s));
|
||||
}};
|
||||
cli.join();
|
||||
svr.join();
|
||||
ASSERT_NE(disconnected_svr_sock, -1);
|
||||
|
||||
const auto asSocketStream = [&](socket_t fd,
|
||||
std::function<bool(Stream &)> func) {
|
||||
return detail::process_client_socket(fd, 0, 0, 0, 0, func);
|
||||
};
|
||||
asSocketStream(disconnected_svr_sock, [&](Stream &ss) {
|
||||
EXPECT_EQ(ss.socket(), disconnected_svr_sock);
|
||||
EXPECT_FALSE(ss.is_writable());
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_EQ(0, close(disconnected_svr_sock));
|
||||
}
|
||||
#endif // #ifndef _WIN32
|
||||
|
||||
+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