|
|
@@ -8,8 +8,8 @@
|
|
|
#ifndef CPPHTTPLIB_HTTPLIB_H
|
|
|
#define CPPHTTPLIB_HTTPLIB_H
|
|
|
|
|
|
-#define CPPHTTPLIB_VERSION "0.37.2"
|
|
|
-#define CPPHTTPLIB_VERSION_NUM "0x002502"
|
|
|
+#define CPPHTTPLIB_VERSION "0.38.0"
|
|
|
+#define CPPHTTPLIB_VERSION_NUM "0x002600"
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00
|
|
|
@@ -1670,6 +1670,11 @@ public:
|
|
|
|
|
|
Server &set_payload_max_length(size_t length);
|
|
|
|
|
|
+ Server &set_websocket_ping_interval(time_t sec);
|
|
|
+ template <class Rep, class Period>
|
|
|
+ Server &set_websocket_ping_interval(
|
|
|
+ const std::chrono::duration<Rep, Period> &duration);
|
|
|
+
|
|
|
bool bind_to_port(const std::string &host, int port, int socket_flags = 0);
|
|
|
int bind_to_any_port(const std::string &host, int socket_flags = 0);
|
|
|
bool listen_after_bind();
|
|
|
@@ -1704,6 +1709,8 @@ protected:
|
|
|
time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND;
|
|
|
time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND;
|
|
|
size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
|
|
|
+ time_t websocket_ping_interval_sec_ =
|
|
|
+ CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND;
|
|
|
|
|
|
private:
|
|
|
using Handlers =
|
|
|
@@ -1879,7 +1886,8 @@ private:
|
|
|
|
|
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
|
|
public:
|
|
|
- [[deprecated("Use ssl_backend_error() instead")]]
|
|
|
+ [[deprecated("Use ssl_backend_error() instead. "
|
|
|
+ "This function will be removed by v1.0.0.")]]
|
|
|
uint64_t ssl_openssl_error() const {
|
|
|
return ssl_backend_error_;
|
|
|
}
|
|
|
@@ -2191,6 +2199,10 @@ protected:
|
|
|
|
|
|
virtual bool create_and_connect_socket(Socket &socket, Error &error);
|
|
|
virtual bool ensure_socket_connection(Socket &socket, Error &error);
|
|
|
+ virtual bool setup_proxy_connection(
|
|
|
+ Socket &socket,
|
|
|
+ std::chrono::time_point<std::chrono::steady_clock> start_time,
|
|
|
+ Response &res, bool &success, Error &error);
|
|
|
|
|
|
// All of:
|
|
|
// shutdown_ssl
|
|
|
@@ -2355,13 +2367,16 @@ protected:
|
|
|
|
|
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
|
|
public:
|
|
|
- [[deprecated("Use load_ca_cert_store() instead")]]
|
|
|
+ [[deprecated("Use load_ca_cert_store() instead. "
|
|
|
+ "This function will be removed by v1.0.0.")]]
|
|
|
void set_ca_cert_store(X509_STORE *ca_cert_store);
|
|
|
|
|
|
- [[deprecated("Use tls::create_ca_store() instead")]]
|
|
|
+ [[deprecated("Use tls::create_ca_store() instead. "
|
|
|
+ "This function will be removed by v1.0.0.")]]
|
|
|
X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size) const;
|
|
|
|
|
|
- [[deprecated("Use set_server_certificate_verifier(VerifyCallback) instead")]]
|
|
|
+ [[deprecated("Use set_server_certificate_verifier(VerifyCallback) instead. "
|
|
|
+ "This function will be removed by v1.0.0.")]]
|
|
|
virtual void set_server_certificate_verifier(
|
|
|
std::function<SSLVerifierResponse(SSL *ssl)> verifier);
|
|
|
#endif
|
|
|
@@ -2590,14 +2605,17 @@ private:
|
|
|
|
|
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
|
|
public:
|
|
|
- [[deprecated("Use tls_context() instead")]]
|
|
|
+ [[deprecated("Use tls_context() instead. "
|
|
|
+ "This function will be removed by v1.0.0.")]]
|
|
|
SSL_CTX *ssl_context() const;
|
|
|
|
|
|
- [[deprecated("Use set_session_verifier(session_t) instead")]]
|
|
|
+ [[deprecated("Use set_session_verifier(session_t) instead. "
|
|
|
+ "This function will be removed by v1.0.0.")]]
|
|
|
void set_server_certificate_verifier(
|
|
|
std::function<SSLVerifierResponse(SSL *ssl)> verifier);
|
|
|
|
|
|
- [[deprecated("Use Result::ssl_backend_error() instead")]]
|
|
|
+ [[deprecated("Use Result::ssl_backend_error() instead. "
|
|
|
+ "This function will be removed by v1.0.0.")]]
|
|
|
long get_verify_result() const;
|
|
|
#endif
|
|
|
};
|
|
|
@@ -2649,18 +2667,22 @@ private:
|
|
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
|
|
public:
|
|
|
[[deprecated("Use SSLServer(PemMemory) or "
|
|
|
- "SSLServer(ContextSetupCallback) instead")]]
|
|
|
+ "SSLServer(ContextSetupCallback) instead. "
|
|
|
+ "This constructor will be removed by v1.0.0.")]]
|
|
|
SSLServer(X509 *cert, EVP_PKEY *private_key,
|
|
|
X509_STORE *client_ca_cert_store = nullptr);
|
|
|
|
|
|
- [[deprecated("Use SSLServer(ContextSetupCallback) instead")]]
|
|
|
+ [[deprecated("Use SSLServer(ContextSetupCallback) instead. "
|
|
|
+ "This constructor will be removed by v1.0.0.")]]
|
|
|
SSLServer(
|
|
|
const std::function<bool(SSL_CTX &ssl_ctx)> &setup_ssl_ctx_callback);
|
|
|
|
|
|
- [[deprecated("Use tls_context() instead")]]
|
|
|
+ [[deprecated("Use tls_context() instead. "
|
|
|
+ "This function will be removed by v1.0.0.")]]
|
|
|
SSL_CTX *ssl_context() const;
|
|
|
|
|
|
- [[deprecated("Use update_certs_pem() instead")]]
|
|
|
+ [[deprecated("Use update_certs_pem() instead. "
|
|
|
+ "This function will be removed by v1.0.0.")]]
|
|
|
void update_certs(X509 *cert, EVP_PKEY *private_key,
|
|
|
X509_STORE *client_ca_cert_store = nullptr);
|
|
|
#endif
|
|
|
@@ -2717,6 +2739,10 @@ private:
|
|
|
std::function<bool(Stream &strm)> callback) override;
|
|
|
bool is_ssl() const override;
|
|
|
|
|
|
+ bool setup_proxy_connection(
|
|
|
+ Socket &socket,
|
|
|
+ std::chrono::time_point<std::chrono::steady_clock> start_time,
|
|
|
+ Response &res, bool &success, Error &error) override;
|
|
|
bool connect_with_proxy(
|
|
|
Socket &sock,
|
|
|
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
|
|
@@ -2741,18 +2767,22 @@ private:
|
|
|
|
|
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
|
|
public:
|
|
|
- [[deprecated("Use SSLClient(host, port, PemMemory) instead")]]
|
|
|
+ [[deprecated("Use SSLClient(host, port, PemMemory) instead. "
|
|
|
+ "This constructor will be removed by v1.0.0.")]]
|
|
|
explicit SSLClient(const std::string &host, int port, X509 *client_cert,
|
|
|
EVP_PKEY *client_key,
|
|
|
const std::string &private_key_password = std::string());
|
|
|
|
|
|
- [[deprecated("Use Result::ssl_backend_error() instead")]]
|
|
|
+ [[deprecated("Use Result::ssl_backend_error() instead. "
|
|
|
+ "This function will be removed by v1.0.0.")]]
|
|
|
long get_verify_result() const;
|
|
|
|
|
|
- [[deprecated("Use tls_context() instead")]]
|
|
|
+ [[deprecated("Use tls_context() instead. "
|
|
|
+ "This function will be removed by v1.0.0.")]]
|
|
|
SSL_CTX *ssl_context() const;
|
|
|
|
|
|
- [[deprecated("Use set_session_verifier(session_t) instead")]]
|
|
|
+ [[deprecated("Use set_session_verifier(session_t) instead. "
|
|
|
+ "This function will be removed by v1.0.0.")]]
|
|
|
void set_server_certificate_verifier(
|
|
|
std::function<SSLVerifierResponse(SSL *ssl)> verifier) override;
|
|
|
|
|
|
@@ -3721,15 +3751,19 @@ private:
|
|
|
friend class httplib::Server;
|
|
|
friend class WebSocketClient;
|
|
|
|
|
|
- WebSocket(Stream &strm, const Request &req, bool is_server)
|
|
|
- : strm_(strm), req_(req), is_server_(is_server) {
|
|
|
+ WebSocket(
|
|
|
+ Stream &strm, const Request &req, bool is_server,
|
|
|
+ time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND)
|
|
|
+ : strm_(strm), req_(req), is_server_(is_server),
|
|
|
+ ping_interval_sec_(ping_interval_sec) {
|
|
|
start_heartbeat();
|
|
|
}
|
|
|
|
|
|
- WebSocket(std::unique_ptr<Stream> &&owned_strm, const Request &req,
|
|
|
- bool is_server)
|
|
|
+ WebSocket(
|
|
|
+ std::unique_ptr<Stream> &&owned_strm, const Request &req, bool is_server,
|
|
|
+ time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND)
|
|
|
: strm_(*owned_strm), owned_strm_(std::move(owned_strm)), req_(req),
|
|
|
- is_server_(is_server) {
|
|
|
+ is_server_(is_server), ping_interval_sec_(ping_interval_sec) {
|
|
|
start_heartbeat();
|
|
|
}
|
|
|
|
|
|
@@ -3740,6 +3774,7 @@ private:
|
|
|
std::unique_ptr<Stream> owned_strm_;
|
|
|
Request req_;
|
|
|
bool is_server_;
|
|
|
+ time_t ping_interval_sec_;
|
|
|
std::atomic<bool> closed_{false};
|
|
|
std::mutex write_mutex_;
|
|
|
std::thread ping_thread_;
|
|
|
@@ -3768,6 +3803,7 @@ public:
|
|
|
const std::string &subprotocol() const;
|
|
|
void set_read_timeout(time_t sec, time_t usec = 0);
|
|
|
void set_write_timeout(time_t sec, time_t usec = 0);
|
|
|
+ void set_websocket_ping_interval(time_t sec);
|
|
|
|
|
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
|
|
void set_ca_cert_path(const std::string &path);
|
|
|
@@ -3791,6 +3827,8 @@ private:
|
|
|
time_t read_timeout_usec_ = 0;
|
|
|
time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND;
|
|
|
time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND;
|
|
|
+ time_t websocket_ping_interval_sec_ =
|
|
|
+ CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND;
|
|
|
|
|
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
|
|
bool is_ssl_ = false;
|
|
|
@@ -6463,33 +6501,114 @@ inline bool can_compress_content_type(const std::string &content_type) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+inline bool parse_quality(const char *b, const char *e, std::string &token,
|
|
|
+ double &quality) {
|
|
|
+ quality = 1.0;
|
|
|
+ token.clear();
|
|
|
+
|
|
|
+ // Split on first ';': left = token name, right = parameters
|
|
|
+ const char *params_b = nullptr;
|
|
|
+ std::size_t params_len = 0;
|
|
|
+
|
|
|
+ divide(
|
|
|
+ b, static_cast<std::size_t>(e - b), ';',
|
|
|
+ [&](const char *lb, std::size_t llen, const char *rb, std::size_t rlen) {
|
|
|
+ auto r = trim(lb, lb + llen, 0, llen);
|
|
|
+ if (r.first < r.second) { token.assign(lb + r.first, lb + r.second); }
|
|
|
+ params_b = rb;
|
|
|
+ params_len = rlen;
|
|
|
+ });
|
|
|
+
|
|
|
+ if (token.empty()) { return false; }
|
|
|
+ if (params_len == 0) { return true; }
|
|
|
+
|
|
|
+ // Scan parameters for q= (stops on first match)
|
|
|
+ bool invalid = false;
|
|
|
+ split_find(params_b, params_b + params_len, ';',
|
|
|
+ (std::numeric_limits<size_t>::max)(),
|
|
|
+ [&](const char *pb, const char *pe) -> bool {
|
|
|
+ // Match exactly "q=" or "Q=" (not "query=" etc.)
|
|
|
+ auto len = static_cast<size_t>(pe - pb);
|
|
|
+ if (len < 2) { return false; }
|
|
|
+ if ((pb[0] != 'q' && pb[0] != 'Q') || pb[1] != '=') {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Trim the value portion
|
|
|
+ auto r = trim(pb, pe, 2, len);
|
|
|
+ if (r.first >= r.second) {
|
|
|
+ invalid = true;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ double v = 0.0;
|
|
|
+ auto res = from_chars(pb + r.first, pb + r.second, v);
|
|
|
+ if (res.ec != std::errc{} || v < 0.0 || v > 1.0) {
|
|
|
+ invalid = true;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ quality = v;
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+
|
|
|
+ return !invalid;
|
|
|
+}
|
|
|
+
|
|
|
inline EncodingType encoding_type(const Request &req, const Response &res) {
|
|
|
- auto ret =
|
|
|
- detail::can_compress_content_type(res.get_header_value("Content-Type"));
|
|
|
- if (!ret) { return EncodingType::None; }
|
|
|
+ if (!can_compress_content_type(res.get_header_value("Content-Type"))) {
|
|
|
+ return EncodingType::None;
|
|
|
+ }
|
|
|
|
|
|
const auto &s = req.get_header_value("Accept-Encoding");
|
|
|
- (void)(s);
|
|
|
+ if (s.empty()) { return EncodingType::None; }
|
|
|
|
|
|
+ // Single-pass: iterate tokens and track the best supported encoding.
|
|
|
+ // Server preference breaks ties (br > gzip > zstd).
|
|
|
+ EncodingType best = EncodingType::None;
|
|
|
+ double best_q = 0.0; // q=0 means "not acceptable"
|
|
|
+
|
|
|
+ // Server preference: Brotli > Gzip > Zstd (lower = more preferred)
|
|
|
+ auto priority = [](EncodingType t) -> int {
|
|
|
+ switch (t) {
|
|
|
+ case EncodingType::Brotli: return 0;
|
|
|
+ case EncodingType::Gzip: return 1;
|
|
|
+ case EncodingType::Zstd: return 2;
|
|
|
+ default: return 3;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ std::string name;
|
|
|
+ split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) {
|
|
|
+ double quality = 1.0;
|
|
|
+ if (!parse_quality(b, e, name, quality)) { return; }
|
|
|
+ if (quality <= 0.0) { return; }
|
|
|
+
|
|
|
+ EncodingType type = EncodingType::None;
|
|
|
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
|
|
|
- // TODO: 'Accept-Encoding' has br, not br;q=0
|
|
|
- ret = s.find("br") != std::string::npos;
|
|
|
- if (ret) { return EncodingType::Brotli; }
|
|
|
+ if (case_ignore::equal(name, "br")) { type = EncodingType::Brotli; }
|
|
|
#endif
|
|
|
-
|
|
|
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
|
|
- // TODO: 'Accept-Encoding' has gzip, not gzip;q=0
|
|
|
- ret = s.find("gzip") != std::string::npos;
|
|
|
- if (ret) { return EncodingType::Gzip; }
|
|
|
+ if (type == EncodingType::None && case_ignore::equal(name, "gzip")) {
|
|
|
+ type = EncodingType::Gzip;
|
|
|
+ }
|
|
|
#endif
|
|
|
-
|
|
|
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
|
|
|
- // TODO: 'Accept-Encoding' has zstd, not zstd;q=0
|
|
|
- ret = s.find("zstd") != std::string::npos;
|
|
|
- if (ret) { return EncodingType::Zstd; }
|
|
|
+ if (type == EncodingType::None && case_ignore::equal(name, "zstd")) {
|
|
|
+ type = EncodingType::Zstd;
|
|
|
+ }
|
|
|
#endif
|
|
|
|
|
|
- return EncodingType::None;
|
|
|
+ if (type == EncodingType::None) { return; }
|
|
|
+
|
|
|
+ // Higher q-value wins; for equal q, server preference breaks ties
|
|
|
+ if (quality > best_q ||
|
|
|
+ (quality == best_q && priority(type) < priority(best))) {
|
|
|
+ best_q = quality;
|
|
|
+ best = type;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return best;
|
|
|
}
|
|
|
|
|
|
inline bool nocompressor::compress(const char *data, size_t data_length,
|
|
|
@@ -6773,6 +6892,21 @@ create_decompressor(const std::string &encoding) {
|
|
|
return decompressor;
|
|
|
}
|
|
|
|
|
|
+// Returns the best available compressor and its Content-Encoding name.
|
|
|
+// Priority: Brotli > Gzip > Zstd (matches server-side preference).
|
|
|
+inline std::pair<std::unique_ptr<compressor>, const char *>
|
|
|
+create_compressor() {
|
|
|
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
|
|
|
+ return {detail::make_unique<brotli_compressor>(), "br"};
|
|
|
+#elif defined(CPPHTTPLIB_ZLIB_SUPPORT)
|
|
|
+ return {detail::make_unique<gzip_compressor>(), "gzip"};
|
|
|
+#elif defined(CPPHTTPLIB_ZSTD_SUPPORT)
|
|
|
+ return {detail::make_unique<zstd_compressor>(), "zstd"};
|
|
|
+#else
|
|
|
+ return {nullptr, nullptr};
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
inline bool is_prohibited_header_name(const std::string &name) {
|
|
|
using udl::operator""_t;
|
|
|
|
|
|
@@ -7605,7 +7739,7 @@ inline bool parse_accept_header(const std::string &s,
|
|
|
struct AcceptEntry {
|
|
|
std::string media_type;
|
|
|
double quality;
|
|
|
- int order; // Original order in header
|
|
|
+ int order;
|
|
|
};
|
|
|
|
|
|
std::vector<AcceptEntry> entries;
|
|
|
@@ -7623,48 +7757,12 @@ inline bool parse_accept_header(const std::string &s,
|
|
|
}
|
|
|
|
|
|
AcceptEntry accept_entry;
|
|
|
- accept_entry.quality = 1.0; // Default quality
|
|
|
accept_entry.order = order++;
|
|
|
|
|
|
- // Find q= parameter
|
|
|
- auto q_pos = entry.find(";q=");
|
|
|
- if (q_pos == std::string::npos) { q_pos = entry.find("; q="); }
|
|
|
-
|
|
|
- if (q_pos != std::string::npos) {
|
|
|
- // Extract media type (before q parameter)
|
|
|
- accept_entry.media_type = trim_copy(entry.substr(0, q_pos));
|
|
|
-
|
|
|
- // Extract quality value
|
|
|
- auto q_start = entry.find('=', q_pos) + 1;
|
|
|
- auto q_end = entry.find(';', q_start);
|
|
|
- if (q_end == std::string::npos) { q_end = entry.length(); }
|
|
|
-
|
|
|
- std::string quality_str =
|
|
|
- trim_copy(entry.substr(q_start, q_end - q_start));
|
|
|
- if (quality_str.empty()) {
|
|
|
- has_invalid_entry = true;
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- {
|
|
|
- double v = 0.0;
|
|
|
- auto res = detail::from_chars(
|
|
|
- quality_str.data(), quality_str.data() + quality_str.size(), v);
|
|
|
- if (res.ec == std::errc{}) {
|
|
|
- accept_entry.quality = v;
|
|
|
- } else {
|
|
|
- has_invalid_entry = true;
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- // Check if quality is in valid range [0.0, 1.0]
|
|
|
- if (accept_entry.quality < 0.0 || accept_entry.quality > 1.0) {
|
|
|
- has_invalid_entry = true;
|
|
|
- return;
|
|
|
- }
|
|
|
- } else {
|
|
|
- // No quality parameter, use entire entry as media type
|
|
|
- accept_entry.media_type = entry;
|
|
|
+ if (!parse_quality(entry.data(), entry.data() + entry.size(),
|
|
|
+ accept_entry.media_type, accept_entry.quality)) {
|
|
|
+ has_invalid_entry = true;
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
// Remove additional parameters from media type
|
|
|
@@ -10746,6 +10844,20 @@ inline Server &Server::set_payload_max_length(size_t length) {
|
|
|
return *this;
|
|
|
}
|
|
|
|
|
|
+inline Server &Server::set_websocket_ping_interval(time_t sec) {
|
|
|
+ websocket_ping_interval_sec_ = sec;
|
|
|
+ return *this;
|
|
|
+}
|
|
|
+
|
|
|
+template <class Rep, class Period>
|
|
|
+inline Server &Server::set_websocket_ping_interval(
|
|
|
+ const std::chrono::duration<Rep, Period> &duration) {
|
|
|
+ detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t /*usec*/) {
|
|
|
+ set_websocket_ping_interval(sec);
|
|
|
+ });
|
|
|
+ return *this;
|
|
|
+}
|
|
|
+
|
|
|
inline bool Server::bind_to_port(const std::string &host, int port,
|
|
|
int socket_flags) {
|
|
|
auto ret = bind_internal(host, port, socket_flags);
|
|
|
@@ -11896,7 +12008,7 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
|
|
{
|
|
|
// Use WebSocket-specific read timeout instead of HTTP timeout
|
|
|
strm.set_read_timeout(CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND, 0);
|
|
|
- ws::WebSocket ws(strm, req, true);
|
|
|
+ ws::WebSocket ws(strm, req, true, websocket_ping_interval_sec_);
|
|
|
entry.handler(req, ws);
|
|
|
}
|
|
|
return true;
|
|
|
@@ -12140,6 +12252,13 @@ inline bool ClientImpl::ensure_socket_connection(Socket &socket, Error &error) {
|
|
|
return create_and_connect_socket(socket, error);
|
|
|
}
|
|
|
|
|
|
+inline bool ClientImpl::setup_proxy_connection(
|
|
|
+ Socket & /*socket*/,
|
|
|
+ std::chrono::time_point<std::chrono::steady_clock> /*start_time*/,
|
|
|
+ Response & /*res*/, bool & /*success*/, Error & /*error*/) {
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
inline void ClientImpl::shutdown_ssl(Socket & /*socket*/,
|
|
|
bool /*shutdown_gracefully*/) {
|
|
|
// If there are any requests in flight from threads other than us, then it's
|
|
|
@@ -12261,27 +12380,14 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
-#ifdef CPPHTTPLIB_SSL_ENABLED
|
|
|
- // TODO: refactoring
|
|
|
- if (is_ssl()) {
|
|
|
- auto &scli = static_cast<SSLClient &>(*this);
|
|
|
- if (!proxy_host_.empty() && proxy_port_ != -1) {
|
|
|
- auto success = false;
|
|
|
- if (!scli.connect_with_proxy(socket_, req.start_time_, res, success,
|
|
|
- error)) {
|
|
|
- if (!success) { output_error_log(error, &req); }
|
|
|
- return success;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (!proxy_host_.empty() && proxy_port_ != -1) {
|
|
|
- if (!scli.initialize_ssl(socket_, error)) {
|
|
|
- output_error_log(error, &req);
|
|
|
- return false;
|
|
|
- }
|
|
|
+ {
|
|
|
+ auto success = true;
|
|
|
+ if (!setup_proxy_connection(socket_, req.start_time_, res, success,
|
|
|
+ error)) {
|
|
|
+ if (!success) { output_error_log(error, &req); }
|
|
|
+ return success;
|
|
|
}
|
|
|
}
|
|
|
-#endif
|
|
|
}
|
|
|
|
|
|
// Mark the current socket as being in use so that it cannot be closed by
|
|
|
@@ -12442,17 +12548,15 @@ ClientImpl::open_stream(const std::string &method, const std::string &path,
|
|
|
return handle;
|
|
|
}
|
|
|
|
|
|
-#ifdef CPPHTTPLIB_SSL_ENABLED
|
|
|
- if (is_ssl()) {
|
|
|
- auto &scli = static_cast<SSLClient &>(*this);
|
|
|
- if (!proxy_host_.empty() && proxy_port_ != -1) {
|
|
|
- if (!scli.initialize_ssl(socket_, handle.error)) {
|
|
|
- handle.response.reset();
|
|
|
- return handle;
|
|
|
- }
|
|
|
+ {
|
|
|
+ auto success = true;
|
|
|
+ auto start_time = std::chrono::steady_clock::now();
|
|
|
+ if (!setup_proxy_connection(socket_, start_time, *handle.response,
|
|
|
+ success, handle.error)) {
|
|
|
+ if (!success) { handle.response.reset(); }
|
|
|
+ return handle;
|
|
|
}
|
|
|
}
|
|
|
-#endif
|
|
|
}
|
|
|
|
|
|
transfer_socket_ownership_to_handle(handle);
|
|
|
@@ -12731,7 +12835,7 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req,
|
|
|
|
|
|
if (res.get_header_value("Connection") == "close" ||
|
|
|
(res.version == "HTTP/1.0" && res.reason != "Connection established")) {
|
|
|
- // TODO this requires a not-entirely-obvious chain of calls to be correct
|
|
|
+ // NOTE: this requires a not-entirely-obvious chain of calls to be correct
|
|
|
// for this to be safe.
|
|
|
|
|
|
// This is safe to call because handle_request is only called by send_
|
|
|
@@ -12970,14 +13074,9 @@ inline bool ClientImpl::write_content_with_provider(Stream &strm,
|
|
|
auto is_shutting_down = []() { return false; };
|
|
|
|
|
|
if (req.is_chunked_content_provider_) {
|
|
|
- // TODO: Brotli support
|
|
|
- std::unique_ptr<detail::compressor> compressor;
|
|
|
-#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
|
|
- if (compress_) {
|
|
|
- compressor = detail::make_unique<detail::gzip_compressor>();
|
|
|
- } else
|
|
|
-#endif
|
|
|
- {
|
|
|
+ auto compressor = compress_ ? detail::create_compressor().first
|
|
|
+ : std::unique_ptr<detail::compressor>();
|
|
|
+ if (!compressor) {
|
|
|
compressor = detail::make_unique<detail::nocompressor>();
|
|
|
}
|
|
|
|
|
|
@@ -13208,14 +13307,15 @@ ClientImpl::send_with_content_provider_and_receiver(
|
|
|
Error &error) {
|
|
|
if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
|
|
|
|
|
|
-#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
|
|
- if (compress_) { req.set_header("Content-Encoding", "gzip"); }
|
|
|
-#endif
|
|
|
+ auto enc = compress_
|
|
|
+ ? detail::create_compressor()
|
|
|
+ : std::pair<std::unique_ptr<detail::compressor>, const char *>(
|
|
|
+ nullptr, nullptr);
|
|
|
|
|
|
-#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
|
|
- if (compress_ && !content_provider_without_length) {
|
|
|
- // TODO: Brotli support
|
|
|
- detail::gzip_compressor compressor;
|
|
|
+ if (enc.second) { req.set_header("Content-Encoding", enc.second); }
|
|
|
+
|
|
|
+ if (enc.first && !content_provider_without_length) {
|
|
|
+ auto &compressor = enc.first;
|
|
|
|
|
|
if (content_provider) {
|
|
|
auto ok = true;
|
|
|
@@ -13226,7 +13326,7 @@ ClientImpl::send_with_content_provider_and_receiver(
|
|
|
if (ok) {
|
|
|
auto last = offset + data_len == content_length;
|
|
|
|
|
|
- auto ret = compressor.compress(
|
|
|
+ auto ret = compressor->compress(
|
|
|
data, data_len, last,
|
|
|
[&](const char *compressed_data, size_t compressed_data_len) {
|
|
|
req.body.append(compressed_data, compressed_data_len);
|
|
|
@@ -13250,19 +13350,17 @@ ClientImpl::send_with_content_provider_and_receiver(
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
- if (!compressor.compress(body, content_length, true,
|
|
|
- [&](const char *data, size_t data_len) {
|
|
|
- req.body.append(data, data_len);
|
|
|
- return true;
|
|
|
- })) {
|
|
|
+ if (!compressor->compress(body, content_length, true,
|
|
|
+ [&](const char *data, size_t data_len) {
|
|
|
+ req.body.append(data, data_len);
|
|
|
+ return true;
|
|
|
+ })) {
|
|
|
error = Error::Compression;
|
|
|
output_error_log(error, &req);
|
|
|
return nullptr;
|
|
|
}
|
|
|
}
|
|
|
- } else
|
|
|
-#endif
|
|
|
- {
|
|
|
+ } else {
|
|
|
if (content_provider) {
|
|
|
req.content_length_ = content_length;
|
|
|
req.content_provider_ = std::move(content_provider);
|
|
|
@@ -15177,7 +15275,8 @@ inline void Client::set_follow_location(bool on) {
|
|
|
|
|
|
inline void Client::set_path_encode(bool on) { cli_->set_path_encode(on); }
|
|
|
|
|
|
-[[deprecated("Use set_path_encode instead")]]
|
|
|
+[[deprecated("Use set_path_encode() instead. "
|
|
|
+ "This function will be removed by v1.0.0.")]]
|
|
|
inline void Client::set_url_encode(bool on) {
|
|
|
cli_->set_path_encode(on);
|
|
|
}
|
|
|
@@ -15429,6 +15528,24 @@ inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) {
|
|
|
return ClientImpl::create_and_connect_socket(socket, error);
|
|
|
}
|
|
|
|
|
|
+inline bool SSLClient::setup_proxy_connection(
|
|
|
+ Socket &socket,
|
|
|
+ std::chrono::time_point<std::chrono::steady_clock> start_time,
|
|
|
+ Response &res, bool &success, Error &error) {
|
|
|
+ if (proxy_host_.empty() || proxy_port_ == -1) { return true; }
|
|
|
+
|
|
|
+ if (!connect_with_proxy(socket, start_time, res, success, error)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!initialize_ssl(socket, error)) {
|
|
|
+ success = false;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
// Assumes that socket_mutex_ is locked and that there are no requests in
|
|
|
// flight
|
|
|
inline bool SSLClient::connect_with_proxy(
|
|
|
@@ -19945,11 +20062,11 @@ inline WebSocket::~WebSocket() {
|
|
|
}
|
|
|
|
|
|
inline void WebSocket::start_heartbeat() {
|
|
|
+ if (ping_interval_sec_ == 0) { return; }
|
|
|
ping_thread_ = std::thread([this]() {
|
|
|
std::unique_lock<std::mutex> lock(ping_mutex_);
|
|
|
while (!closed_) {
|
|
|
- ping_cv_.wait_for(lock, std::chrono::seconds(
|
|
|
- CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND));
|
|
|
+ ping_cv_.wait_for(lock, std::chrono::seconds(ping_interval_sec_));
|
|
|
if (closed_) { break; }
|
|
|
lock.unlock();
|
|
|
if (!send_frame(Opcode::Ping, nullptr, 0)) {
|
|
|
@@ -20087,7 +20204,8 @@ inline bool WebSocketClient::connect() {
|
|
|
Request req;
|
|
|
req.method = "GET";
|
|
|
req.path = path_;
|
|
|
- ws_ = std::unique_ptr<WebSocket>(new WebSocket(std::move(strm), req, false));
|
|
|
+ ws_ = std::unique_ptr<WebSocket>(
|
|
|
+ new WebSocket(std::move(strm), req, false, websocket_ping_interval_sec_));
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@@ -20127,6 +20245,10 @@ inline void WebSocketClient::set_write_timeout(time_t sec, time_t usec) {
|
|
|
write_timeout_usec_ = usec;
|
|
|
}
|
|
|
|
|
|
+inline void WebSocketClient::set_websocket_ping_interval(time_t sec) {
|
|
|
+ websocket_ping_interval_sec_ = sec;
|
|
|
+}
|
|
|
+
|
|
|
#ifdef CPPHTTPLIB_SSL_ENABLED
|
|
|
|
|
|
inline void WebSocketClient::set_ca_cert_path(const std::string &path) {
|