Compare commits

..

33 Commits

Author SHA1 Message Date
yhirose 7f6d413ddd Release v0.17.0 2024-09-01 07:53:56 -04:00
yhirose 88277139e7 Added set_ipv6_v6only method (#1905)
* Added `set_ipv6_v6only` method

* Adjust the place where socket_options is called
2024-09-01 07:52:24 -04:00
yhirose 6cdd3493a1 Fix #1788 2024-09-01 01:55:27 -04:00
yhirose 9c91b6f4a6 Fix #1645 2024-09-01 00:11:07 -04:00
yhirose cee838e335 Documentation 2024-08-31 17:42:43 -04:00
yhirose d82c82db2c Add sleep in handle_EINTR 2024-08-31 17:19:52 -04:00
yhirose ba638ff38e Update Docker support 2024-08-31 17:09:20 -04:00
yhirose da0c6579fa Breaking Change! get_header_ methods on Request and Response now take a default value. 2024-08-31 17:07:48 -04:00
yhirose 52a18c78a5 Add docker related files 2024-08-27 00:23:31 -04:00
yhirose 048edec9ed Changed CPPHTTPLIB_KEEPALIVE_MAX_COUNT to 100 2024-08-26 21:10:38 -04:00
yhirose af56b7ec0b Release v0.16.3 2024-08-17 09:53:26 -04:00
Jiwoo Park 6c3e8482f7 Fix KeepAliveTest.SSLClientReconnectionPost (#1895) 2024-08-10 07:19:59 -04:00
yhirose 390f2c41f6 Fix #1878 (#1893)
* Fix #1878
2024-08-08 22:07:46 -04:00
yhirose aa04feebb4 Fix warnings 2024-08-08 20:54:33 -04:00
yhirose 45f3694f82 Fix problem with clean command in Makefile 2024-08-08 19:30:46 -04:00
yhirose c5c54b31e2 Release v0.16.2 2024-08-08 11:48:50 -04:00
Mark Mentovai 69c84c9597 BoringSSL compatibility fixes (#1892)
This patch is necessary to build cpp-httplib in Crashpad, itself in
Chromium, using BoringSSL. Details at [1].

The fixes include:
 - Library version check: tolerate BoringSSL as an alternative to
   OpenSSL 3.
 - Don’t call `OPENSSL_thread_stop`, which is not in BoringSSL.
 - Use `SSL_get_peer_certificate` (deprecated in OpenSSL 3), the old
   name for `SSL_get1_peer_certificate`, because the new name is not in
   BoringSSL.
 - Call `SSL_set_tlsext_host_name` directly instead of making an
   `SSL_ctrl` call that BoringSSL does not support. The feared
   -Wold-style-cast warning that occurs when buidling with OpenSSL is
   not triggered in BoringSSL.

[1] https://chromium.googlesource.com/crashpad/crashpad/+/1a62a0182557c89494676c06611f1ca731bcb2db
2024-08-08 11:47:56 -04:00
yhirose ae63b89cbf Use SOCK_CLOEXEC instead of __linux__ 2024-08-06 17:31:55 -04:00
yhirose ff038f98b7 Merge branch 'thread-safe-cloexec' of github.com:kdombroski/cpp-httplib into kdombroski-thread-safe-cloexec 2024-08-06 17:22:43 -04:00
yhirose e00fd06355 Release v0.16.1 2024-08-06 17:04:22 -04:00
yhirose 521529d24d Fix #1481 (with content provider) (#1527)
* Fix #1481 (with content provider)

* Improve shutdown performance

* Make shutdown action more stable

* Move some tests up

* Simplified

* Simplified
2024-08-06 13:43:00 -04:00
yhirose ed0719f2bc Code format 2024-08-06 07:20:05 -04:00
hanslivingstone 6a848b1a16 Require a minimum of TLS 1.2 (#1889)
TLS 1. is deprecated: https://www.ietf.org/rfc/rfc8996.html
2024-07-30 17:18:33 -04:00
mol123 c8bcaf8a91 Fix build when targeting Windows 7 as platform. (#1869)
* Fix build when targeting Windows 7 as platform.

This change makes more of the code introduced in
https://github.com/yhirose/cpp-httplib/pull/1775
conditional on feature macros.

`CreateFile2`, `CreateFileMappingFromApp` and `MapViewOfFileFromApp` are
available only starting from Windows 8.

 * https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2
 * https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingfromapp
 * https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffilefromapp

* Update feature macros used and use `GetFileSizeEx` conditionally.
2024-07-02 10:58:40 -04:00
Hlado 8cd0ed0509 Added move assignment operator to Client class. (#1873) 2024-06-30 11:17:00 -04:00
Hlado 177d8420a1 Added .gitattributes file to prevent git from changing line endings (#1872)
of text files using as data for tests.
2024-06-30 11:16:48 -04:00
Daniel Ludwig 388a8c007c Fix build on Windows with no WINAPI_PARTITION_APP support (#1865) 2024-06-24 15:13:37 -04:00
Andrea Pappacoda bdefdce1ae test: fix GetRangeWithMaxLongLength on 32 bit machines (#1867)
The test used the hardcoded long value for 64 bit machines even on 32
bit ones, leading to test failures. With this patch the max long length
is obtained using std::numeric_limits<long>::max(). Thanks to q2a3z for
the hint!

Fixes: https://github.com/yhirose/cpp-httplib/issues/1795
2024-06-23 17:49:00 -04:00
Zhenlin Huang 9e4f93d87e Allow hex for ipv6 literal addr in redirect (#1859)
Co-authored-by: jaredhuang <jaredhuang@tencent.com>
2024-06-17 11:44:51 -04:00
yhirose 0b657d28cf Added example/one_time_request.cc. 2024-06-14 18:29:34 -04:00
Rainer Schielke c1a09daf15 avoid memory leaks if linked with static openssl libs (#1857)
* New function SSLServer::update_certs. Allows to update certificates while server is running

* New function SSLServer::update_certs. Added unit test

* avoid memory leaks if linked with static openssl libs

---------

Co-authored-by: CEU\schielke <Rainer.Schielke@heidelberg.com>
2024-06-14 15:40:03 -04:00
Karen Dombroski fb739dbaec threadsafe accept on windows, linux
* Windows has WSAAccept() which will create sockets inheriting flags from
  the server socket

* Linux has accept4() which has a flags argument supporting SOCK_CLOEXEC
2024-05-01 21:58:58 +12:00
Karen Dombroski 50fce538c6 threadsafe CLOEXEC on platforms that support it
SOCK_CLOEXEC is a flag available on some platforms to enable creation of
sockets with CLOEXEC already set
2024-05-01 21:46:50 +12:00
11 changed files with 772 additions and 313 deletions
+2
View File
@@ -0,0 +1,2 @@
/test/www*/dir/*.html text eol=lf
/test/www*/dir/*.txt text eol=lf
+2
View File
@@ -24,6 +24,7 @@ test/*.srl
*.swp
build/
Debug
Release
*.vcxproj.user
@@ -34,4 +35,5 @@ Release
ipch
*.dSYM
.*
!/.gitattributes
!/.travis.yml
+12
View File
@@ -0,0 +1,12 @@
FROM ubuntu AS builder
WORKDIR /build
COPY httplib.h .
COPY docker/main.cc .
RUN apt update && apt install g++ -y
RUN g++ -std=c++23 -static -o server -O2 -I. -DCPPHTTPLIB_USE_POLL main.cc && strip server
FROM scratch
COPY --from=builder /build/server /server
COPY docker/html/index.html /html/index.html
EXPOSE 80
CMD ["/server"]
+16
View File
@@ -848,6 +848,22 @@ $ ./split.py
Wrote out/httplib.h and out/httplib.cc
```
Dockerfile for Static HTTP Server
---------------------------------
Dockerfile for static HTTP server is available. Port number of this HTTP server is 80, and it serves static files from `/html` directory in the container.
```bash
> docker build -t cpp-httplib-server .
...
> docker run --init --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server
Serving HTTP on 0.0.0.0 port 80 ...
192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"
192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..."
192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 "-" "Mozilla/5.0 ..."
```
NOTE
----
+7
View File
@@ -0,0 +1,7 @@
services:
http:
build: .
ports:
- "8080:80"
volumes:
- ./docker/html:/html
+21
View File
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<title>Welcome to cpp-httplib!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to cpp-httplib!</h1>
<p>If you see this page, the cpp-httplib web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="https://github.com/yhirose/cpp-httplib">github.com/yhirose/cpp-httplib</a>.<br/>
<p><em>Thank you for using cpp-httplib.</em></p>
</body>
</html>
+77
View File
@@ -0,0 +1,77 @@
//
// main.cc
//
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
// MIT License
//
#include <chrono>
#include <ctime>
#include <format>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <httplib.h>
constexpr auto error_html = R"(<html>
<head><title>{} {}</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>cpp-httplib/{}</center>
</body>
</html>
)";
std::string time_local() {
auto p = std::chrono::system_clock::now();
auto t = std::chrono::system_clock::to_time_t(p);
std::stringstream ss;
ss << std::put_time(std::localtime(&t), "%d/%b/%Y:%H:%M:%S %z");
return ss.str();
}
std::string log(auto &req, auto &res) {
auto remote_user = "-"; // TODO:
auto request = std::format("{} {} {}", req.method, req.path, req.version);
auto body_bytes_sent = res.get_header_value("Content-Length");
auto http_referer = "-"; // TODO:
auto http_user_agent = req.get_header_value("User-Agent", "-");
// NOTE: From NGINX defualt access log format
// log_format combined '$remote_addr - $remote_user [$time_local] '
// '"$request" $status $body_bytes_sent '
// '"$http_referer" "$http_user_agent"';
return std::format(R"({} - {} [{}] "{}" {} {} "{}" "{}")", req.remote_addr,
remote_user, time_local(), request, res.status,
body_bytes_sent, http_referer, http_user_agent);
}
int main(int argc, const char **argv) {
auto base_dir = "./html";
auto host = "0.0.0.0";
auto port = 80;
httplib::Server svr;
svr.set_error_handler([](auto & /*req*/, auto &res) {
auto body =
std::format(error_html, res.status, httplib::status_message(res.status),
CPPHTTPLIB_VERSION);
res.set_content(body, "text/html");
});
svr.set_logger(
[](auto &req, auto &res) { std::cout << log(req, res) << std::endl; });
svr.set_mount_point("/", base_dir);
std::cout << std::format("Serving HTTP on {0} port {1} ...", host, port)
<< std::endl;
auto ret = svr.listen(host, port);
return ret ? 0 : 1;
}
+5 -3
View File
@@ -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
+56
View File
@@ -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();
}
+257 -108
View File
@@ -8,7 +8,7 @@
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION "0.16.0"
#define CPPHTTPLIB_VERSION "0.17.0"
/*
* Configuration
@@ -19,7 +19,7 @@
#endif
#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100
#endif
#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND
@@ -90,6 +90,10 @@
#define CPPHTTPLIB_TCP_NODELAY false
#endif
#ifndef CPPHTTPLIB_IPV6_V6ONLY
#define CPPHTTPLIB_IPV6_V6ONLY false
#endif
#ifndef CPPHTTPLIB_RECV_BUFSIZ
#define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u)
#endif
@@ -269,7 +273,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
@@ -560,8 +569,10 @@ struct Request {
#endif
bool has_header(const std::string &key) const;
std::string get_header_value(const std::string &key, size_t id = 0) const;
uint64_t get_header_value_u64(const std::string &key, size_t id = 0) const;
std::string get_header_value(const std::string &key, const char *def = "",
size_t id = 0) const;
uint64_t get_header_value_u64(const std::string &key, uint64_t def = 0,
size_t id = 0) const;
size_t get_header_value_count(const std::string &key) const;
void set_header(const std::string &key, const std::string &val);
@@ -592,8 +603,10 @@ struct Response {
std::string location; // Redirect location
bool has_header(const std::string &key) const;
std::string get_header_value(const std::string &key, size_t id = 0) const;
uint64_t get_header_value_u64(const std::string &key, size_t id = 0) const;
std::string get_header_value(const std::string &key, const char *def = "",
size_t id = 0) const;
uint64_t get_header_value_u64(const std::string &key, uint64_t def = 0,
size_t id = 0) const;
size_t get_header_value_count(const std::string &key) const;
void set_header(const std::string &key, const std::string &val);
@@ -726,6 +739,10 @@ private:
assert(true == static_cast<bool>(fn));
fn();
}
#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL)
OPENSSL_thread_stop();
#endif
}
ThreadPool &pool_;
@@ -887,6 +904,7 @@ public:
Server &set_address_family(int family);
Server &set_tcp_nodelay(bool on);
Server &set_ipv6_v6only(bool on);
Server &set_socket_options(SocketOptions socket_options);
Server &set_default_headers(Headers headers);
@@ -919,6 +937,7 @@ public:
bool is_running() const;
void wait_until_ready() const;
void stop();
void decommission();
std::function<TaskQueue *(void)> new_task_queue;
@@ -993,7 +1012,7 @@ private:
virtual bool process_and_close_socket(socket_t sock);
std::atomic<bool> is_running_{false};
std::atomic<bool> done_{false};
std::atomic<bool> is_decommisioned{false};
struct MountPointEntry {
std::string mount_point;
@@ -1026,6 +1045,7 @@ private:
int address_family_ = AF_UNSPEC;
bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
SocketOptions socket_options_ = default_socket_options;
Headers default_headers_;
@@ -1082,9 +1102,10 @@ public:
// Request Headers
bool has_request_header(const std::string &key) const;
std::string get_request_header_value(const std::string &key,
const char *def = "",
size_t id = 0) const;
uint64_t get_request_header_value_u64(const std::string &key,
size_t id = 0) const;
uint64_t def = 0, size_t id = 0) const;
size_t get_request_header_value_count(const std::string &key) const;
private:
@@ -1307,6 +1328,7 @@ public:
void set_address_family(int family);
void set_tcp_nodelay(bool on);
void set_ipv6_v6only(bool on);
void set_socket_options(SocketOptions socket_options);
void set_connection_timeout(time_t sec, time_t usec = 0);
@@ -1444,6 +1466,7 @@ protected:
int address_family_ = AF_UNSPEC;
bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
SocketOptions socket_options_ = nullptr;
bool compress_ = false;
@@ -1526,6 +1549,7 @@ public:
const std::string &client_key_path);
Client(Client &&) = default;
Client &operator=(Client &&) = default;
~Client();
@@ -1819,9 +1843,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;
@@ -1904,8 +1928,8 @@ inline void duration_to_sec_and_usec(const T &duration, U callback) {
}
inline uint64_t get_header_value_u64(const Headers &headers,
const std::string &key, size_t id,
uint64_t def) {
const std::string &key, uint64_t def,
size_t id) {
auto rng = headers.equal_range(key);
auto it = rng.first;
std::advance(it, static_cast<ssize_t>(id));
@@ -1918,13 +1942,13 @@ inline uint64_t get_header_value_u64(const Headers &headers,
} // namespace detail
inline uint64_t Request::get_header_value_u64(const std::string &key,
size_t id) const {
return detail::get_header_value_u64(headers, key, id, 0);
uint64_t def, size_t id) const {
return detail::get_header_value_u64(headers, key, def, id);
}
inline uint64_t Response::get_header_value_u64(const std::string &key,
size_t id) const {
return detail::get_header_value_u64(headers, key, id, 0);
uint64_t def, size_t id) const {
return detail::get_header_value_u64(headers, key, def, id);
}
template <typename... Args>
@@ -1952,19 +1976,19 @@ inline ssize_t Stream::write_format(const char *fmt, const Args &...args) {
}
inline void default_socket_options(socket_t sock) {
int yes = 1;
int opt = 1;
#ifdef _WIN32
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<const char *>(&yes), sizeof(yes));
reinterpret_cast<const char *>(&opt), sizeof(opt));
setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
reinterpret_cast<const char *>(&yes), sizeof(yes));
reinterpret_cast<const char *>(&opt), sizeof(opt));
#else
#ifdef SO_REUSEPORT
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
reinterpret_cast<const void *>(&yes), sizeof(yes));
reinterpret_cast<const void *>(&opt), sizeof(opt));
#else
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<const void *>(&yes), sizeof(yes));
reinterpret_cast<const void *>(&opt), sizeof(opt));
#endif
#endif
}
@@ -2109,8 +2133,9 @@ inline std::ostream &operator<<(std::ostream &os, const Error &obj) {
}
inline uint64_t Result::get_request_header_value_u64(const std::string &key,
uint64_t def,
size_t id) const {
return detail::get_header_value_u64(request_headers_, key, id, 0);
return detail::get_header_value_u64(request_headers_, key, def, id);
}
template <class Rep, class Period>
@@ -2202,15 +2227,18 @@ bool process_client_socket(socket_t sock, time_t read_timeout_sec,
time_t write_timeout_usec,
std::function<bool(Stream &)> callback);
socket_t create_client_socket(
const std::string &host, const std::string &ip, int port,
int address_family, bool tcp_nodelay, SocketOptions socket_options,
time_t connection_timeout_sec, time_t connection_timeout_usec,
time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
time_t write_timeout_usec, const std::string &intf, Error &error);
socket_t create_client_socket(const std::string &host, const std::string &ip,
int port, int address_family, bool tcp_nodelay,
bool ipv6_v6only, SocketOptions socket_options,
time_t connection_timeout_sec,
time_t connection_timeout_usec,
time_t read_timeout_sec, time_t read_timeout_usec,
time_t write_timeout_sec,
time_t write_timeout_usec,
const std::string &intf, Error &error);
const char *get_header_value(const Headers &headers, const std::string &key,
size_t id = 0, const char *def = nullptr);
const char *def, size_t id);
std::string params_to_query_str(const Params &params);
@@ -2819,24 +2847,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; }
@@ -2908,7 +2963,10 @@ template <typename T> inline ssize_t handle_EINTR(T fn) {
ssize_t res = 0;
while (true) {
res = fn();
if (res < 0 && errno == EINTR) { continue; }
if (res < 0 && errno == EINTR) {
std::this_thread::sleep_for(std::chrono::milliseconds{1});
continue;
}
break;
}
return res;
@@ -3192,7 +3250,7 @@ inline int shutdown_socket(socket_t sock) {
template <typename BindOrConnect>
socket_t create_socket(const std::string &host, const std::string &ip, int port,
int address_family, int socket_flags, bool tcp_nodelay,
SocketOptions socket_options,
bool ipv6_v6only, SocketOptions socket_options,
BindOrConnect bind_or_connect) {
// Get address info
const char *node = nullptr;
@@ -3219,7 +3277,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 +3293,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 +3342,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;
@@ -3286,36 +3361,39 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
#endif
if (tcp_nodelay) {
auto yes = 1;
auto opt = 1;
#ifdef _WIN32
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
reinterpret_cast<const char *>(&yes), sizeof(yes));
reinterpret_cast<const char *>(&opt), sizeof(opt));
#else
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
reinterpret_cast<const void *>(&yes), sizeof(yes));
reinterpret_cast<const void *>(&opt), sizeof(opt));
#endif
}
if (rp->ai_family == AF_INET6) {
auto opt = ipv6_v6only ? 1 : 0;
#ifdef _WIN32
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
reinterpret_cast<const char *>(&opt), sizeof(opt));
#else
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
reinterpret_cast<const void *>(&opt), sizeof(opt));
#endif
}
if (socket_options) { socket_options(sock); }
if (rp->ai_family == AF_INET6) {
auto no = 0;
#ifdef _WIN32
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
reinterpret_cast<const char *>(&no), sizeof(no));
#else
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
reinterpret_cast<const void *>(&no), sizeof(no));
#endif
}
// 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);
@@ -3410,13 +3488,15 @@ inline std::string if2ip(int address_family, const std::string &ifn) {
inline socket_t create_client_socket(
const std::string &host, const std::string &ip, int port,
int address_family, bool tcp_nodelay, SocketOptions socket_options,
time_t connection_timeout_sec, time_t connection_timeout_usec,
time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
int address_family, bool tcp_nodelay, bool ipv6_v6only,
SocketOptions socket_options, time_t connection_timeout_sec,
time_t connection_timeout_usec, time_t read_timeout_sec,
time_t read_timeout_usec, time_t write_timeout_sec,
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 {
host, ip, port, address_family, 0, tcp_nodelay, ipv6_v6only,
std::move(socket_options),
[&](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 +3520,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);
@@ -3888,8 +3971,8 @@ inline bool has_header(const Headers &headers, const std::string &key) {
}
inline const char *get_header_value(const Headers &headers,
const std::string &key, size_t id,
const char *def) {
const std::string &key, const char *def,
size_t id) {
auto rng = headers.equal_range(key);
auto it = rng.first;
std::advance(it, static_cast<ssize_t>(id));
@@ -4086,7 +4169,7 @@ inline bool read_content_chunked(Stream &strm, T &x,
inline bool is_chunked_transfer_encoding(const Headers &headers) {
return compare_case_ignore(
get_header_value(headers, "Transfer-Encoding", 0, ""), "chunked");
get_header_value(headers, "Transfer-Encoding", "", 0), "chunked");
}
template <typename T, typename U>
@@ -5429,8 +5512,8 @@ inline bool Request::has_header(const std::string &key) const {
}
inline std::string Request::get_header_value(const std::string &key,
size_t id) const {
return detail::get_header_value(headers, key, id, "");
const char *def, size_t id) const {
return detail::get_header_value(headers, key, def, id);
}
inline size_t Request::get_header_value_count(const std::string &key) const {
@@ -5494,8 +5577,9 @@ inline bool Response::has_header(const std::string &key) const {
}
inline std::string Response::get_header_value(const std::string &key,
const char *def,
size_t id) const {
return detail::get_header_value(headers, key, id, "");
return detail::get_header_value(headers, key, def, id);
}
inline size_t Response::get_header_value_count(const std::string &key) const {
@@ -5580,8 +5664,9 @@ inline bool Result::has_request_header(const std::string &key) const {
}
inline std::string Result::get_request_header_value(const std::string &key,
const char *def,
size_t id) const {
return detail::get_header_value(request_headers_, key, id, "");
return detail::get_header_value(request_headers_, key, def, id);
}
inline size_t
@@ -5989,6 +6074,11 @@ inline Server &Server::set_tcp_nodelay(bool on) {
return *this;
}
inline Server &Server::set_ipv6_v6only(bool on) {
ipv6_v6only_ = on;
return *this;
}
inline Server &Server::set_socket_options(SocketOptions socket_options) {
socket_options_ = std::move(socket_options);
return *this;
@@ -6040,27 +6130,27 @@ inline Server &Server::set_payload_max_length(size_t length) {
inline bool Server::bind_to_port(const std::string &host, int port,
int socket_flags) {
return bind_internal(host, port, socket_flags) >= 0;
auto ret = bind_internal(host, port, socket_flags);
if (ret == -1) { is_decommisioned = true; }
return ret >= 0;
}
inline int Server::bind_to_any_port(const std::string &host, int socket_flags) {
return bind_internal(host, 0, socket_flags);
auto ret = bind_internal(host, 0, socket_flags);
if (ret == -1) { is_decommisioned = true; }
return ret;
}
inline bool Server::listen_after_bind() {
auto se = detail::scope_exit([&]() { done_ = true; });
return listen_internal();
}
inline bool Server::listen_after_bind() { return listen_internal(); }
inline bool Server::listen(const std::string &host, int port,
int socket_flags) {
auto se = detail::scope_exit([&]() { done_ = true; });
return bind_to_port(host, port, socket_flags) && listen_internal();
}
inline bool Server::is_running() const { return is_running_; }
inline void Server::wait_until_ready() const {
while (!is_running() && !done_) {
while (!is_running_ && !is_decommisioned) {
std::this_thread::sleep_for(std::chrono::milliseconds{1});
}
}
@@ -6072,8 +6162,11 @@ inline void Server::stop() {
detail::shutdown_socket(sock);
detail::close_socket(sock);
}
is_decommisioned = false;
}
inline void Server::decommission() { is_decommisioned = true; }
inline bool Server::parse_request_line(const char *s, Request &req) const {
auto len = strlen(s);
if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; }
@@ -6416,8 +6509,8 @@ Server::create_server_socket(const std::string &host, int port,
SocketOptions socket_options) const {
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 {
ipv6_v6only_, std::move(socket_options),
[](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool {
if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
return false;
}
@@ -6428,6 +6521,8 @@ Server::create_server_socket(const std::string &host, int port,
inline int Server::bind_internal(const std::string &host, int port,
int socket_flags) {
if (is_decommisioned) { return -1; }
if (!is_valid()) { return -1; }
svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
@@ -6453,6 +6548,8 @@ inline int Server::bind_internal(const std::string &host, int port,
}
inline bool Server::listen_internal() {
if (is_decommisioned) { return false; }
auto ret = true;
is_running_ = true;
auto se = detail::scope_exit([&]() { is_running_ = false; });
@@ -6473,7 +6570,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) {
@@ -6533,6 +6639,7 @@ inline bool Server::listen_internal() {
task_queue->shutdown();
}
is_decommisioned = !ret;
return ret;
}
@@ -6952,6 +7059,7 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
url_encode_ = rhs.url_encode_;
address_family_ = rhs.address_family_;
tcp_nodelay_ = rhs.tcp_nodelay_;
ipv6_v6only_ = rhs.ipv6_v6only_;
socket_options_ = rhs.socket_options_;
compress_ = rhs.compress_;
decompress_ = rhs.decompress_;
@@ -6980,9 +7088,9 @@ inline socket_t ClientImpl::create_client_socket(Error &error) const {
if (!proxy_host_.empty() && proxy_port_ != -1) {
return detail::create_client_socket(
proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_,
socket_options_, connection_timeout_sec_, connection_timeout_usec_,
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
write_timeout_usec_, interface_, error);
ipv6_v6only_, socket_options_, connection_timeout_sec_,
connection_timeout_usec_, read_timeout_sec_, read_timeout_usec_,
write_timeout_sec_, write_timeout_usec_, interface_, error);
}
// Check is custom IP specified for host_
@@ -6991,10 +7099,10 @@ inline socket_t ClientImpl::create_client_socket(Error &error) const {
if (it != addr_map_.end()) { ip = it->second; }
return detail::create_client_socket(
host_, ip, port_, address_family_, tcp_nodelay_, socket_options_,
connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_,
read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_,
error);
host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_,
socket_options_, connection_timeout_sec_, connection_timeout_usec_,
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
write_timeout_usec_, interface_, error);
}
inline bool ClientImpl::create_and_connect_socket(Socket &socket,
@@ -7271,7 +7379,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; }
@@ -7631,6 +7739,14 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
return ret;
};
if (res.has_header("Content-Length")) {
if (!req.content_receiver) {
auto len = std::min<size_t>(res.get_header_value_u64("Content-Length"),
res.body.max_size());
if (len > 0) { res.body.reserve(len); }
}
}
int dummy_status;
if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
dummy_status, std::move(progress), std::move(out),
@@ -8157,7 +8273,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);
}
@@ -8389,6 +8506,8 @@ inline void ClientImpl::set_address_family(int family) {
inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
inline void ClientImpl::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; }
inline void ClientImpl::set_socket_options(SocketOptions socket_options) {
socket_options_ = std::move(socket_options);
}
@@ -8508,13 +8627,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 +8825,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 +8857,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 +8891,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 +8928,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 +9182,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 +9214,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 +9410,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 +9690,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 +9701,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 +9722,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 +9762,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 +9773,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 +9794,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) {
+317 -202
View File
@@ -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;
@@ -301,25 +467,25 @@ TEST(ParseMultipartBoundaryTest, ValueWithQuotesAndCharset) {
TEST(GetHeaderValueTest, DefaultValue) {
Headers headers = {{"Dummy", "Dummy"}};
auto val = detail::get_header_value(headers, "Content-Type", 0, "text/plain");
auto val = detail::get_header_value(headers, "Content-Type", "text/plain", 0);
EXPECT_STREQ("text/plain", val);
}
TEST(GetHeaderValueTest, DefaultValueInt) {
Headers headers = {{"Dummy", "Dummy"}};
auto val = detail::get_header_value_u64(headers, "Content-Length", 0, 100);
auto val = detail::get_header_value_u64(headers, "Content-Length", 100, 0);
EXPECT_EQ(100ull, val);
}
TEST(GetHeaderValueTest, RegularValue) {
Headers headers = {{"Content-Type", "text/html"}, {"Dummy", "Dummy"}};
auto val = detail::get_header_value(headers, "Content-Type", 0, "text/plain");
auto val = detail::get_header_value(headers, "Content-Type", "text/plain", 0);
EXPECT_STREQ("text/html", val);
}
TEST(GetHeaderValueTest, RegularValueWithDifferentCase) {
Headers headers = {{"Content-Type", "text/html"}, {"Dummy", "Dummy"}};
auto val = detail::get_header_value(headers, "content-type", 0, "text/plain");
auto val = detail::get_header_value(headers, "content-type", "text/plain", 0);
EXPECT_STREQ("text/html", val);
}
@@ -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"));
@@ -4480,7 +4652,7 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
auto error = Error::Success;
auto client_sock = detail::create_client_socket(
HOST, "", PORT, AF_UNSPEC, false, nullptr,
HOST, "", PORT, AF_UNSPEC, false, false, nullptr,
/*connection_timeout_sec=*/5, 0,
/*read_timeout_sec=*/5, 0,
/*write_timeout_sec=*/5, 0, std::string(), error);
@@ -4754,6 +4926,52 @@ TEST(ServerStopTest, ListenFailure) {
t.join();
}
TEST(ServerStopTest, Decommision) {
Server svr;
svr.Get("/hi", [&](const Request &, Response &res) { res.body = "hi..."; });
for (int i = 0; i < 4; i++) {
auto is_even = !(i % 2);
std::thread t{[&] {
try {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (is_even) {
throw std::runtime_error("Some thing that happens to go wrong.");
}
svr.listen(HOST, PORT);
} catch (...) { svr.decommission(); }
}};
svr.wait_until_ready();
// Server is up
{
Client cli(HOST, PORT);
auto res = cli.Get("/hi");
if (is_even) {
EXPECT_FALSE(res);
} else {
EXPECT_TRUE(res);
EXPECT_EQ("hi...", res->body);
}
}
svr.stop();
t.join();
// Server is down...
{
Client cli(HOST, PORT);
auto res = cli.Get("/hi");
EXPECT_FALSE(res);
}
}
}
TEST(StreamingTest, NoContentLengthStreaming) {
Server svr;
@@ -4984,6 +5202,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 +7233,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 +7558,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);
}