mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| af56b7ec0b | |||
| 6c3e8482f7 | |||
| 390f2c41f6 | |||
| aa04feebb4 | |||
| 45f3694f82 | |||
| c5c54b31e2 | |||
| 69c84c9597 | |||
| ae63b89cbf | |||
| ff038f98b7 | |||
| e00fd06355 | |||
| 521529d24d | |||
| ed0719f2bc | |||
| 6a848b1a16 | |||
| c8bcaf8a91 | |||
| 8cd0ed0509 | |||
| 177d8420a1 | |||
| 388a8c007c | |||
| bdefdce1ae | |||
| 9e4f93d87e | |||
| 0b657d28cf | |||
| c1a09daf15 | |||
| fb739dbaec | |||
| 50fce538c6 |
@@ -0,0 +1,2 @@
|
||||
/test/www*/dir/*.html text eol=lf
|
||||
/test/www*/dir/*.txt text eol=lf
|
||||
@@ -24,6 +24,7 @@ test/*.srl
|
||||
|
||||
*.swp
|
||||
|
||||
build/
|
||||
Debug
|
||||
Release
|
||||
*.vcxproj.user
|
||||
@@ -34,4 +35,5 @@ Release
|
||||
ipch
|
||||
*.dSYM
|
||||
.*
|
||||
!/.gitattributes
|
||||
!/.travis.yml
|
||||
|
||||
+5
-3
@@ -1,8 +1,7 @@
|
||||
#CXX = clang++
|
||||
CXXFLAGS = -O2 -std=c++11 -I.. -Wall -Wextra -pthread
|
||||
|
||||
PREFIX = /usr/local
|
||||
#PREFIX = $(shell brew --prefix)
|
||||
PREFIX ?= $(shell brew --prefix)
|
||||
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
@@ -51,9 +50,12 @@ ssecli : ssecli.cc ../httplib.h Makefile
|
||||
benchmark : benchmark.cc ../httplib.h Makefile
|
||||
$(CXX) -o benchmark $(CXXFLAGS) benchmark.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
one_time_request : one_time_request.cc ../httplib.h Makefile
|
||||
$(CXX) -o one_time_request $(CXXFLAGS) one_time_request.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
pem:
|
||||
openssl genrsa 2048 > key.pem
|
||||
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||
|
||||
clean:
|
||||
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark *.pem
|
||||
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request *.pem
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace httplib;
|
||||
|
||||
const char *HOST = "localhost";
|
||||
const int PORT = 1234;
|
||||
|
||||
void one_time_request_server(const char *label) {
|
||||
std::thread th;
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content(std::string("Hello from ") + label, "text/plain");
|
||||
|
||||
// Stop server
|
||||
th = std::thread([&]() { svr.stop(); });
|
||||
});
|
||||
|
||||
svr.listen(HOST, PORT);
|
||||
th.join();
|
||||
|
||||
std::cout << label << " ended..." << std::endl;
|
||||
}
|
||||
|
||||
void send_request(const char *label) {
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
std::cout << "Send " << label << " request" << std::endl;
|
||||
auto res = cli.Get("/hi");
|
||||
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl;
|
||||
} else {
|
||||
std::cout << "Request error: " + to_string(res.error()) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
auto th1 = std::thread([&]() { one_time_request_server("Server #1"); });
|
||||
auto th2 = std::thread([&]() { one_time_request_server("Server #2"); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
send_request("1st");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
send_request("2nd");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
send_request("3rd");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
th1.join();
|
||||
th2.join();
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.16.0"
|
||||
#define CPPHTTPLIB_VERSION "0.16.3"
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
@@ -269,7 +269,12 @@ using socket_t = int;
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER < 0x30000000L
|
||||
#if defined(OPENSSL_IS_BORINGSSL)
|
||||
#if OPENSSL_VERSION_NUMBER < 0x1010107f
|
||||
#error Please use OpenSSL or a current version of BoringSSL
|
||||
#endif
|
||||
#define SSL_get1_peer_certificate SSL_get_peer_certificate
|
||||
#elif OPENSSL_VERSION_NUMBER < 0x30000000L
|
||||
#error Sorry, OpenSSL versions prior to 3.0.0 are not supported
|
||||
#endif
|
||||
|
||||
@@ -726,6 +731,10 @@ private:
|
||||
assert(true == static_cast<bool>(fn));
|
||||
fn();
|
||||
}
|
||||
|
||||
#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL)
|
||||
OPENSSL_thread_stop();
|
||||
#endif
|
||||
}
|
||||
|
||||
ThreadPool &pool_;
|
||||
@@ -1526,6 +1535,7 @@ public:
|
||||
const std::string &client_key_path);
|
||||
|
||||
Client(Client &&) = default;
|
||||
Client &operator=(Client &&) = default;
|
||||
|
||||
~Client();
|
||||
|
||||
@@ -1819,9 +1829,9 @@ public:
|
||||
bool is_valid() const override;
|
||||
|
||||
SSL_CTX *ssl_context() const;
|
||||
|
||||
void update_certs (X509 *cert, EVP_PKEY *private_key,
|
||||
X509_STORE *client_ca_cert_store = nullptr);
|
||||
|
||||
void update_certs(X509 *cert, EVP_PKEY *private_key,
|
||||
X509_STORE *client_ca_cert_store = nullptr);
|
||||
|
||||
private:
|
||||
bool process_and_close_socket(socket_t sock) override;
|
||||
@@ -2819,24 +2829,51 @@ inline bool mmap::open(const char *path) {
|
||||
wpath += path[i];
|
||||
}
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM | \
|
||||
WINAPI_PARTITION_GAMES) && \
|
||||
(_WIN32_WINNT >= _WIN32_WINNT_WIN8)
|
||||
hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ,
|
||||
OPEN_EXISTING, NULL);
|
||||
#else
|
||||
hFile_ = ::CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
#endif
|
||||
|
||||
if (hFile_ == INVALID_HANDLE_VALUE) { return false; }
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM | \
|
||||
WINAPI_PARTITION_GAMES)
|
||||
LARGE_INTEGER size{};
|
||||
if (!::GetFileSizeEx(hFile_, &size)) { return false; }
|
||||
size_ = static_cast<size_t>(size.QuadPart);
|
||||
#else
|
||||
DWORD sizeHigh;
|
||||
DWORD sizeLow;
|
||||
sizeLow = ::GetFileSize(hFile_, &sizeHigh);
|
||||
if (sizeLow == INVALID_FILE_SIZE) { return false; }
|
||||
size_ = (static_cast<size_t>(sizeHigh) << (sizeof(DWORD) * 8)) | sizeLow;
|
||||
#endif
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) && \
|
||||
(_WIN32_WINNT >= _WIN32_WINNT_WIN8)
|
||||
hMapping_ =
|
||||
::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL);
|
||||
#else
|
||||
hMapping_ = ::CreateFileMappingW(hFile_, NULL, PAGE_READONLY, size.HighPart,
|
||||
size.LowPart, NULL);
|
||||
#endif
|
||||
|
||||
if (hMapping_ == NULL) {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) && \
|
||||
(_WIN32_WINNT >= _WIN32_WINNT_WIN8)
|
||||
addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0);
|
||||
#else
|
||||
addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0);
|
||||
#endif
|
||||
#else
|
||||
fd_ = ::open(path, O_RDONLY);
|
||||
if (fd_ == -1) { return false; }
|
||||
@@ -3219,7 +3256,13 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
|
||||
const auto addrlen = host.length();
|
||||
if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; }
|
||||
|
||||
#ifdef SOCK_CLOEXEC
|
||||
auto sock = socket(hints.ai_family, hints.ai_socktype | SOCK_CLOEXEC,
|
||||
hints.ai_protocol);
|
||||
#else
|
||||
auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
|
||||
#endif
|
||||
|
||||
if (sock != INVALID_SOCKET) {
|
||||
sockaddr_un addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
@@ -3229,10 +3272,14 @@ 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);
|
||||
|
||||
#ifndef SOCK_CLOEXEC
|
||||
fcntl(sock, F_SETFD, FD_CLOEXEC);
|
||||
#endif
|
||||
|
||||
if (socket_options) { socket_options(sock); }
|
||||
|
||||
if (!bind_or_connect(sock, hints)) {
|
||||
bool dummy;
|
||||
if (!bind_or_connect(sock, hints, dummy)) {
|
||||
close_socket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
}
|
||||
@@ -3274,11 +3321,18 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
|
||||
sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||
}
|
||||
#else
|
||||
|
||||
#ifdef SOCK_CLOEXEC
|
||||
auto sock =
|
||||
socket(rp->ai_family, rp->ai_socktype | SOCK_CLOEXEC, rp->ai_protocol);
|
||||
#else
|
||||
auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
if (sock == INVALID_SOCKET) { continue; }
|
||||
|
||||
#ifndef _WIN32
|
||||
#if !defined _WIN32 && !defined SOCK_CLOEXEC
|
||||
if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) {
|
||||
close_socket(sock);
|
||||
continue;
|
||||
@@ -3310,12 +3364,15 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
|
||||
}
|
||||
|
||||
// bind or connect
|
||||
if (bind_or_connect(sock, *rp)) {
|
||||
auto quit = false;
|
||||
if (bind_or_connect(sock, *rp, quit)) {
|
||||
freeaddrinfo(result);
|
||||
return sock;
|
||||
}
|
||||
|
||||
close_socket(sock);
|
||||
|
||||
if (quit) { break; }
|
||||
}
|
||||
|
||||
freeaddrinfo(result);
|
||||
@@ -3416,7 +3473,7 @@ inline socket_t create_client_socket(
|
||||
time_t write_timeout_usec, const std::string &intf, Error &error) {
|
||||
auto sock = create_socket(
|
||||
host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options),
|
||||
[&](socket_t sock2, struct addrinfo &ai) -> bool {
|
||||
[&](socket_t sock2, struct addrinfo &ai, bool &quit) -> bool {
|
||||
if (!intf.empty()) {
|
||||
#ifdef USE_IF2IP
|
||||
auto ip_from_if = if2ip(address_family, intf);
|
||||
@@ -3440,7 +3497,10 @@ inline socket_t create_client_socket(
|
||||
}
|
||||
error = wait_until_socket_is_ready(sock2, connection_timeout_sec,
|
||||
connection_timeout_usec);
|
||||
if (error != Error::Success) { return false; }
|
||||
if (error != Error::Success) {
|
||||
if (error == Error::ConnectionTimeout) { quit = true; }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
set_nonblocking(sock2, false);
|
||||
@@ -6417,7 +6477,7 @@ Server::create_server_socket(const std::string &host, int port,
|
||||
return detail::create_socket(
|
||||
host, std::string(), port, address_family_, socket_flags, tcp_nodelay_,
|
||||
std::move(socket_options),
|
||||
[](socket_t sock, struct addrinfo &ai) -> bool {
|
||||
[](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool {
|
||||
if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
|
||||
return false;
|
||||
}
|
||||
@@ -6473,7 +6533,16 @@ inline bool Server::listen_internal() {
|
||||
#ifndef _WIN32
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined _WIN32
|
||||
// sockets conneced via WASAccept inherit flags NO_HANDLE_INHERIT,
|
||||
// OVERLAPPED
|
||||
socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0);
|
||||
#elif defined SOCK_CLOEXEC
|
||||
socket_t sock = accept4(svr_sock_, nullptr, nullptr, SOCK_CLOEXEC);
|
||||
#else
|
||||
socket_t sock = accept(svr_sock_, nullptr, nullptr);
|
||||
#endif
|
||||
|
||||
if (sock == INVALID_SOCKET) {
|
||||
if (errno == EMFILE) {
|
||||
@@ -7271,7 +7340,7 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
|
||||
if (location.empty()) { return false; }
|
||||
|
||||
const static std::regex re(
|
||||
R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)");
|
||||
R"((?:(https?):)?(?://(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)");
|
||||
|
||||
std::smatch m;
|
||||
if (!std::regex_match(location, m, re)) { return false; }
|
||||
@@ -8157,7 +8226,8 @@ inline Result ClientImpl::Patch(const std::string &path,
|
||||
|
||||
inline Result ClientImpl::Patch(const std::string &path,
|
||||
const std::string &body,
|
||||
const std::string &content_type, Progress progress) {
|
||||
const std::string &content_type,
|
||||
Progress progress) {
|
||||
return Patch(path, Headers(), body, content_type, progress);
|
||||
}
|
||||
|
||||
@@ -8508,13 +8578,29 @@ inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex,
|
||||
return ssl;
|
||||
}
|
||||
|
||||
inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl,
|
||||
inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock,
|
||||
bool shutdown_gracefully) {
|
||||
// sometimes we may want to skip this to try to avoid SIGPIPE if we know
|
||||
// the remote has closed the network connection
|
||||
// Note that it is not always possible to avoid SIGPIPE, this is merely a
|
||||
// best-efforts.
|
||||
if (shutdown_gracefully) { SSL_shutdown(ssl); }
|
||||
if (shutdown_gracefully) {
|
||||
#ifdef _WIN32
|
||||
SSL_shutdown(ssl);
|
||||
#else
|
||||
timeval tv;
|
||||
tv.tv_sec = 1;
|
||||
tv.tv_usec = 0;
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
|
||||
reinterpret_cast<const void *>(&tv), sizeof(tv));
|
||||
|
||||
auto ret = SSL_shutdown(ssl);
|
||||
while (ret == 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
ret = SSL_shutdown(ssl);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> guard(ctx_mutex);
|
||||
SSL_free(ssl);
|
||||
@@ -8690,7 +8776,7 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
|
||||
SSL_OP_NO_COMPRESSION |
|
||||
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
|
||||
|
||||
SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION);
|
||||
SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
|
||||
|
||||
if (private_key_password != nullptr && (private_key_password[0] != '\0')) {
|
||||
SSL_CTX_set_default_passwd_cb_userdata(
|
||||
@@ -8722,7 +8808,7 @@ inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key,
|
||||
SSL_OP_NO_COMPRESSION |
|
||||
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
|
||||
|
||||
SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION);
|
||||
SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
|
||||
|
||||
if (SSL_CTX_use_certificate(ctx_, cert) != 1 ||
|
||||
SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) {
|
||||
@@ -8756,17 +8842,17 @@ inline bool SSLServer::is_valid() const { return ctx_; }
|
||||
|
||||
inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; }
|
||||
|
||||
inline void SSLServer::update_certs (X509 *cert, EVP_PKEY *private_key,
|
||||
X509_STORE *client_ca_cert_store) {
|
||||
inline void SSLServer::update_certs(X509 *cert, EVP_PKEY *private_key,
|
||||
X509_STORE *client_ca_cert_store) {
|
||||
|
||||
std::lock_guard<std::mutex> guard(ctx_mutex_);
|
||||
std::lock_guard<std::mutex> guard(ctx_mutex_);
|
||||
|
||||
SSL_CTX_use_certificate (ctx_, cert);
|
||||
SSL_CTX_use_PrivateKey (ctx_, private_key);
|
||||
SSL_CTX_use_certificate(ctx_, cert);
|
||||
SSL_CTX_use_PrivateKey(ctx_, private_key);
|
||||
|
||||
if (client_ca_cert_store != nullptr) {
|
||||
SSL_CTX_set_cert_store (ctx_, client_ca_cert_store);
|
||||
}
|
||||
if (client_ca_cert_store != nullptr) {
|
||||
SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool SSLServer::process_and_close_socket(socket_t sock) {
|
||||
@@ -8793,7 +8879,7 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) {
|
||||
// Shutdown gracefully if the result seemed successful, non-gracefully if
|
||||
// the connection appeared to be closed.
|
||||
const bool shutdown_gracefully = ret;
|
||||
detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully);
|
||||
detail::ssl_delete(ctx_mutex_, ssl, sock, shutdown_gracefully);
|
||||
}
|
||||
|
||||
detail::shutdown_socket(sock);
|
||||
@@ -9047,11 +9133,14 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||
return true;
|
||||
},
|
||||
[&](SSL *ssl2) {
|
||||
#if defined(OPENSSL_IS_BORINGSSL)
|
||||
SSL_set_tlsext_host_name(ssl2, host_.c_str());
|
||||
#else
|
||||
// NOTE: Direct call instead of using the OpenSSL macro to suppress
|
||||
// -Wold-style-cast warning
|
||||
// SSL_set_tlsext_host_name(ssl2, host_.c_str());
|
||||
SSL_ctrl(ssl2, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name,
|
||||
static_cast<void *>(const_cast<char *>(host_.c_str())));
|
||||
#endif
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -9076,7 +9165,8 @@ inline void SSLClient::shutdown_ssl_impl(Socket &socket,
|
||||
return;
|
||||
}
|
||||
if (socket.ssl) {
|
||||
detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully);
|
||||
detail::ssl_delete(ctx_mutex_, socket.ssl, socket.sock,
|
||||
shutdown_gracefully);
|
||||
socket.ssl = nullptr;
|
||||
}
|
||||
assert(socket.ssl == nullptr);
|
||||
@@ -9271,7 +9361,7 @@ inline Client::Client(const std::string &scheme_host_port,
|
||||
cli_ = detail::make_unique<ClientImpl>(scheme_host_port, 80,
|
||||
client_cert_path, client_key_path);
|
||||
}
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
inline Client::Client(const std::string &host, int port)
|
||||
: cli_(detail::make_unique<ClientImpl>(host, port)) {}
|
||||
@@ -9551,7 +9641,8 @@ inline Result Client::Patch(const std::string &path, const char *body,
|
||||
}
|
||||
inline Result Client::Patch(const std::string &path, const char *body,
|
||||
size_t content_length,
|
||||
const std::string &content_type, Progress progress) {
|
||||
const std::string &content_type,
|
||||
Progress progress) {
|
||||
return cli_->Patch(path, body, content_length, content_type, progress);
|
||||
}
|
||||
inline Result Client::Patch(const std::string &path, const Headers &headers,
|
||||
@@ -9561,15 +9652,18 @@ inline Result Client::Patch(const std::string &path, const Headers &headers,
|
||||
}
|
||||
inline Result Client::Patch(const std::string &path, const Headers &headers,
|
||||
const char *body, size_t content_length,
|
||||
const std::string &content_type, Progress progress) {
|
||||
return cli_->Patch(path, headers, body, content_length, content_type, progress);
|
||||
const std::string &content_type,
|
||||
Progress progress) {
|
||||
return cli_->Patch(path, headers, body, content_length, content_type,
|
||||
progress);
|
||||
}
|
||||
inline Result Client::Patch(const std::string &path, const std::string &body,
|
||||
const std::string &content_type) {
|
||||
return cli_->Patch(path, body, content_type);
|
||||
}
|
||||
inline Result Client::Patch(const std::string &path, const std::string &body,
|
||||
const std::string &content_type, Progress progress) {
|
||||
const std::string &content_type,
|
||||
Progress progress) {
|
||||
return cli_->Patch(path, body, content_type, progress);
|
||||
}
|
||||
inline Result Client::Patch(const std::string &path, const Headers &headers,
|
||||
@@ -9579,7 +9673,8 @@ inline Result Client::Patch(const std::string &path, const Headers &headers,
|
||||
}
|
||||
inline Result Client::Patch(const std::string &path, const Headers &headers,
|
||||
const std::string &body,
|
||||
const std::string &content_type, Progress progress) {
|
||||
const std::string &content_type,
|
||||
Progress progress) {
|
||||
return cli_->Patch(path, headers, body, content_type, progress);
|
||||
}
|
||||
inline Result Client::Patch(const std::string &path, size_t content_length,
|
||||
@@ -9618,7 +9713,8 @@ inline Result Client::Delete(const std::string &path, const char *body,
|
||||
}
|
||||
inline Result Client::Delete(const std::string &path, const char *body,
|
||||
size_t content_length,
|
||||
const std::string &content_type, Progress progress) {
|
||||
const std::string &content_type,
|
||||
Progress progress) {
|
||||
return cli_->Delete(path, body, content_length, content_type, progress);
|
||||
}
|
||||
inline Result Client::Delete(const std::string &path, const Headers &headers,
|
||||
@@ -9628,15 +9724,18 @@ inline Result Client::Delete(const std::string &path, const Headers &headers,
|
||||
}
|
||||
inline Result Client::Delete(const std::string &path, const Headers &headers,
|
||||
const char *body, size_t content_length,
|
||||
const std::string &content_type, Progress progress) {
|
||||
return cli_->Delete(path, headers, body, content_length, content_type, progress);
|
||||
const std::string &content_type,
|
||||
Progress progress) {
|
||||
return cli_->Delete(path, headers, body, content_length, content_type,
|
||||
progress);
|
||||
}
|
||||
inline Result Client::Delete(const std::string &path, const std::string &body,
|
||||
const std::string &content_type) {
|
||||
return cli_->Delete(path, body, content_type);
|
||||
}
|
||||
inline Result Client::Delete(const std::string &path, const std::string &body,
|
||||
const std::string &content_type, Progress progress) {
|
||||
const std::string &content_type,
|
||||
Progress progress) {
|
||||
return cli_->Delete(path, body, content_type, progress);
|
||||
}
|
||||
inline Result Client::Delete(const std::string &path, const Headers &headers,
|
||||
@@ -9646,7 +9745,8 @@ inline Result Client::Delete(const std::string &path, const Headers &headers,
|
||||
}
|
||||
inline Result Client::Delete(const std::string &path, const Headers &headers,
|
||||
const std::string &body,
|
||||
const std::string &content_type, Progress progress) {
|
||||
const std::string &content_type,
|
||||
Progress progress) {
|
||||
return cli_->Delete(path, headers, body, content_type, progress);
|
||||
}
|
||||
inline Result Client::Options(const std::string &path) {
|
||||
|
||||
+266
-197
@@ -6,6 +6,7 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
@@ -53,11 +54,176 @@ MultipartFormData &get_file_value(MultipartFormDataItems &files,
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(ConstructorTest, MoveConstructible) {
|
||||
#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, StatusCode::OK_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));
|
||||
}};
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(pathname_);
|
||||
}
|
||||
|
||||
#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));
|
||||
}};
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(pathname_);
|
||||
EXPECT_EQ(std::to_string(getpid()), remote_port_val);
|
||||
}
|
||||
#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));
|
||||
}};
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(abstract_addr);
|
||||
}
|
||||
#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
|
||||
|
||||
TEST(ClientTest, MoveConstructible) {
|
||||
EXPECT_FALSE(std::is_copy_constructible<Client>::value);
|
||||
EXPECT_TRUE(std::is_nothrow_move_constructible<Client>::value);
|
||||
}
|
||||
|
||||
TEST(ClientTest, MoveAssignable) {
|
||||
EXPECT_FALSE(std::is_copy_assignable<Client>::value);
|
||||
EXPECT_TRUE(std::is_nothrow_move_assignable<Client>::value);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
TEST(StartupTest, WSAStartup) {
|
||||
WSADATA wsaData;
|
||||
@@ -1748,32 +1914,34 @@ TEST(BindServerTest, BindAndListenSeparatelySSLEncryptedKey) {
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
X509* readCertificate (const std::string& strFileName) {
|
||||
std::ifstream inStream (strFileName);
|
||||
std::string strCertPEM ((std::istreambuf_iterator<char>(inStream)), std::istreambuf_iterator<char>());
|
||||
X509 *readCertificate(const std::string &strFileName) {
|
||||
std::ifstream inStream(strFileName);
|
||||
std::string strCertPEM((std::istreambuf_iterator<char>(inStream)),
|
||||
std::istreambuf_iterator<char>());
|
||||
|
||||
if (strCertPEM.empty ()) return (nullptr);
|
||||
if (strCertPEM.empty()) return (nullptr);
|
||||
|
||||
BIO* pbCert = BIO_new (BIO_s_mem ());
|
||||
BIO_write (pbCert, strCertPEM.c_str (), (int)strCertPEM.size ());
|
||||
X509* pCert = PEM_read_bio_X509 (pbCert, NULL, 0, NULL);
|
||||
BIO_free (pbCert);
|
||||
BIO *pbCert = BIO_new(BIO_s_mem());
|
||||
BIO_write(pbCert, strCertPEM.c_str(), (int)strCertPEM.size());
|
||||
X509 *pCert = PEM_read_bio_X509(pbCert, NULL, 0, NULL);
|
||||
BIO_free(pbCert);
|
||||
|
||||
return (pCert);
|
||||
return (pCert);
|
||||
}
|
||||
|
||||
EVP_PKEY* readPrivateKey (const std::string& strFileName) {
|
||||
std::ifstream inStream (strFileName);
|
||||
std::string strPrivateKeyPEM ((std::istreambuf_iterator<char>(inStream)), std::istreambuf_iterator<char>());
|
||||
EVP_PKEY *readPrivateKey(const std::string &strFileName) {
|
||||
std::ifstream inStream(strFileName);
|
||||
std::string strPrivateKeyPEM((std::istreambuf_iterator<char>(inStream)),
|
||||
std::istreambuf_iterator<char>());
|
||||
|
||||
if (strPrivateKeyPEM.empty ()) return (nullptr);
|
||||
if (strPrivateKeyPEM.empty()) return (nullptr);
|
||||
|
||||
BIO* pbPrivKey = BIO_new (BIO_s_mem ());
|
||||
BIO_write (pbPrivKey, strPrivateKeyPEM.c_str (), (int) strPrivateKeyPEM.size ());
|
||||
EVP_PKEY* pPrivateKey = PEM_read_bio_PrivateKey (pbPrivKey, NULL, NULL, NULL);
|
||||
BIO_free (pbPrivKey);
|
||||
BIO *pbPrivKey = BIO_new(BIO_s_mem());
|
||||
BIO_write(pbPrivKey, strPrivateKeyPEM.c_str(), (int)strPrivateKeyPEM.size());
|
||||
EVP_PKEY *pPrivateKey = PEM_read_bio_PrivateKey(pbPrivKey, NULL, NULL, NULL);
|
||||
BIO_free(pbPrivKey);
|
||||
|
||||
return (pPrivateKey);
|
||||
return (pPrivateKey);
|
||||
}
|
||||
|
||||
TEST(BindServerTest, UpdateCerts) {
|
||||
@@ -1782,26 +1950,26 @@ TEST(BindServerTest, UpdateCerts) {
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
ASSERT_TRUE(port > 0);
|
||||
|
||||
X509* cert = readCertificate (SERVER_CERT_FILE);
|
||||
X509* ca_cert = readCertificate (CLIENT_CA_CERT_FILE);
|
||||
EVP_PKEY* key = readPrivateKey (SERVER_PRIVATE_KEY_FILE);
|
||||
X509 *cert = readCertificate(SERVER_CERT_FILE);
|
||||
X509 *ca_cert = readCertificate(CLIENT_CA_CERT_FILE);
|
||||
EVP_PKEY *key = readPrivateKey(SERVER_PRIVATE_KEY_FILE);
|
||||
|
||||
ASSERT_TRUE(cert != nullptr);
|
||||
ASSERT_TRUE(cert != nullptr);
|
||||
ASSERT_TRUE(ca_cert != nullptr);
|
||||
ASSERT_TRUE(key != nullptr);
|
||||
ASSERT_TRUE(key != nullptr);
|
||||
|
||||
X509_STORE* cert_store = X509_STORE_new ();
|
||||
X509_STORE *cert_store = X509_STORE_new();
|
||||
|
||||
X509_STORE_add_cert (cert_store, ca_cert);
|
||||
X509_STORE_add_cert(cert_store, ca_cert);
|
||||
|
||||
svr.update_certs (cert, key, cert_store);
|
||||
svr.update_certs(cert, key, cert_store);
|
||||
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
svr.stop();
|
||||
|
||||
X509_free (cert);
|
||||
X509_free (ca_cert);
|
||||
EVP_PKEY_free (key);
|
||||
X509_free(cert);
|
||||
X509_free(ca_cert);
|
||||
EVP_PKEY_free(key);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2350,8 +2518,8 @@ protected:
|
||||
})
|
||||
.Get("/with-range-customized-response",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.status = StatusCode::BadRequest_400;
|
||||
res.set_content(JSON_DATA, "application/json");
|
||||
res.status = StatusCode::BadRequest_400;
|
||||
res.set_content(JSON_DATA, "application/json");
|
||||
})
|
||||
.Post("/chunked",
|
||||
[&](const Request &req, Response & /*res*/) {
|
||||
@@ -3473,8 +3641,10 @@ TEST_F(ServerTest, GetStreamedWithRangeError) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetRangeWithMaxLongLength) {
|
||||
auto res =
|
||||
cli_.Get("/with-range", {{"Range", "bytes=0-9223372036854775807"}});
|
||||
auto res = cli_.Get(
|
||||
"/with-range",
|
||||
{{"Range",
|
||||
"bytes=0-" + std::to_string(std::numeric_limits<long>::max())}});
|
||||
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
|
||||
EXPECT_EQ("0", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(false, res->has_header("Content-Range"));
|
||||
@@ -3630,7 +3800,8 @@ TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetWithRangeCustomizedResponse) {
|
||||
auto res = cli_.Get("/with-range-customized-response", {{make_range_header({{1, 2}})}});
|
||||
auto res = cli_.Get("/with-range-customized-response",
|
||||
{{make_range_header({{1, 2}})}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::BadRequest_400, res->status);
|
||||
EXPECT_EQ(true, res->has_header("Content-Length"));
|
||||
@@ -3639,7 +3810,8 @@ TEST_F(ServerTest, GetWithRangeCustomizedResponse) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetWithRangeMultipartCustomizedResponseMultipleRange) {
|
||||
auto res = cli_.Get("/with-range-customized-response", {{make_range_header({{1, 2}, {4, 5}})}});
|
||||
auto res = cli_.Get("/with-range-customized-response",
|
||||
{{make_range_header({{1, 2}, {4, 5}})}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::BadRequest_400, res->status);
|
||||
EXPECT_EQ(true, res->has_header("Content-Length"));
|
||||
@@ -4984,6 +5156,63 @@ TEST(KeepAliveTest, SSLClientReconnection) {
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(StatusCode::OK_200, result->status);
|
||||
}
|
||||
|
||||
TEST(KeepAliveTest, SSLClientReconnectionPost) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
svr.set_keep_alive_timeout(1);
|
||||
std::string content = "reconnect";
|
||||
|
||||
svr.Post("/hi", [](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr] { svr.listen(HOST, PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
SSLClient cli(HOST, PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
cli.set_keep_alive(true);
|
||||
|
||||
auto result = cli.Post(
|
||||
"/hi", content.size(),
|
||||
[&content](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
sink.write(content.c_str(), content.size());
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
|
||||
// Recoonect
|
||||
result = cli.Post(
|
||||
"/hi", content.size(),
|
||||
[&content](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
sink.write(content.c_str(), content.size());
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
result = cli.Post(
|
||||
"/hi", content.size(),
|
||||
[&content](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
sink.write(content.c_str(), content.size());
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(ClientProblemDetectionTest, ContentProvider) {
|
||||
@@ -6958,166 +7187,6 @@ TEST(MultipartFormDataTest, ContentLength) {
|
||||
|
||||
#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, StatusCode::OK_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));
|
||||
}};
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(pathname_);
|
||||
}
|
||||
|
||||
#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));
|
||||
}};
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(pathname_);
|
||||
EXPECT_EQ(std::to_string(getpid()), remote_port_val);
|
||||
}
|
||||
#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));
|
||||
}};
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(abstract_addr);
|
||||
}
|
||||
#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
|
||||
|
||||
TEST(TaskQueueTest, IncreaseAtomicInteger) {
|
||||
static constexpr unsigned int number_of_tasks{1000000};
|
||||
std::atomic_uint count{0};
|
||||
@@ -7443,6 +7512,6 @@ TEST(UniversalClientImplTest, Ipv6LiteralAddress) {
|
||||
std::string ipV6TestURL = "http://[ff06::c3]";
|
||||
|
||||
Client cli(ipV6TestURL + ":" + std::to_string(port), CLIENT_CERT_FILE,
|
||||
CLIENT_PRIVATE_KEY_FILE);
|
||||
CLIENT_PRIVATE_KEY_FILE);
|
||||
EXPECT_EQ(cli.port(), port);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user