Compare commits

...

28 Commits

Author SHA1 Message Date
yhirose 4e6ded1f36 Release v0.12.0 2023-02-07 10:27:40 -05:00
yhirose d663588491 Removed is_writable() from DataSink (Resolve #1478, too) (#1483) 2023-02-04 13:53:42 -05:00
yhirose 439caf5b79 Fix #1479 2023-02-01 19:11:11 -05:00
yhirose c4ba43ca6f Removed incorrect comment 2023-01-24 09:07:42 -05:00
Jiwoo Park 20cba2ecd9 Support CTest (#1468)
* Add test/CMakeLists.txt to enable testing with CTest

Add HTTPLIB_TEST option
Downlaod GoogleTest source using FetchContent module
Generate cert files to test httplib with OpenSSL

* Generate cert2.pem with a new certificate request

* Use the latest GoogleTest library
2023-01-21 03:43:22 -05:00
yhirose 0ff2e16d69 Issue 52666: cpp-httplib:server_fuzzer: Timeout in server_fuzzer 2023-01-21 01:09:19 -05:00
Ray Beck 51607ec752 add to_human_string (#1467)
* add to_human_string

* replace to_string with to_human_string

* fix test
2023-01-19 07:01:34 -05:00
yhirose 7992b14896 Release v0.11.4 2023-01-09 17:08:11 -05:00
Ray Beck 7e420aeed3 add support for requests with both MultipartFormDataItems and Content Providers (#1454)
* add support for requests with both MultipartFormDataItems and ContentProviders

* rework implementation

* use const auto & and fix offset calculation

* fix zero items

* snake case variables

* clang-format

* commonize get_multipart_content_provider, add Put() with MultipartFormDataProviderItems

* fix linker multiple definition error

* add test MultipartFormDataTest.DataProviderItems
2023-01-08 18:38:14 -05:00
yukun 227d2c2050 Add EINTR and EAGAIN judge for accept (#1438)
* Add EINTR and EAGAIN judge for accept

* Add EINTR signal tests

* Cancel win32 and win64 compile on signal unittest

Co-authored-by: yukun.yu <yukun.yu@alibaba-inc.com>
2022-12-20 19:34:51 -05:00
yhirose 93e53c91f7 Updated unit test 2022-12-10 11:45:56 -05:00
Jiwoo Park 58cffd3223 std::condition_variable::notify_one/all() should be called after unlocking mutex (#1448)
* Move next job in task queue rather than copy

* Notify waiting thread after unlocking mutex

* Add unit test for TaskQueue

* Don't use C++14 feature in test code
2022-12-09 17:37:48 -05:00
Ingo Bauersachs 8f32271e8c Support LOCAL_ADDR and LOCAL_PORT header in client Request (#1450)
Having the local address/port is useful if the server is bound to
all interfaces, e.g. to serve different content for developers
on localhost only.
2022-12-06 08:23:09 -05:00
yhirose c8c1c3d376 Fix #1442 2022-12-01 17:21:40 -05:00
yhirose 9f512acb42 Removed code for upsupported OpenSSL 2022-12-01 13:41:18 -05:00
yhirose c0b461a3b7 Release v0.11.3 2022-11-29 19:26:10 -05:00
yhirose 74fe5a5029 Fix #1426 2022-11-27 10:53:11 -05:00
yhirose 9d0a9d4e23 Fix #1437 2022-11-27 10:21:24 -05:00
TheMarpe 5758769ad3 Windows CMake directory install fix (#1434)
* Fixed install convention pre 3.25

* Simplified CMAKEDIR installation directory
2022-11-24 22:51:55 -05:00
Ray Beck e7eadc3605 add SYSTEM to include to prevent warnings (#1428) 2022-11-24 22:23:11 -05:00
yhirose 07c6e58951 Fix #1421 2022-11-15 11:57:14 -05:00
Pavel Artemkin 87994811a1 undef poll at the end if CPPHTTPLIB_USE_POLL (#1427)
* undef poll at the end if CPPHTTPLIB_USE_POLL

* win32 only
2022-11-13 21:49:49 -05:00
yhirose 42feb7e8be Updated README 2022-11-10 08:27:30 -05:00
yhirose 26196b70af Add test case for 'Issue 52666 in oss-fuzz: cpp-httplib:server_fuzzer: Timeout in server_fuzzer' 2022-11-03 13:07:04 -04:00
Changbin Park 93a51979c4 Get client process id over ip/port when server runs on UNIX socket. (#1418)
* handle socket options for UNIX socket same as others

 * set FD_CLOEXEC by default
 * invoke `socket_options` callback if set

* Offer Client info even on UNIX socket based Server

HTTP Request header "REMOTE_PORT" contains client process id if possible
when Server works on UNIX socket.

* retrigger checks

* retrigger checks

* add support macOS

Co-authored-by: Changbin Park <changbin.park@ahnlab.com>
2022-11-03 10:25:18 -04:00
Changbin Park ad7edc7b27 avoid lockup in ThreadPool::shutdown() on legacy host (#1417)
It is found on CentOS 7.0.1406 which is very early version of CentOS 7.

Co-authored-by: Changbin Park <changbin.park@ahnlab.com>
2022-11-03 10:23:45 -04:00
Andreas Kempf 27cd4e6ffe Support compilation on AIX (#1402)
Disable the ifaddrs.h include statement and the USE_IF2IP flag on AIX.
AIX is a Unix OS but does not offer the ifaddrs.h header.
2022-10-14 10:49:48 -04:00
yhirose cae5a8be1c Added more fuzzing corpus 2022-09-24 08:31:21 -04:00
9 changed files with 893 additions and 228 deletions
+10 -11
View File
@@ -8,6 +8,7 @@
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
* HTTPLIB_REQUIRE_BROTLI (default off)
* HTTPLIB_COMPILE (default off)
* HTTPLIB_TEST (default off)
* BROTLI_USE_STATIC_LIBS - tells Cmake to use the static Brotli libs (only works if you have them installed).
* OPENSSL_USE_STATIC_LIBS - tells Cmake to use the static OpenSSL libs (only works if you have them installed).
@@ -88,7 +89,7 @@ option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a c
if(HTTPLIB_COMPILE)
set(HTTPLIB_IS_COMPILED TRUE)
endif()
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_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON)
# Defaults to static library
@@ -193,7 +194,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 +222,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)
@@ -285,3 +279,8 @@ install(EXPORT httplibTargets
NAMESPACE ${PROJECT_NAME}::
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
)
if(HTTPLIB_TEST)
include(CTest)
add_subdirectory(test)
endif()
+5
View File
@@ -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");
@@ -492,6 +493,10 @@ auto res = cli.Get("/hi", headers);
```
or
```c++
auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}});
```
or
```c++
cli.set_default_headers({
{ "Accept-Encoding", "gzip, deflate" }
});
+1 -1
View File
@@ -19,7 +19,7 @@ public:
unique_lock<mutex> lk(m_);
int id = id_;
cv_.wait(lk, [&] { return cid_ == id; });
if (sink->is_writable()) { sink->write(message_.data(), message_.size()); }
sink->write(message_.data(), message_.size());
}
void send_event(const string &message) {
+292 -126
View File
@@ -8,7 +8,7 @@
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION "0.11.2"
#define CPPHTTPLIB_VERSION "0.12.0"
/*
* Configuration
@@ -172,7 +172,9 @@ using socket_t = SOCKET;
#else // not _WIN32
#include <arpa/inet.h>
#ifndef _AIX
#include <ifaddrs.h>
#endif
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
@@ -253,14 +255,10 @@ using socket_t = int;
#if OPENSSL_VERSION_NUMBER < 0x1010100fL
#error Sorry, OpenSSL versions prior to 1.1.1 are not supported
#elif OPENSSL_VERSION_NUMBER < 0x30000000L
#define SSL_get1_peer_certificate SSL_get_peer_certificate
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#include <openssl/crypto.h>
inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
return M_ASN1_STRING_data(asn1);
}
#endif
#endif
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
@@ -342,7 +340,6 @@ public:
std::function<bool(const char *data, size_t data_len)> write;
std::function<void()> done;
std::function<bool()> is_writable;
std::ostream os;
private:
@@ -371,6 +368,14 @@ using ContentProviderWithoutLength =
using ContentProviderResourceReleaser = std::function<void(bool success)>;
struct MultipartFormDataProvider {
std::string name;
ContentProviderWithoutLength provider;
std::string filename;
std::string content_type;
};
using MultipartFormDataProviderItems = std::vector<MultipartFormDataProvider>;
using ContentReceiverWithProgress =
std::function<bool(const char *data, size_t data_length, uint64_t offset,
uint64_t total_length)>;
@@ -415,6 +420,8 @@ struct Request {
std::string remote_addr;
int remote_port = -1;
std::string local_addr;
int local_port = -1;
// for server
std::string version;
@@ -516,6 +523,7 @@ public:
virtual ssize_t read(char *ptr, size_t size) = 0;
virtual ssize_t write(const char *ptr, size_t size) = 0;
virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0;
virtual socket_t socket() const = 0;
template <typename... Args>
@@ -548,8 +556,11 @@ public:
~ThreadPool() override = default;
void enqueue(std::function<void()> fn) override {
std::unique_lock<std::mutex> lock(mutex_);
jobs_.push_back(std::move(fn));
{
std::unique_lock<std::mutex> lock(mutex_);
jobs_.push_back(std::move(fn));
}
cond_.notify_one();
}
@@ -583,7 +594,7 @@ private:
if (pool_.shutdown_ && pool_.jobs_.empty()) { break; }
fn = pool_.jobs_.front();
fn = std::move(pool_.jobs_.front());
pool_.jobs_.pop_front();
}
@@ -901,6 +912,7 @@ public:
Result Head(const std::string &path, const Headers &headers);
Result Post(const std::string &path);
Result Post(const std::string &path, const Headers &headers);
Result Post(const std::string &path, const char *body, size_t content_length,
const std::string &content_type);
Result Post(const std::string &path, const Headers &headers, const char *body,
@@ -929,6 +941,9 @@ public:
const MultipartFormDataItems &items);
Result Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items, const std::string &boundary);
Result Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items);
Result Put(const std::string &path);
Result Put(const std::string &path, const char *body, size_t content_length,
@@ -958,6 +973,9 @@ public:
const MultipartFormDataItems &items);
Result Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items, const std::string &boundary);
Result Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items);
Result Patch(const std::string &path);
Result Patch(const std::string &path, const char *body, size_t content_length,
@@ -1196,6 +1214,9 @@ private:
ContentProvider content_provider,
ContentProviderWithoutLength content_provider_without_length,
const std::string &content_type);
ContentProviderWithoutLength get_multipart_content_provider(
const std::string &boundary, const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items);
std::string adjust_host_string(const std::string &host) const;
@@ -1262,6 +1283,7 @@ public:
Result Head(const std::string &path, const Headers &headers);
Result Post(const std::string &path);
Result Post(const std::string &path, const Headers &headers);
Result Post(const std::string &path, const char *body, size_t content_length,
const std::string &content_type);
Result Post(const std::string &path, const Headers &headers, const char *body,
@@ -1290,6 +1312,10 @@ public:
const MultipartFormDataItems &items);
Result Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items, const std::string &boundary);
Result Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items);
Result Put(const std::string &path);
Result Put(const std::string &path, const char *body, size_t content_length,
const std::string &content_type);
@@ -1318,6 +1344,10 @@ public:
const MultipartFormDataItems &items);
Result Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items, const std::string &boundary);
Result Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items);
Result Patch(const std::string &path);
Result Patch(const std::string &path, const char *body, size_t content_length,
const std::string &content_type);
@@ -1533,7 +1563,7 @@ inline void duration_to_sec_and_usec(const T &duration, U callback) {
auto usec = std::chrono::duration_cast<std::chrono::microseconds>(
duration - std::chrono::seconds(sec))
.count();
callback(sec, usec);
callback(static_cast<time_t>(sec), static_cast<time_t>(usec));
}
template <typename T>
@@ -1634,20 +1664,20 @@ Server::set_idle_interval(const std::chrono::duration<Rep, Period> &duration) {
inline std::string to_string(const Error error) {
switch (error) {
case Error::Success: return "Success";
case Error::Connection: return "Connection";
case Error::BindIPAddress: return "BindIPAddress";
case Error::Read: return "Read";
case Error::Write: return "Write";
case Error::ExceedRedirectCount: return "ExceedRedirectCount";
case Error::Canceled: return "Canceled";
case Error::SSLConnection: return "SSLConnection";
case Error::SSLLoadingCerts: return "SSLLoadingCerts";
case Error::SSLServerVerification: return "SSLServerVerification";
case Error::Success: return "Success (no error)";
case Error::Connection: return "Could not establish connection";
case Error::BindIPAddress: return "Failed to bind IP address";
case Error::Read: return "Failed to read connection";
case Error::Write: return "Failed to write connection";
case Error::ExceedRedirectCount: return "Maximum redirect count exceeded";
case Error::Canceled: return "Connection handling canceled";
case Error::SSLConnection: return "SSL connection failed";
case Error::SSLLoadingCerts: return "SSL certificate loading failed";
case Error::SSLServerVerification: return "SSL server verification failed";
case Error::UnsupportedMultipartBoundaryChars:
return "UnsupportedMultipartBoundaryChars";
case Error::Compression: return "Compression";
case Error::ConnectionTimeout: return "ConnectionTimeout";
return "Unsupported HTTP multipart boundary characters";
case Error::Compression: return "Compression failed";
case Error::ConnectionTimeout: return "Connection timed out";
case Error::Unknown: return "Unknown";
default: break;
}
@@ -1779,6 +1809,7 @@ public:
ssize_t read(char *ptr, size_t size) override;
ssize_t write(const char *ptr, size_t size) override;
void get_remote_ip_and_port(std::string &ip, int &port) const override;
void get_local_ip_and_port(std::string &ip, int &port) const override;
socket_t socket() const override;
const std::string &get_buffer() const;
@@ -2447,6 +2478,7 @@ public:
ssize_t read(char *ptr, size_t size) override;
ssize_t write(const char *ptr, size_t size) override;
void get_remote_ip_and_port(std::string &ip, int &port) const override;
void get_local_ip_and_port(std::string &ip, int &port) const override;
socket_t socket() const override;
private:
@@ -2476,6 +2508,7 @@ public:
ssize_t read(char *ptr, size_t size) override;
ssize_t write(const char *ptr, size_t size) override;
void get_remote_ip_and_port(std::string &ip, int &port) const override;
void get_local_ip_and_port(std::string &ip, int &port) const override;
socket_t socket() const override;
private:
@@ -2600,6 +2633,9 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
hints.ai_addrlen = static_cast<socklen_t>(
sizeof(addr) - sizeof(addr.sun_path) + addrlen);
fcntl(sock, F_SETFD, FD_CLOEXEC);
if (socket_options) { socket_options(sock); }
if (!bind_or_connect(sock, hints)) {
close_socket(sock);
sock = INVALID_SOCKET;
@@ -2720,7 +2756,7 @@ inline bool bind_ip_address(socket_t sock, const std::string &host) {
return ret;
}
#if !defined _WIN32 && !defined ANDROID
#if !defined _WIN32 && !defined ANDROID && !defined _AIX
#define USE_IF2IP
#endif
@@ -2841,9 +2877,8 @@ inline socket_t create_client_socket(
return sock;
}
inline bool get_remote_ip_and_port(const struct sockaddr_storage &addr,
socklen_t addr_len, std::string &ip,
int &port) {
inline bool get_ip_and_port(const struct sockaddr_storage &addr,
socklen_t addr_len, std::string &ip, int &port) {
if (addr.ss_family == AF_INET) {
port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
} else if (addr.ss_family == AF_INET6) {
@@ -2864,13 +2899,40 @@ inline bool get_remote_ip_and_port(const struct sockaddr_storage &addr,
return true;
}
inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) {
struct sockaddr_storage addr;
socklen_t addr_len = sizeof(addr);
if (!getsockname(sock, reinterpret_cast<struct sockaddr *>(&addr),
&addr_len)) {
get_ip_and_port(addr, addr_len, ip, port);
}
}
inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
struct sockaddr_storage addr;
socklen_t addr_len = sizeof(addr);
if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
&addr_len)) {
get_remote_ip_and_port(addr, addr_len, ip, port);
#ifndef _WIN32
if (addr.ss_family == AF_UNIX) {
#if defined(__linux__)
struct ucred ucred;
socklen_t len = sizeof(ucred);
if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) {
port = ucred.pid;
}
#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__
pid_t pid;
socklen_t len = sizeof(pid);
if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) {
port = pid;
}
#endif
return;
}
#endif
get_ip_and_port(addr, addr_len, ip, port);
}
}
@@ -3569,7 +3631,7 @@ inline bool write_content(Stream &strm, const ContentProvider &content_provider,
data_sink.write = [&](const char *d, size_t l) -> bool {
if (ok) {
if (write_data(strm, d, l)) {
if (strm.is_writable() && write_data(strm, d, l)) {
offset += l;
} else {
ok = false;
@@ -3578,14 +3640,14 @@ inline bool write_content(Stream &strm, const ContentProvider &content_provider,
return ok;
};
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
while (offset < end_offset && !is_shutting_down()) {
if (!content_provider(offset, end_offset - offset, data_sink)) {
if (!strm.is_writable()) {
error = Error::Write;
return false;
} else if (!content_provider(offset, end_offset - offset, data_sink)) {
error = Error::Canceled;
return false;
}
if (!ok) {
} else if (!ok) {
error = Error::Write;
return false;
}
@@ -3617,18 +3679,21 @@ write_content_without_length(Stream &strm,
data_sink.write = [&](const char *d, size_t l) -> bool {
if (ok) {
offset += l;
if (!write_data(strm, d, l)) { ok = false; }
if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; }
}
return ok;
};
data_sink.done = [&](void) { data_available = false; };
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
while (data_available && !is_shutting_down()) {
if (!content_provider(offset, 0, data_sink)) { return false; }
if (!ok) { return false; }
if (!strm.is_writable()) {
return false;
} else if (!content_provider(offset, 0, data_sink)) {
return false;
} else if (!ok) {
return false;
}
}
return true;
}
@@ -3657,7 +3722,10 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
// Emit chunked response header and footer for each chunk
auto chunk =
from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
if (!strm.is_writable() ||
!write_data(strm, chunk.data(), chunk.size())) {
ok = false;
}
}
} else {
ok = false;
@@ -3696,14 +3764,14 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
}
};
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
while (data_available && !is_shutting_down()) {
if (!content_provider(offset, 0, data_sink)) {
if (!strm.is_writable()) {
error = Error::Write;
return false;
} else if (!content_provider(offset, 0, data_sink)) {
error = Error::Canceled;
return false;
}
if (!ok) {
} else if (!ok) {
error = Error::Write;
return false;
}
@@ -3897,6 +3965,9 @@ public:
if (std::regex_match(header, m, re_content_disposition)) {
file_.name = m[1];
file_.filename = m[2];
} else {
is_valid_ = false;
return false;
}
}
buf_erase(pos + crlf_.size());
@@ -4090,29 +4161,48 @@ inline bool is_multipart_boundary_chars_valid(const std::string &boundary) {
return valid;
}
template <typename T>
inline std::string
serialize_multipart_formdata_item_begin(const T &item,
const std::string &boundary) {
std::string body = "--" + boundary + "\r\n";
body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
if (!item.filename.empty()) {
body += "; filename=\"" + item.filename + "\"";
}
body += "\r\n";
if (!item.content_type.empty()) {
body += "Content-Type: " + item.content_type + "\r\n";
}
body += "\r\n";
return body;
}
inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; }
inline std::string
serialize_multipart_formdata_finish(const std::string &boundary) {
return "--" + boundary + "--\r\n";
}
inline std::string
serialize_multipart_formdata_get_content_type(const std::string &boundary) {
return "multipart/form-data; boundary=" + boundary;
}
inline std::string
serialize_multipart_formdata(const MultipartFormDataItems &items,
const std::string &boundary,
std::string &content_type) {
const std::string &boundary, bool finish = true) {
std::string body;
for (const auto &item : items) {
body += "--" + boundary + "\r\n";
body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
if (!item.filename.empty()) {
body += "; filename=\"" + item.filename + "\"";
}
body += "\r\n";
if (!item.content_type.empty()) {
body += "Content-Type: " + item.content_type + "\r\n";
}
body += "\r\n";
body += item.content + "\r\n";
body += serialize_multipart_formdata_item_begin(item, boundary);
body += item.content + serialize_multipart_formdata_item_end();
}
body += "--" + boundary + "--\r\n";
if (finish) body += serialize_multipart_formdata_finish(boundary);
content_type = "multipart/form-data; boundary=" + boundary;
return body;
}
@@ -4497,8 +4587,8 @@ inline void hosted_at(const std::string &hostname,
*reinterpret_cast<struct sockaddr_storage *>(rp->ai_addr);
std::string ip;
int dummy = -1;
if (detail::get_remote_ip_and_port(addr, sizeof(struct sockaddr_storage),
ip, dummy)) {
if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip,
dummy)) {
addrs.push_back(ip);
}
}
@@ -4723,7 +4813,7 @@ inline bool SocketStream::is_readable() const {
inline bool SocketStream::is_writable() const {
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
is_socket_alive(sock_);
is_socket_alive(sock_);
}
inline ssize_t SocketStream::read(char *ptr, size_t size) {
@@ -4788,6 +4878,11 @@ inline void SocketStream::get_remote_ip_and_port(std::string &ip,
return detail::get_remote_ip_and_port(sock_, ip, port);
}
inline void SocketStream::get_local_ip_and_port(std::string &ip,
int &port) const {
return detail::get_local_ip_and_port(sock_, ip, port);
}
inline socket_t SocketStream::socket() const { return sock_; }
// Buffer stream implementation
@@ -4813,6 +4908,9 @@ inline ssize_t BufferStream::write(const char *ptr, size_t size) {
inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
int & /*port*/) const {}
inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/,
int & /*port*/) const {}
inline socket_t BufferStream::socket() const { return 0; }
inline const std::string &BufferStream::get_buffer() const { return buffer; }
@@ -5460,6 +5558,8 @@ inline bool Server::listen_internal() {
// Try to accept new connections after a short sleep.
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
} else if (errno == EINTR || errno == EAGAIN) {
continue;
}
if (svr_sock_ != INVALID_SOCKET) {
detail::close_socket(svr_sock_);
@@ -5792,6 +5892,10 @@ Server::process_request(Stream &strm, bool close_connection,
req.set_header("REMOTE_ADDR", req.remote_addr);
req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
strm.get_local_ip_and_port(req.local_addr, req.local_port);
req.set_header("LOCAL_ADDR", req.local_addr);
req.set_header("LOCAL_PORT", std::to_string(req.local_port));
if (req.has_header("Range")) {
const auto &range_header_value = req.get_header_value("Range");
if (!detail::parse_range_header(range_header_value, req.ranges)) {
@@ -5831,7 +5935,16 @@ Server::process_request(Stream &strm, bool close_connection,
routed = true;
} else {
res.status = 500;
res.set_header("EXCEPTION_WHAT", e.what());
std::string val;
auto s = e.what();
for (size_t i = 0; s[i]; i++) {
switch (s[i]) {
case '\r': val += "\\r"; break;
case '\n': val += "\\n"; break;
default: val += s[i]; break;
}
}
res.set_header("EXCEPTION_WHAT", val);
}
} catch (...) {
if (exception_handler_) {
@@ -6273,7 +6386,7 @@ inline bool ClientImpl::write_content_with_provider(Stream &strm,
return detail::write_content(strm, req.content_provider_, 0,
req.content_length_, is_shutting_down, error);
}
} // namespace httplib
}
inline bool ClientImpl::write_request(Stream &strm, Request &req,
bool close_connection, Error &error) {
@@ -6436,8 +6549,6 @@ inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
return ok;
};
data_sink.is_writable = [&](void) { return ok && true; };
while (ok && offset < content_length) {
if (!content_provider(offset, content_length - offset, data_sink)) {
error = Error::Canceled;
@@ -6585,6 +6696,48 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
return true;
}
inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
const std::string &boundary, const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items) {
size_t cur_item = 0, cur_start = 0;
// cur_item and cur_start are copied to within the std::function and maintain
// state between successive calls
return [&, cur_item, cur_start](size_t offset,
DataSink &sink) mutable -> bool {
if (!offset && items.size()) {
sink.os << detail::serialize_multipart_formdata(items, boundary, false);
return true;
} else if (cur_item < provider_items.size()) {
if (!cur_start) {
const auto &begin = detail::serialize_multipart_formdata_item_begin(
provider_items[cur_item], boundary);
offset += begin.size();
cur_start = offset;
sink.os << begin;
}
DataSink cur_sink;
bool has_data = true;
cur_sink.write = sink.write;
cur_sink.done = [&]() { has_data = false; };
if (!provider_items[cur_item].provider(offset - cur_start, cur_sink))
return false;
if (!has_data) {
sink.os << detail::serialize_multipart_formdata_item_end();
cur_item++;
cur_start = 0;
}
return true;
} else {
sink.os << detail::serialize_multipart_formdata_finish(boundary);
sink.done();
return true;
}
};
}
inline bool
ClientImpl::process_socket(const Socket &socket,
std::function<bool(Stream &strm)> callback) {
@@ -6730,6 +6883,11 @@ inline Result ClientImpl::Post(const std::string &path) {
return Post(path, std::string(), std::string());
}
inline Result ClientImpl::Post(const std::string &path,
const Headers &headers) {
return Post(path, headers, nullptr, 0, std::string());
}
inline Result ClientImpl::Post(const std::string &path, const char *body,
size_t content_length,
const std::string &content_type) {
@@ -6802,9 +6960,10 @@ inline Result ClientImpl::Post(const std::string &path,
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items) {
std::string content_type;
const auto &body = detail::serialize_multipart_formdata(
items, detail::make_multipart_data_boundary(), content_type);
const auto &boundary = detail::make_multipart_data_boundary();
const auto &content_type =
detail::serialize_multipart_formdata_get_content_type(boundary);
const auto &body = detail::serialize_multipart_formdata(items, boundary);
return Post(path, headers, body, content_type.c_str());
}
@@ -6815,12 +6974,25 @@ inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
}
std::string content_type;
const auto &body =
detail::serialize_multipart_formdata(items, boundary, content_type);
const auto &content_type =
detail::serialize_multipart_formdata_get_content_type(boundary);
const auto &body = detail::serialize_multipart_formdata(items, boundary);
return Post(path, headers, body, content_type.c_str());
}
inline Result
ClientImpl::Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items) {
const auto &boundary = detail::make_multipart_data_boundary();
const auto &content_type =
detail::serialize_multipart_formdata_get_content_type(boundary);
return send_with_content_provider(
"POST", path, headers, nullptr, 0, nullptr,
get_multipart_content_provider(boundary, items, provider_items),
content_type);
}
inline Result ClientImpl::Put(const std::string &path) {
return Put(path, std::string(), std::string());
}
@@ -6897,9 +7069,10 @@ inline Result ClientImpl::Put(const std::string &path,
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items) {
std::string content_type;
const auto &body = detail::serialize_multipart_formdata(
items, detail::make_multipart_data_boundary(), content_type);
const auto &boundary = detail::make_multipart_data_boundary();
const auto &content_type =
detail::serialize_multipart_formdata_get_content_type(boundary);
const auto &body = detail::serialize_multipart_formdata(items, boundary);
return Put(path, headers, body, content_type);
}
@@ -6910,12 +7083,24 @@ inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
}
std::string content_type;
const auto &body =
detail::serialize_multipart_formdata(items, boundary, content_type);
const auto &content_type =
detail::serialize_multipart_formdata_get_content_type(boundary);
const auto &body = detail::serialize_multipart_formdata(items, boundary);
return Put(path, headers, body, content_type);
}
inline Result
ClientImpl::Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items) {
const auto &boundary = detail::make_multipart_data_boundary();
const auto &content_type =
detail::serialize_multipart_formdata_get_content_type(boundary);
return send_with_content_provider(
"PUT", path, headers, nullptr, 0, nullptr,
get_multipart_content_provider(boundary, items, provider_items),
content_type);
}
inline Result ClientImpl::Patch(const std::string &path) {
return Patch(path, std::string(), std::string());
}
@@ -7275,55 +7460,12 @@ process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec,
return callback(strm);
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
static std::shared_ptr<std::vector<std::mutex>> openSSL_locks_;
class SSLThreadLocks {
public:
SSLThreadLocks() {
openSSL_locks_ =
std::make_shared<std::vector<std::mutex>>(CRYPTO_num_locks());
CRYPTO_set_locking_callback(locking_callback);
}
~SSLThreadLocks() { CRYPTO_set_locking_callback(nullptr); }
private:
static void locking_callback(int mode, int type, const char * /*file*/,
int /*line*/) {
auto &lk = (*openSSL_locks_)[static_cast<size_t>(type)];
if (mode & CRYPTO_LOCK) {
lk.lock();
} else {
lk.unlock();
}
}
};
#endif
class SSLInit {
public:
SSLInit() {
#if OPENSSL_VERSION_NUMBER < 0x1010001fL
SSL_load_error_strings();
SSL_library_init();
#else
OPENSSL_init_ssl(
OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
#endif
}
~SSLInit() {
#if OPENSSL_VERSION_NUMBER < 0x1010001fL
ERR_free_strings();
#endif
}
private:
#if OPENSSL_VERSION_NUMBER < 0x10100000L
SSLThreadLocks thread_init_;
#endif
};
// SSL socket stream implementation
@@ -7347,7 +7489,7 @@ inline bool SSLSocketStream::is_readable() const {
inline bool SSLSocketStream::is_writable() const {
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
is_socket_alive(sock_);
is_socket_alive(sock_);
}
inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
@@ -7418,6 +7560,11 @@ inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
detail::get_remote_ip_and_port(sock_, ip, port);
}
inline void SSLSocketStream::get_local_ip_and_port(std::string &ip,
int &port) const {
detail::get_local_ip_and_port(sock_, ip, port);
}
inline socket_t SSLSocketStream::socket() const { return sock_; }
static SSLInit sslinit_;
@@ -7727,7 +7874,7 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
return false;
}
auto server_cert = SSL_get_peer_certificate(ssl2);
auto server_cert = SSL_get1_peer_certificate(ssl2);
if (server_cert == nullptr) {
error = Error::SSLServerVerification;
@@ -8053,6 +8200,9 @@ inline Result Client::Head(const std::string &path, const Headers &headers) {
}
inline Result Client::Post(const std::string &path) { return cli_->Post(path); }
inline Result Client::Post(const std::string &path, const Headers &headers) {
return cli_->Post(path, headers);
}
inline Result Client::Post(const std::string &path, const char *body,
size_t content_length,
const std::string &content_type) {
@@ -8115,6 +8265,12 @@ inline Result Client::Post(const std::string &path, const Headers &headers,
const std::string &boundary) {
return cli_->Post(path, headers, items, boundary);
}
inline Result
Client::Post(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items) {
return cli_->Post(path, headers, items, provider_items);
}
inline Result Client::Put(const std::string &path) { return cli_->Put(path); }
inline Result Client::Put(const std::string &path, const char *body,
size_t content_length,
@@ -8178,6 +8334,12 @@ inline Result Client::Put(const std::string &path, const Headers &headers,
const std::string &boundary) {
return cli_->Put(path, headers, items, boundary);
}
inline Result
Client::Put(const std::string &path, const Headers &headers,
const MultipartFormDataItems &items,
const MultipartFormDataProviderItems &provider_items) {
return cli_->Put(path, headers, items, provider_items);
}
inline Result Client::Patch(const std::string &path) {
return cli_->Patch(path);
}
@@ -8383,4 +8545,8 @@ inline SSL_CTX *Client::ssl_context() const {
} // namespace httplib
#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL)
#undef poll
#endif
#endif // CPPHTTPLIB_HTTPLIB_H
+93
View File
@@ -0,0 +1,93 @@
cmake_policy(SET CMP0135 NEW)
include(FetchContent)
include(GoogleTest)
set(BUILD_GMOCK OFF)
set(INSTALL_GTEST OFF)
set(gtest_force_shared_crt ON)
FetchContent_Declare(
gtest
URL https://github.com/google/googletest/archive/main.tar.gz
)
FetchContent_MakeAvailable(gtest)
add_executable(httplib-test test.cc)
target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main)
gtest_discover_tests(httplib-test)
execute_process(
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www www
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www2 www2
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www3 www3
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/ca-bundle.crt ca-bundle.crt
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/image.jpg image.jpg
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
if(HTTPLIB_IS_USING_OPENSSL)
find_program(OPENSSL_COMMAND
NAMES openssl
PATHS ${OPENSSL_INCLUDE_DIR}/../bin
REQUIRED
)
execute_process(
COMMAND ${OPENSSL_COMMAND} genrsa 2048
OUTPUT_FILE key.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem
COMMAND ${OPENSSL_COMMAND} x509 -days 3650 -req -signkey key.pem
OUTPUT_FILE cert.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} req -x509 -new -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} genrsa 2048
OUTPUT_FILE rootCA.key.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} req -x509 -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.rootCA.conf -key rootCA.key.pem -days 1024
OUTPUT_FILE rootCA.cert.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} genrsa 2048
OUTPUT_FILE client.key.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key client.key.pem
COMMAND ${OPENSSL_COMMAND} x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial
OUTPUT_FILE client.cert.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} genrsa -passout pass:test123! 2048
OUTPUT_FILE key_encrypted.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
execute_process(
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key_encrypted.pem
COMMAND ${OPENSSL_COMMAND} x509 -days 3650 -req -signkey key_encrypted.pem
OUTPUT_FILE cert_encrypted.pem
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
endif()
+5 -2
View File
@@ -22,8 +22,6 @@ public:
ssize_t write(const std::string &s) { return write(s.data(), s.size()); }
std::string get_remote_addr() const { return ""; }
bool is_readable() const override { return true; }
bool is_writable() const override { return true; }
@@ -33,6 +31,11 @@ public:
port = 8080;
}
void get_local_ip_and_port(std::string &ip, int &port) const override {
ip = "127.0.0.1";
port = 8080;
}
socket_t socket() const override { return 0; }
private:
+487 -88
View File
@@ -1,10 +1,12 @@
#include <httplib.h>
#include <signal.h>
#include <gtest/gtest.h>
#include <atomic>
#include <chrono>
#include <future>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <thread>
@@ -594,7 +596,7 @@ TEST(ConnectionErrorTest, InvalidHostCheckResultErrorToString) {
ASSERT_TRUE(!res);
stringstream s;
s << "error code: " << res.error();
EXPECT_EQ("error code: Connection (2)", s.str());
EXPECT_EQ("error code: Could not establish connection (2)", s.str());
}
TEST(ConnectionErrorTest, InvalidPort) {
@@ -942,6 +944,44 @@ TEST(UrlWithSpace, Redirect_Online) {
#endif
#if !defined(_WIN32) && !defined(_WIN64)
TEST(ReceiveSignals, Signal) {
auto setupSignalHandlers = []() {
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = [](int sig, siginfo_t *, void *) {
switch (sig) {
case SIGINT:
default: break;
}
};
::sigaction(SIGINT, &act, nullptr);
};
Server svr;
int port = 0;
auto thread = std::thread([&]() {
setupSignalHandlers();
port = svr.bind_to_any_port("localhost");
svr.listen_after_bind();
});
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
ASSERT_TRUE(svr.is_running());
pthread_kill(thread.native_handle(), SIGINT);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
ASSERT_TRUE(svr.is_running());
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
}
#endif
TEST(RedirectToDifferentPort, Redirect) {
Server svr1;
svr1.Get("/1", [&](const Request & /*req*/, Response &res) {
@@ -1324,7 +1364,12 @@ TEST(NoContentTest, ContentLength) {
}
TEST(RoutingHandlerTest, PreRoutingHandler) {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(svr.is_valid());
#else
Server svr;
#endif
svr.set_pre_routing_handler([](const Request &req, Response &res) {
if (req.path == "/routing_handler") {
@@ -1355,7 +1400,12 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
std::this_thread::sleep_for(std::chrono::seconds(1));
{
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLClient cli(HOST, PORT);
cli.enable_server_certificate_verification(false);
#else
Client cli(HOST, PORT);
#endif
auto res = cli.Get("/routing_handler");
ASSERT_TRUE(res);
@@ -1368,7 +1418,12 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
}
{
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLClient cli(HOST, PORT);
cli.enable_server_certificate_verification(false);
#else
Client cli(HOST, PORT);
#endif
auto res = cli.Get("/hi");
ASSERT_TRUE(res);
@@ -1379,7 +1434,12 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
}
{
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLClient cli(HOST, PORT);
cli.enable_server_certificate_verification(false);
#else
Client cli(HOST, PORT);
#endif
auto res = cli.Get("/aaa");
ASSERT_TRUE(res);
@@ -1521,6 +1581,17 @@ protected:
std::stoi(req.get_header_value("REMOTE_PORT")));
res.set_content(remote_addr.c_str(), "text/plain");
})
.Get("/local_addr",
[&](const Request &req, Response &res) {
EXPECT_TRUE(req.has_header("LOCAL_PORT"));
EXPECT_TRUE(req.has_header("LOCAL_ADDR"));
auto local_addr = req.get_header_value("LOCAL_ADDR");
auto local_port = req.get_header_value("LOCAL_PORT");
EXPECT_EQ(req.local_addr, local_addr);
EXPECT_EQ(req.local_port, std::stoi(local_port));
res.set_content(local_addr.append(":").append(local_port),
"text/plain");
})
.Get("/endwith%",
[&](const Request & /*req*/, Response &res) {
res.set_content("Hello World!", "text/plain");
@@ -1579,7 +1650,6 @@ protected:
[&](const Request & /*req*/, Response &res) {
res.set_chunked_content_provider(
"text/plain", [](size_t /*offset*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.os << "123";
sink.os << "456";
sink.os << "789";
@@ -1593,7 +1663,6 @@ protected:
res.set_chunked_content_provider(
"text/plain",
[i](size_t /*offset*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
switch (*i) {
case 0: sink.os << "123"; break;
case 1: sink.os << "456"; break;
@@ -1623,7 +1692,6 @@ protected:
res.set_content_provider(
data->size(), "text/plain",
[data](size_t offset, size_t length, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
size_t DATA_CHUNK_SIZE = 4;
const auto &d = *data;
auto out_len =
@@ -1643,8 +1711,6 @@ protected:
res.set_content_provider(
size_t(-1), "text/plain",
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
if (!sink.is_writable()) return false;
sink.os << "data_chunk";
return true;
});
@@ -1715,6 +1781,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);
@@ -2125,6 +2207,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);
@@ -2779,6 +2876,15 @@ TEST_F(ServerTest, GetMethodRemoteAddr) {
EXPECT_TRUE(res->body == "::1" || res->body == "127.0.0.1");
}
TEST_F(ServerTest, GetMethodLocalAddr) {
auto res = cli_.Get("/local_addr");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_TRUE(res->body == std::string("::1:").append(to_string(PORT)) ||
res->body == std::string("127.0.0.1:").append(to_string(PORT)));
}
TEST_F(ServerTest, HTTPResponseSplitting) {
auto res = cli_.Get("/http_response_splitting");
ASSERT_TRUE(res);
@@ -2841,7 +2947,6 @@ TEST_F(ServerTest, PutWithContentProvider) {
auto res = cli_.Put(
"/put", 3,
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.os << "PUT";
return true;
},
@@ -2868,7 +2973,6 @@ TEST_F(ServerTest, PutWithContentProviderWithoutLength) {
auto res = cli_.Put(
"/put",
[](size_t /*offset*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.os << "PUT";
sink.done();
return true;
@@ -2895,7 +2999,6 @@ TEST_F(ServerTest, PutWithContentProviderWithGzip) {
auto res = cli_.Put(
"/put", 3,
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.os << "PUT";
return true;
},
@@ -2924,7 +3027,6 @@ TEST_F(ServerTest, PutWithContentProviderWithoutLengthWithGzip) {
auto res = cli_.Put(
"/put",
[](size_t /*offset*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.os << "PUT";
sink.done();
return true;
@@ -3808,9 +3910,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); });
@@ -3823,11 +3928,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();
@@ -4181,7 +4296,8 @@ protected:
res.set_content("Hello World!", "text/plain");
});
t_ = thread([&]() { ASSERT_TRUE(svr_.listen(std::string(), 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));
@@ -4753,7 +4869,7 @@ TEST(SendAPI, SimpleInterface_Online) {
TEST(ClientImplMethods, GetSocketTest) {
httplib::Server svr;
svr.Get( "/", [&](const httplib::Request& /*req*/, httplib::Response& res) {
svr.Get("/", [&](const httplib::Request & /*req*/, httplib::Response &res) {
res.status = 200;
});
@@ -5021,6 +5137,247 @@ TEST(MultipartFormDataTest, LargeData) {
t.join();
}
TEST(MultipartFormDataTest, DataProviderItems) {
std::random_device seed_gen;
std::mt19937 random(seed_gen());
std::string rand1;
rand1.resize(1000);
std::generate(rand1.begin(), rand1.end(), [&]() { return random(); });
std::string rand2;
rand2.resize(3000);
std::generate(rand2.begin(), rand2.end(), [&]() { return random(); });
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
svr.Post("/post-none", [&](const Request &req, Response & /*res*/,
const ContentReader &content_reader) {
ASSERT_FALSE(req.is_multipart_form_data());
std::string body;
content_reader([&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
EXPECT_EQ(body, "");
});
svr.Post("/post-items", [&](const Request &req, Response & /*res*/,
const ContentReader &content_reader) {
ASSERT_TRUE(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;
});
ASSERT_TRUE(files.size() == 2);
EXPECT_EQ(std::string(files[0].name), "name1");
EXPECT_EQ(files[0].content, "Testing123");
EXPECT_EQ(files[0].filename, "filename1");
EXPECT_EQ(files[0].content_type, "application/octet-stream");
EXPECT_EQ(files[1].name, "name2");
EXPECT_EQ(files[1].content, "Testing456");
EXPECT_EQ(files[1].filename, "");
EXPECT_EQ(files[1].content_type, "");
});
svr.Post("/post-providers", [&](const Request &req, Response & /*res*/,
const ContentReader &content_reader) {
ASSERT_TRUE(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;
});
ASSERT_TRUE(files.size() == 2);
EXPECT_EQ(files[0].name, "name3");
EXPECT_EQ(files[0].content, rand1);
EXPECT_EQ(files[0].filename, "filename3");
EXPECT_EQ(files[0].content_type, "");
EXPECT_EQ(files[1].name, "name4");
EXPECT_EQ(files[1].content, rand2);
EXPECT_EQ(files[1].filename, "filename4");
EXPECT_EQ(files[1].content_type, "");
});
svr.Post("/post-both", [&](const Request &req, Response & /*res*/,
const ContentReader &content_reader) {
ASSERT_TRUE(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;
});
ASSERT_TRUE(files.size() == 4);
EXPECT_EQ(std::string(files[0].name), "name1");
EXPECT_EQ(files[0].content, "Testing123");
EXPECT_EQ(files[0].filename, "filename1");
EXPECT_EQ(files[0].content_type, "application/octet-stream");
EXPECT_EQ(files[1].name, "name2");
EXPECT_EQ(files[1].content, "Testing456");
EXPECT_EQ(files[1].filename, "");
EXPECT_EQ(files[1].content_type, "");
EXPECT_EQ(files[2].name, "name3");
EXPECT_EQ(files[2].content, rand1);
EXPECT_EQ(files[2].filename, "filename3");
EXPECT_EQ(files[2].content_type, "");
EXPECT_EQ(files[3].name, "name4");
EXPECT_EQ(files[3].content, rand2);
EXPECT_EQ(files[3].filename, "filename4");
EXPECT_EQ(files[3].content_type, "");
});
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));
{
Client cli("https://localhost:8080");
cli.enable_server_certificate_verification(false);
MultipartFormDataItems items{
{"name1", "Testing123", "filename1", "application/octet-stream"},
{"name2", "Testing456", "", ""}, // not a file
};
{
auto res = cli.Post("/post-none", {}, {}, {});
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
}
MultipartFormDataProviderItems providers;
{
auto res =
cli.Post("/post-items", {}, items, providers); // empty providers
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
}
providers.push_back({"name3",
[&](size_t offset, httplib::DataSink &sink) -> bool {
// test the offset is given correctly at each step
if (!offset)
sink.os.write(rand1.data(), 30);
else if (offset == 30)
sink.os.write(rand1.data() + 30, 300);
else if (offset == 330)
sink.os.write(rand1.data() + 330, 670);
else if (offset == rand1.size())
sink.done();
return true;
},
"filename3",
{}});
providers.push_back({"name4",
[&](size_t offset, httplib::DataSink &sink) -> bool {
// test the offset is given correctly at each step
if (!offset)
sink.os.write(rand2.data(), 2000);
else if (offset == 2000)
sink.os.write(rand2.data() + 2000, 1);
else if (offset == 2001)
sink.os.write(rand2.data() + 2001, 999);
else if (offset == rand2.size())
sink.done();
return true;
},
"filename4",
{}});
{
auto res = cli.Post("/post-providers", {}, {}, providers);
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
}
{
auto res = cli.Post("/post-both", {}, items, providers);
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
}
}
svr.stop();
t.join();
}
TEST(MultipartFormDataTest, BadHeader) {
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"
": BAD...\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(400, res->status);
svr.stop();
t.join();
}
TEST(MultipartFormDataTest, WithPreamble) {
Server svr;
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
@@ -5067,7 +5424,7 @@ 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) {
const ContentReader &content_reader) {
if (req.is_multipart_form_data()) {
MultipartFormDataItems files;
content_reader(
@@ -5127,7 +5484,7 @@ TEST(MultipartFormDataTest, PostCustomBoundary) {
}
TEST(MultipartFormDataTest, PostInvalidBoundaryChars) {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::string data(1024 * 1024 * 2, '&');
@@ -5141,12 +5498,12 @@ TEST(MultipartFormDataTest, PostInvalidBoundaryChars) {
{"hello", "world", "", ""},
};
for (const char& c: " \t\r\n") {
auto res = cli.Post("/invalid_boundary", {}, items, string("abc123").append(1, c));
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) {
@@ -5215,38 +5572,39 @@ TEST(MultipartFormDataTest, PutFormData) {
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;
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;
});
}
});
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));
@@ -5291,7 +5649,7 @@ TEST(MultipartFormDataTest, PutInvalidBoundaryChars) {
{"hello", "world", "", ""},
};
for (const char& c: " \t\r\n") {
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);
@@ -5303,9 +5661,7 @@ TEST(MultipartFormDataTest, PutInvalidBoundaryChars) {
#ifndef _WIN32
class UnixSocketTest : public ::testing::Test {
protected:
void TearDown() override {
std::remove(pathname_.c_str());
}
void TearDown() override { std::remove(pathname_.c_str()); }
void client_GET(const std::string &addr) {
httplib::Client cli{addr};
@@ -5320,9 +5676,9 @@ protected:
EXPECT_EQ(resp.body, content_);
}
const std::string pathname_ {"./httplib-server.sock"};
const std::string pattern_ {"/hi"};
const std::string content_ {"Hello World!"};
const std::string pathname_{"./httplib-server.sock"};
const std::string pattern_{"/hi"};
const std::string content_{"Hello World!"};
};
TEST_F(UnixSocketTest, pathname) {
@@ -5331,8 +5687,9 @@ TEST_F(UnixSocketTest, pathname) {
res.set_content(content_, "text/plain");
});
std::thread t {[&] {
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80)); }};
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));
}
@@ -5344,18 +5701,45 @@ TEST_F(UnixSocketTest, pathname) {
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};
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)); }};
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));
}
@@ -5369,58 +5753,58 @@ TEST_F(UnixSocketTest, abstract) {
#endif
TEST(SocketStream, is_writable_UNIX) {
int fd[2];
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fd));
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(fd[0], [&] (Stream &s0) {
EXPECT_EQ(s0.socket(), fd[0]);
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(fd[1]));
EXPECT_EQ(0, close(fds[1]));
EXPECT_FALSE(s0.is_writable());
return true;
});
EXPECT_EQ(0, close(fd[0]));
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_port = htons(PORT + 1);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
int disconnected_svr_sock = -1;
std::thread svr {[&] {
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, ::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 {[&] {
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, 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) {
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());
@@ -5429,4 +5813,19 @@ TEST(SocketStream, is_writable_INET) {
ASSERT_EQ(0, close(disconnected_svr_sock));
}
#endif // #ifndef _WIN32
#endif // #ifndef _WIN32
TEST(TaskQueueTest, IncreaseAtomicInteger) {
static constexpr unsigned int number_of_task{1000000};
std::atomic_uint count{0};
std::unique_ptr<TaskQueue> task_queue{
new ThreadPool{CPPHTTPLIB_THREAD_POOL_COUNT}};
for (unsigned int i = 0; i < number_of_task; ++i) {
task_queue->enqueue(
[&count] { count.fetch_add(1, std::memory_order_relaxed); });
}
EXPECT_NO_THROW(task_queue->shutdown());
EXPECT_EQ(number_of_task, count.load());
}