// // httplib.h // // Copyright (c) 2026 Yuji Hirose. All rights reserved. // MIT License // #ifndef CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_VERSION "0.31.0" #define CPPHTTPLIB_VERSION_NUM "0x001F00" /* * Platform compatibility check */ #if defined(_WIN32) && !defined(_WIN64) #if defined(_MSC_VER) #pragma message( \ "cpp-httplib doesn't support 32-bit Windows. Please use a 64-bit compiler.") #else #warning \ "cpp-httplib doesn't support 32-bit Windows. Please use a 64-bit compiler." #endif #elif defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ < 8 #warning \ "cpp-httplib doesn't support 32-bit platforms. Please use a 64-bit compiler." #elif defined(__SIZEOF_SIZE_T__) && __SIZEOF_SIZE_T__ < 8 #warning \ "cpp-httplib doesn't support platforms where size_t is less than 64 bits." #endif #ifdef _WIN32 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00 #error \ "cpp-httplib doesn't support Windows 8 or lower. Please use Windows 10 or later." #endif #endif /* * Configuration */ #ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND #define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 #endif #ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND #define CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND 10000 #endif #ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT #define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100 #endif #ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND #define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 #endif #ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND #define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 #endif #ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND #define CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND 5 #endif #ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND #define CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND 0 #endif #ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND #define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND 5 #endif #ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND #define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND 0 #endif #ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND #define CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND 300 #endif #ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND #define CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND 0 #endif #ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND #define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND 5 #endif #ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND #define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0 #endif #ifndef CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND #define CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND 0 #endif #ifndef CPPHTTPLIB_EXPECT_100_THRESHOLD #define CPPHTTPLIB_EXPECT_100_THRESHOLD 1024 #endif #ifndef CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND #define CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND 1000 #endif #ifndef CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD #define CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD (1024 * 1024) #endif #ifndef CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND #define CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND 50 #endif #ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND #define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 #endif #ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND #ifdef _WIN32 #define CPPHTTPLIB_IDLE_INTERVAL_USECOND 1000 #else #define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 #endif #endif #ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH #define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 #endif #ifndef CPPHTTPLIB_HEADER_MAX_LENGTH #define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 #endif #ifndef CPPHTTPLIB_HEADER_MAX_COUNT #define CPPHTTPLIB_HEADER_MAX_COUNT 100 #endif #ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT #define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 #endif #ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT #define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024 #endif #ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH #define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (100 * 1024 * 1024) // 100MB #endif #ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH #define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 #endif #ifndef CPPHTTPLIB_RANGE_MAX_COUNT #define CPPHTTPLIB_RANGE_MAX_COUNT 1024 #endif #ifndef CPPHTTPLIB_TCP_NODELAY #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 #ifndef CPPHTTPLIB_SEND_BUFSIZ #define CPPHTTPLIB_SEND_BUFSIZ size_t(16384u) #endif #ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ #define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) #endif #ifndef CPPHTTPLIB_THREAD_POOL_COUNT #define CPPHTTPLIB_THREAD_POOL_COUNT \ ((std::max)(8u, std::thread::hardware_concurrency() > 0 \ ? std::thread::hardware_concurrency() - 1 \ : 0)) #endif #ifndef CPPHTTPLIB_RECV_FLAGS #define CPPHTTPLIB_RECV_FLAGS 0 #endif #ifndef CPPHTTPLIB_SEND_FLAGS #define CPPHTTPLIB_SEND_FLAGS 0 #endif #ifndef CPPHTTPLIB_LISTEN_BACKLOG #define CPPHTTPLIB_LISTEN_BACKLOG 5 #endif #ifndef CPPHTTPLIB_MAX_LINE_LENGTH #define CPPHTTPLIB_MAX_LINE_LENGTH 32768 #endif /* * Headers */ #ifdef _WIN32 #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif //_CRT_SECURE_NO_WARNINGS #ifndef _CRT_NONSTDC_NO_DEPRECATE #define _CRT_NONSTDC_NO_DEPRECATE #endif //_CRT_NONSTDC_NO_DEPRECATE #if defined(_MSC_VER) #if _MSC_VER < 1900 #error Sorry, Visual Studio versions prior to 2015 are not supported #endif #pragma comment(lib, "ws2_32.lib") #ifndef _SSIZE_T_DEFINED using ssize_t = __int64; #define _SSIZE_T_DEFINED #endif #endif // _MSC_VER #ifndef S_ISREG #define S_ISREG(m) (((m) & S_IFREG) == S_IFREG) #endif // S_ISREG #ifndef S_ISDIR #define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR) #endif // S_ISDIR #ifndef NOMINMAX #define NOMINMAX #endif // NOMINMAX #include #include #include #if defined(__has_include) #if __has_include() // afunix.h uses types declared in winsock2.h, so has to be included after it. #include #define CPPHTTPLIB_HAVE_AFUNIX_H 1 #endif #endif #ifndef WSA_FLAG_NO_HANDLE_INHERIT #define WSA_FLAG_NO_HANDLE_INHERIT 0x80 #endif using nfds_t = unsigned long; using socket_t = SOCKET; using socklen_t = int; #else // not _WIN32 #include #if !defined(_AIX) && !defined(__MVS__) #include #endif #ifdef __MVS__ #include #ifndef NI_MAXHOST #define NI_MAXHOST 1025 #endif #endif #include #include #include #ifdef __linux__ #include #undef _res // Undefine _res macro to avoid conflicts with user code (#2278) #endif #include #include #include #include #include #include #include #include using socket_t = int; #ifndef INVALID_SOCKET #define INVALID_SOCKET (-1) #endif #endif //_WIN32 #if defined(__APPLE__) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO) || \ defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) #if TARGET_OS_MAC #include #include #endif #endif // CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO or // CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN #ifdef CPPHTTPLIB_OPENSSL_SUPPORT #ifdef _WIN32 #include // these are defined in wincrypt.h and it breaks compilation if BoringSSL is // used #undef X509_NAME #undef X509_CERT_PAIR #undef X509_EXTENSIONS #undef PKCS7_SIGNER_INFO #ifdef _MSC_VER #pragma comment(lib, "crypt32.lib") #endif #endif // _WIN32 #if defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) #if TARGET_OS_MAC #include #endif #endif // CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO #include #include #include #include #if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) #include #endif #include #include #if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER) #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 #endif // CPPHTTPLIB_OPENSSL_SUPPORT #ifdef CPPHTTPLIB_MBEDTLS_SUPPORT #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #ifdef _MSC_VER #pragma comment(lib, "crypt32.lib") #endif #endif // _WIN32 #if defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) #if TARGET_OS_MAC #include #endif #endif // CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN // Mbed TLS 3.x API compatibility #if MBEDTLS_VERSION_MAJOR >= 3 #define CPPHTTPLIB_MBEDTLS_V3 #endif #endif // CPPHTTPLIB_MBEDTLS_SUPPORT // Define CPPHTTPLIB_SSL_ENABLED if any SSL backend is available // This simplifies conditional compilation when adding new backends (e.g., // wolfSSL) #if defined(CPPHTTPLIB_OPENSSL_SUPPORT) || defined(CPPHTTPLIB_MBEDTLS_SUPPORT) #define CPPHTTPLIB_SSL_ENABLED #endif #ifdef CPPHTTPLIB_ZLIB_SUPPORT #include #endif #ifdef CPPHTTPLIB_BROTLI_SUPPORT #include #include #endif #ifdef CPPHTTPLIB_ZSTD_SUPPORT #include #endif /* * Declaration */ namespace httplib { namespace detail { /* * Backport std::make_unique from C++14. * * NOTE: This code came up with the following stackoverflow post: * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique * */ template typename std::enable_if::value, std::unique_ptr>::type make_unique(Args &&...args) { return std::unique_ptr(new T(std::forward(args)...)); } template typename std::enable_if::value, std::unique_ptr>::type make_unique(std::size_t n) { typedef typename std::remove_extent::type RT; return std::unique_ptr(new RT[n]); } namespace case_ignore { inline unsigned char to_lower(int c) { const static unsigned char table[256] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, }; return table[(unsigned char)(char)c]; } inline bool equal(const std::string &a, const std::string &b) { return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) { return to_lower(ca) == to_lower(cb); }); } struct equal_to { bool operator()(const std::string &a, const std::string &b) const { return equal(a, b); } }; struct hash { size_t operator()(const std::string &key) const { return hash_core(key.data(), key.size(), 0); } size_t hash_core(const char *s, size_t l, size_t h) const { return (l == 0) ? h : hash_core(s + 1, l - 1, // Unsets the 6 high bits of h, therefore no // overflow happens (((std::numeric_limits::max)() >> 6) & h * 33) ^ static_cast(to_lower(*s))); } }; template using unordered_set = std::unordered_set; } // namespace case_ignore // This is based on // "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". struct scope_exit { explicit scope_exit(std::function &&f) : exit_function(std::move(f)), execute_on_destruction{true} {} scope_exit(scope_exit &&rhs) noexcept : exit_function(std::move(rhs.exit_function)), execute_on_destruction{rhs.execute_on_destruction} { rhs.release(); } ~scope_exit() { if (execute_on_destruction) { this->exit_function(); } } void release() { this->execute_on_destruction = false; } private: scope_exit(const scope_exit &) = delete; void operator=(const scope_exit &) = delete; scope_exit &operator=(scope_exit &&) = delete; std::function exit_function; bool execute_on_destruction; }; // Simple from_chars implementation for integer and double types (C++17 // substitute) template struct from_chars_result { const char *ptr; std::errc ec; }; template inline from_chars_result from_chars(const char *first, const char *last, T &value, int base = 10) { value = 0; const char *p = first; bool negative = false; if (p != last && *p == '-') { negative = true; ++p; } if (p == last) { return {first, std::errc::invalid_argument}; } T result = 0; for (; p != last; ++p) { char c = *p; int digit = -1; if ('0' <= c && c <= '9') { digit = c - '0'; } else if ('a' <= c && c <= 'z') { digit = c - 'a' + 10; } else if ('A' <= c && c <= 'Z') { digit = c - 'A' + 10; } else { break; } if (digit < 0 || digit >= base) { break; } if (result > ((std::numeric_limits::max)() - digit) / base) { return {p, std::errc::result_out_of_range}; } result = result * base + digit; } if (p == first || (negative && p == first + 1)) { return {first, std::errc::invalid_argument}; } value = negative ? -result : result; return {p, std::errc{}}; } // from_chars for double (simple wrapper for strtod) inline from_chars_result from_chars(const char *first, const char *last, double &value) { std::string s(first, last); char *endptr = nullptr; errno = 0; value = std::strtod(s.c_str(), &endptr); if (endptr == s.c_str()) { return {first, std::errc::invalid_argument}; } if (errno == ERANGE) { return {first + (endptr - s.c_str()), std::errc::result_out_of_range}; } return {first + (endptr - s.c_str()), std::errc{}}; } } // namespace detail enum SSLVerifierResponse { // no decision has been made, use the built-in certificate verifier NoDecisionMade, // connection certificate is verified and accepted CertificateAccepted, // connection certificate was processed but is rejected CertificateRejected }; enum StatusCode { // Information responses Continue_100 = 100, SwitchingProtocol_101 = 101, Processing_102 = 102, EarlyHints_103 = 103, // Successful responses OK_200 = 200, Created_201 = 201, Accepted_202 = 202, NonAuthoritativeInformation_203 = 203, NoContent_204 = 204, ResetContent_205 = 205, PartialContent_206 = 206, MultiStatus_207 = 207, AlreadyReported_208 = 208, IMUsed_226 = 226, // Redirection messages MultipleChoices_300 = 300, MovedPermanently_301 = 301, Found_302 = 302, SeeOther_303 = 303, NotModified_304 = 304, UseProxy_305 = 305, unused_306 = 306, TemporaryRedirect_307 = 307, PermanentRedirect_308 = 308, // Client error responses BadRequest_400 = 400, Unauthorized_401 = 401, PaymentRequired_402 = 402, Forbidden_403 = 403, NotFound_404 = 404, MethodNotAllowed_405 = 405, NotAcceptable_406 = 406, ProxyAuthenticationRequired_407 = 407, RequestTimeout_408 = 408, Conflict_409 = 409, Gone_410 = 410, LengthRequired_411 = 411, PreconditionFailed_412 = 412, PayloadTooLarge_413 = 413, UriTooLong_414 = 414, UnsupportedMediaType_415 = 415, RangeNotSatisfiable_416 = 416, ExpectationFailed_417 = 417, ImATeapot_418 = 418, MisdirectedRequest_421 = 421, UnprocessableContent_422 = 422, Locked_423 = 423, FailedDependency_424 = 424, TooEarly_425 = 425, UpgradeRequired_426 = 426, PreconditionRequired_428 = 428, TooManyRequests_429 = 429, RequestHeaderFieldsTooLarge_431 = 431, UnavailableForLegalReasons_451 = 451, // Server error responses InternalServerError_500 = 500, NotImplemented_501 = 501, BadGateway_502 = 502, ServiceUnavailable_503 = 503, GatewayTimeout_504 = 504, HttpVersionNotSupported_505 = 505, VariantAlsoNegotiates_506 = 506, InsufficientStorage_507 = 507, LoopDetected_508 = 508, NotExtended_510 = 510, NetworkAuthenticationRequired_511 = 511, }; using Headers = std::unordered_multimap; using Params = std::multimap; using Match = std::smatch; using DownloadProgress = std::function; using UploadProgress = std::function; struct Response; using ResponseHandler = std::function; struct FormData { std::string name; std::string content; std::string filename; std::string content_type; Headers headers; }; struct FormField { std::string name; std::string content; Headers headers; }; using FormFields = std::multimap; using FormFiles = std::multimap; struct MultipartFormData { FormFields fields; // Text fields from multipart FormFiles files; // Files from multipart // Text field access std::string get_field(const std::string &key, size_t id = 0) const; std::vector get_fields(const std::string &key) const; bool has_field(const std::string &key) const; size_t get_field_count(const std::string &key) const; // File access FormData get_file(const std::string &key, size_t id = 0) const; std::vector get_files(const std::string &key) const; bool has_file(const std::string &key) const; size_t get_file_count(const std::string &key) const; }; struct UploadFormData { std::string name; std::string content; std::string filename; std::string content_type; }; using UploadFormDataItems = std::vector; class DataSink { public: DataSink() : os(&sb_), sb_(*this) {} DataSink(const DataSink &) = delete; DataSink &operator=(const DataSink &) = delete; DataSink(DataSink &&) = delete; DataSink &operator=(DataSink &&) = delete; std::function write; std::function is_writable; std::function done; std::function done_with_trailer; std::ostream os; private: class data_sink_streambuf final : public std::streambuf { public: explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {} protected: std::streamsize xsputn(const char *s, std::streamsize n) override { sink_.write(s, static_cast(n)); return n; } private: DataSink &sink_; }; data_sink_streambuf sb_; }; using ContentProvider = std::function; using ContentProviderWithoutLength = std::function; using ContentProviderResourceReleaser = std::function; struct FormDataProvider { std::string name; ContentProviderWithoutLength provider; std::string filename; std::string content_type; }; using FormDataProviderItems = std::vector; using ContentReceiverWithProgress = std::function; using ContentReceiver = std::function; using FormDataHeader = std::function; class ContentReader { public: using Reader = std::function; using FormDataReader = std::function; ContentReader(Reader reader, FormDataReader multipart_reader) : reader_(std::move(reader)), formdata_reader_(std::move(multipart_reader)) {} bool operator()(FormDataHeader header, ContentReceiver receiver) const { return formdata_reader_(std::move(header), std::move(receiver)); } bool operator()(ContentReceiver receiver) const { return reader_(std::move(receiver)); } Reader reader_; FormDataReader formdata_reader_; }; using Range = std::pair; using Ranges = std::vector; #ifdef CPPHTTPLIB_SSL_ENABLED // TLS abstraction layer - public type definitions and API namespace tls { // Opaque handles (defined as void* for abstraction) using ctx_t = void *; using session_t = void *; using const_session_t = const void *; // For read-only session access using cert_t = void *; using ca_store_t = void *; // TLS versions enum class Version { TLS1_2 = 0x0303, TLS1_3 = 0x0304, }; // Subject Alternative Names (SAN) entry types enum class SanType { DNS, IP, EMAIL, URI, OTHER }; // SAN entry structure struct SanEntry { SanType type; std::string value; }; // Verification context for certificate verification callback struct VerifyContext { session_t session; // TLS session handle cert_t cert; // Current certificate being verified int depth; // Certificate chain depth (0 = leaf) bool preverify_ok; // OpenSSL/Mbed TLS pre-verification result long error_code; // Backend-specific error code (0 = no error) const char *error_string; // Human-readable error description // Certificate introspection methods std::string subject_cn() const; std::string issuer_name() const; bool check_hostname(const char *hostname) const; std::vector sans() const; bool validity(time_t ¬_before, time_t ¬_after) const; std::string serial() const; }; using VerifyCallback = std::function; // TlsError codes for TLS operations (backend-independent) enum class ErrorCode : int { Success = 0, WantRead, // Non-blocking: need to wait for read WantWrite, // Non-blocking: need to wait for write PeerClosed, // Peer closed the connection Fatal, // Unrecoverable error SyscallError, // System call error (check sys_errno) CertVerifyFailed, // Certificate verification failed HostnameMismatch, // Hostname verification failed }; // TLS error information struct TlsError { ErrorCode code = ErrorCode::Fatal; uint64_t backend_code = 0; // OpenSSL: ERR_get_error(), mbedTLS: return value int sys_errno = 0; // errno when SyscallError // Convert verification error code to human-readable string static std::string verify_error_to_string(long error_code); }; // RAII wrapper for peer certificate class PeerCert { public: PeerCert(); PeerCert(PeerCert &&other) noexcept; PeerCert &operator=(PeerCert &&other) noexcept; ~PeerCert(); PeerCert(const PeerCert &) = delete; PeerCert &operator=(const PeerCert &) = delete; explicit operator bool() const; std::string subject_cn() const; std::string issuer_name() const; bool check_hostname(const char *hostname) const; std::vector sans() const; bool validity(time_t ¬_before, time_t ¬_after) const; std::string serial() const; private: explicit PeerCert(cert_t cert); cert_t cert_ = nullptr; friend PeerCert get_peer_cert_from_session(const_session_t session); }; // Callback for TLS context setup (used by SSLServer constructor) using ContextSetupCallback = std::function; } // namespace tls #endif struct Request { std::string method; std::string path; std::string matched_route; Params params; Headers headers; Headers trailers; std::string body; std::string remote_addr; int remote_port = -1; std::string local_addr; int local_port = -1; // for server std::string version; std::string target; MultipartFormData form; Ranges ranges; Match matches; std::unordered_map path_params; std::function is_connection_closed = []() { return true; }; // for client std::vector accept_content_types; ResponseHandler response_handler; ContentReceiverWithProgress content_receiver; DownloadProgress download_progress; UploadProgress upload_progress; bool has_header(const std::string &key) const; std::string get_header_value(const std::string &key, const char *def = "", size_t id = 0) const; size_t get_header_value_u64(const std::string &key, size_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); bool has_trailer(const std::string &key) const; std::string get_trailer_value(const std::string &key, size_t id = 0) const; size_t get_trailer_value_count(const std::string &key) const; bool has_param(const std::string &key) const; std::string get_param_value(const std::string &key, size_t id = 0) const; size_t get_param_value_count(const std::string &key) const; bool is_multipart_form_data() const; // private members... size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; size_t content_length_ = 0; ContentProvider content_provider_; bool is_chunked_content_provider_ = false; size_t authorization_count_ = 0; std::chrono::time_point start_time_ = (std::chrono::steady_clock::time_point::min)(); #ifdef CPPHTTPLIB_SSL_ENABLED tls::const_session_t ssl = nullptr; tls::PeerCert peer_cert() const; std::string sni() const; #endif }; struct Response { std::string version; int status = -1; std::string reason; Headers headers; Headers trailers; std::string body; std::string location; // Redirect location bool has_header(const std::string &key) const; std::string get_header_value(const std::string &key, const char *def = "", size_t id = 0) const; size_t get_header_value_u64(const std::string &key, size_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); bool has_trailer(const std::string &key) const; std::string get_trailer_value(const std::string &key, size_t id = 0) const; size_t get_trailer_value_count(const std::string &key) const; void set_redirect(const std::string &url, int status = StatusCode::Found_302); void set_content(const char *s, size_t n, const std::string &content_type); void set_content(const std::string &s, const std::string &content_type); void set_content(std::string &&s, const std::string &content_type); void set_content_provider( size_t length, const std::string &content_type, ContentProvider provider, ContentProviderResourceReleaser resource_releaser = nullptr); void set_content_provider( const std::string &content_type, ContentProviderWithoutLength provider, ContentProviderResourceReleaser resource_releaser = nullptr); void set_chunked_content_provider( const std::string &content_type, ContentProviderWithoutLength provider, ContentProviderResourceReleaser resource_releaser = nullptr); void set_file_content(const std::string &path, const std::string &content_type); void set_file_content(const std::string &path); Response() = default; Response(const Response &) = default; Response &operator=(const Response &) = default; Response(Response &&) = default; Response &operator=(Response &&) = default; ~Response() { if (content_provider_resource_releaser_) { content_provider_resource_releaser_(content_provider_success_); } } // private members... size_t content_length_ = 0; ContentProvider content_provider_; ContentProviderResourceReleaser content_provider_resource_releaser_; bool is_chunked_content_provider_ = false; bool content_provider_success_ = false; std::string file_content_path_; std::string file_content_content_type_; }; enum class Error { Success = 0, Unknown, Connection, BindIPAddress, Read, Write, ExceedRedirectCount, Canceled, SSLConnection, SSLLoadingCerts, SSLServerVerification, SSLServerHostnameVerification, UnsupportedMultipartBoundaryChars, Compression, ConnectionTimeout, ProxyConnection, ConnectionClosed, Timeout, ResourceExhaustion, TooManyFormDataFiles, ExceedMaxPayloadSize, ExceedUriMaxLength, ExceedMaxSocketDescriptorCount, InvalidRequestLine, InvalidHTTPMethod, InvalidHTTPVersion, InvalidHeaders, MultipartParsing, OpenFile, Listen, GetSockName, UnsupportedAddressFamily, HTTPParsing, InvalidRangeHeader, // For internal use only SSLPeerCouldBeClosed_, }; std::string to_string(Error error); std::ostream &operator<<(std::ostream &os, const Error &obj); class Stream { public: virtual ~Stream() = default; virtual bool is_readable() const = 0; virtual bool wait_readable() const = 0; virtual bool wait_writable() const = 0; virtual ssize_t read(char *ptr, size_t size) = 0; virtual ssize_t write(const char *ptr, size_t size) = 0; virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0; virtual socket_t socket() const = 0; virtual time_t duration() const = 0; ssize_t write(const char *ptr); ssize_t write(const std::string &s); Error get_error() const { return error_; } protected: Error error_ = Error::Success; }; class TaskQueue { public: TaskQueue() = default; virtual ~TaskQueue() = default; virtual bool enqueue(std::function fn) = 0; virtual void shutdown() = 0; virtual void on_idle() {} }; class ThreadPool final : public TaskQueue { public: explicit ThreadPool(size_t n, size_t mqr = 0); ThreadPool(const ThreadPool &) = delete; ~ThreadPool() override = default; bool enqueue(std::function fn) override; void shutdown() override; private: struct worker { explicit worker(ThreadPool &pool); void operator()(); ThreadPool &pool_; }; friend struct worker; std::vector threads_; std::list> jobs_; bool shutdown_; size_t max_queued_requests_ = 0; std::condition_variable cond_; std::mutex mutex_; }; using Logger = std::function; // Forward declaration for Error type enum class Error; using ErrorLogger = std::function; using SocketOptions = std::function; void default_socket_options(socket_t sock); const char *status_message(int status); std::string to_string(Error error); std::ostream &operator<<(std::ostream &os, const Error &obj); std::string get_bearer_token_auth(const Request &req); namespace detail { class MatcherBase { public: MatcherBase(std::string pattern) : pattern_(std::move(pattern)) {} virtual ~MatcherBase() = default; const std::string &pattern() const { return pattern_; } // Match request path and populate its matches and virtual bool match(Request &request) const = 0; private: std::string pattern_; }; /** * Captures parameters in request path and stores them in Request::path_params * * Capture name is a substring of a pattern from : to /. * The rest of the pattern is matched against the request path directly * Parameters are captured starting from the next character after * the end of the last matched static pattern fragment until the next /. * * Example pattern: * "/path/fragments/:capture/more/fragments/:second_capture" * Static fragments: * "/path/fragments/", "more/fragments/" * * Given the following request path: * "/path/fragments/:1/more/fragments/:2" * the resulting capture will be * {{"capture", "1"}, {"second_capture", "2"}} */ class PathParamsMatcher final : public MatcherBase { public: PathParamsMatcher(const std::string &pattern); bool match(Request &request) const override; private: // Treat segment separators as the end of path parameter capture // Does not need to handle query parameters as they are parsed before path // matching static constexpr char separator = '/'; // Contains static path fragments to match against, excluding the '/' after // path params // Fragments are separated by path params std::vector static_fragments_; // Stores the names of the path parameters to be used as keys in the // Request::path_params map std::vector param_names_; }; /** * Performs std::regex_match on request path * and stores the result in Request::matches * * Note that regex match is performed directly on the whole request. * This means that wildcard patterns may match multiple path segments with /: * "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end". */ class RegexMatcher final : public MatcherBase { public: RegexMatcher(const std::string &pattern) : MatcherBase(pattern), regex_(pattern) {} bool match(Request &request) const override; private: std::regex regex_; }; int close_socket(socket_t sock); ssize_t write_headers(Stream &strm, const Headers &headers); bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec, time_t usec); } // namespace detail class Server { public: using Handler = std::function; using ExceptionHandler = std::function; enum class HandlerResponse { Handled, Unhandled, }; using HandlerWithResponse = std::function; using HandlerWithContentReader = std::function; using Expect100ContinueHandler = std::function; Server(); virtual ~Server(); virtual bool is_valid() const; Server &Get(const std::string &pattern, Handler handler); Server &Post(const std::string &pattern, Handler handler); Server &Post(const std::string &pattern, HandlerWithContentReader handler); Server &Put(const std::string &pattern, Handler handler); Server &Put(const std::string &pattern, HandlerWithContentReader handler); Server &Patch(const std::string &pattern, Handler handler); Server &Patch(const std::string &pattern, HandlerWithContentReader handler); Server &Delete(const std::string &pattern, Handler handler); Server &Delete(const std::string &pattern, HandlerWithContentReader handler); Server &Options(const std::string &pattern, Handler handler); bool set_base_dir(const std::string &dir, const std::string &mount_point = std::string()); bool set_mount_point(const std::string &mount_point, const std::string &dir, Headers headers = Headers()); bool remove_mount_point(const std::string &mount_point); Server &set_file_extension_and_mimetype_mapping(const std::string &ext, const std::string &mime); Server &set_default_file_mimetype(const std::string &mime); Server &set_file_request_handler(Handler handler); template Server &set_error_handler(ErrorHandlerFunc &&handler) { return set_error_handler_core( std::forward(handler), std::is_convertible{}); } Server &set_exception_handler(ExceptionHandler handler); Server &set_pre_routing_handler(HandlerWithResponse handler); Server &set_post_routing_handler(Handler handler); Server &set_pre_request_handler(HandlerWithResponse handler); Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); Server &set_logger(Logger logger); Server &set_pre_compression_logger(Logger logger); Server &set_error_logger(ErrorLogger error_logger); 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); Server & set_header_writer(std::function const &writer); Server &set_trusted_proxies(const std::vector &proxies); Server &set_keep_alive_max_count(size_t count); Server &set_keep_alive_timeout(time_t sec); Server &set_read_timeout(time_t sec, time_t usec = 0); template Server &set_read_timeout(const std::chrono::duration &duration); Server &set_write_timeout(time_t sec, time_t usec = 0); template Server &set_write_timeout(const std::chrono::duration &duration); Server &set_idle_interval(time_t sec, time_t usec = 0); template Server &set_idle_interval(const std::chrono::duration &duration); Server &set_payload_max_length(size_t length); 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(); bool listen(const std::string &host, int port, int socket_flags = 0); bool is_running() const; void wait_until_ready() const; void stop(); void decommission(); std::function new_task_queue; protected: bool process_request(Stream &strm, const std::string &remote_addr, int remote_port, const std::string &local_addr, int local_port, bool close_connection, bool &connection_closed, const std::function &setup_request); std::atomic svr_sock_{INVALID_SOCKET}; std::vector trusted_proxies_; size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND; time_t read_timeout_usec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND; time_t write_timeout_sec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND; time_t write_timeout_usec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND; 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; private: using Handlers = std::vector, Handler>>; using HandlersForContentReader = std::vector, HandlerWithContentReader>>; static std::unique_ptr make_matcher(const std::string &pattern); Server &set_error_handler_core(HandlerWithResponse handler, std::true_type); Server &set_error_handler_core(Handler handler, std::false_type); socket_t create_server_socket(const std::string &host, int port, int socket_flags, SocketOptions socket_options) const; int bind_internal(const std::string &host, int port, int socket_flags); bool listen_internal(); bool routing(Request &req, Response &res, Stream &strm); bool handle_file_request(Request &req, Response &res); bool check_if_not_modified(const Request &req, Response &res, const std::string &etag, time_t mtime) const; bool check_if_range(Request &req, const std::string &etag, time_t mtime) const; bool dispatch_request(Request &req, Response &res, const Handlers &handlers) const; bool dispatch_request_for_content_reader( Request &req, Response &res, ContentReader content_reader, const HandlersForContentReader &handlers) const; bool parse_request_line(const char *s, Request &req) const; void apply_ranges(const Request &req, Response &res, std::string &content_type, std::string &boundary) const; bool write_response(Stream &strm, bool close_connection, Request &req, Response &res); bool write_response_with_content(Stream &strm, bool close_connection, const Request &req, Response &res); bool write_response_core(Stream &strm, bool close_connection, const Request &req, Response &res, bool need_apply_ranges); bool write_content_with_provider(Stream &strm, const Request &req, Response &res, const std::string &boundary, const std::string &content_type); bool read_content(Stream &strm, Request &req, Response &res); bool read_content_with_content_receiver(Stream &strm, Request &req, Response &res, ContentReceiver receiver, FormDataHeader multipart_header, ContentReceiver multipart_receiver); bool read_content_core(Stream &strm, Request &req, Response &res, ContentReceiver receiver, FormDataHeader multipart_header, ContentReceiver multipart_receiver) const; virtual bool process_and_close_socket(socket_t sock); void output_log(const Request &req, const Response &res) const; void output_pre_compression_log(const Request &req, const Response &res) const; void output_error_log(const Error &err, const Request *req) const; std::atomic is_running_{false}; std::atomic is_decommissioned{false}; struct MountPointEntry { std::string mount_point; std::string base_dir; Headers headers; }; std::vector base_dirs_; std::map file_extension_and_mimetype_map_; std::string default_file_mimetype_ = "application/octet-stream"; Handler file_request_handler_; Handlers get_handlers_; Handlers post_handlers_; HandlersForContentReader post_handlers_for_content_reader_; Handlers put_handlers_; HandlersForContentReader put_handlers_for_content_reader_; Handlers patch_handlers_; HandlersForContentReader patch_handlers_for_content_reader_; Handlers delete_handlers_; HandlersForContentReader delete_handlers_for_content_reader_; Handlers options_handlers_; HandlerWithResponse error_handler_; ExceptionHandler exception_handler_; HandlerWithResponse pre_routing_handler_; Handler post_routing_handler_; HandlerWithResponse pre_request_handler_; Expect100ContinueHandler expect_100_continue_handler_; mutable std::mutex logger_mutex_; Logger logger_; Logger pre_compression_logger_; ErrorLogger error_logger_; 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_; std::function header_writer_ = detail::write_headers; }; class Result { public: Result() = default; Result(std::unique_ptr &&res, Error err, Headers &&request_headers = Headers{}) : res_(std::move(res)), err_(err), request_headers_(std::move(request_headers)) {} // Response operator bool() const { return res_ != nullptr; } bool operator==(std::nullptr_t) const { return res_ == nullptr; } bool operator!=(std::nullptr_t) const { return res_ != nullptr; } const Response &value() const { return *res_; } Response &value() { return *res_; } const Response &operator*() const { return *res_; } Response &operator*() { return *res_; } const Response *operator->() const { return res_.get(); } Response *operator->() { return res_.get(); } // Error Error error() const { return err_; } // 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; size_t get_request_header_value_u64(const std::string &key, size_t def = 0, size_t id = 0) const; size_t get_request_header_value_count(const std::string &key) const; private: std::unique_ptr res_; Error err_ = Error::Unknown; Headers request_headers_; #ifdef CPPHTTPLIB_SSL_ENABLED public: Result(std::unique_ptr &&res, Error err, Headers &&request_headers, int ssl_error) : res_(std::move(res)), err_(err), request_headers_(std::move(request_headers)), ssl_error_(ssl_error) {} Result(std::unique_ptr &&res, Error err, Headers &&request_headers, int ssl_error, unsigned long ssl_backend_error) : res_(std::move(res)), err_(err), request_headers_(std::move(request_headers)), ssl_error_(ssl_error), ssl_backend_error_(ssl_backend_error) {} int ssl_error() const { return ssl_error_; } unsigned long ssl_backend_error() const { return ssl_backend_error_; } private: int ssl_error_ = 0; unsigned long ssl_backend_error_ = 0; #endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT public: [[deprecated("Use ssl_backend_error() instead")]] unsigned long ssl_openssl_error() const { return ssl_backend_error_; } #endif }; struct ClientConnection { socket_t sock = INVALID_SOCKET; bool is_open() const { return sock != INVALID_SOCKET; } ClientConnection() = default; ~ClientConnection(); ClientConnection(const ClientConnection &) = delete; ClientConnection &operator=(const ClientConnection &) = delete; ClientConnection(ClientConnection &&other) noexcept : sock(other.sock) #ifdef CPPHTTPLIB_SSL_ENABLED , session(other.session) #endif { other.sock = INVALID_SOCKET; #ifdef CPPHTTPLIB_SSL_ENABLED other.session = nullptr; #endif } ClientConnection &operator=(ClientConnection &&other) noexcept { if (this != &other) { sock = other.sock; other.sock = INVALID_SOCKET; #ifdef CPPHTTPLIB_SSL_ENABLED session = other.session; other.session = nullptr; #endif } return *this; } #ifdef CPPHTTPLIB_SSL_ENABLED tls::session_t session = nullptr; #endif }; namespace detail { struct ChunkedDecoder; struct BodyReader { Stream *stream = nullptr; bool has_content_length = false; size_t content_length = 0; size_t payload_max_length = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; size_t bytes_read = 0; bool chunked = false; bool eof = false; std::unique_ptr chunked_decoder; Error last_error = Error::Success; ssize_t read(char *buf, size_t len); bool has_error() const { return last_error != Error::Success; } }; inline ssize_t read_body_content(Stream *stream, BodyReader &br, char *buf, size_t len) { (void)stream; return br.read(buf, len); } class decompressor; } // namespace detail class ClientImpl { public: explicit ClientImpl(const std::string &host); explicit ClientImpl(const std::string &host, int port); explicit ClientImpl(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path); virtual ~ClientImpl(); virtual bool is_valid() const; struct StreamHandle { std::unique_ptr response; Error error = Error::Success; StreamHandle() = default; StreamHandle(const StreamHandle &) = delete; StreamHandle &operator=(const StreamHandle &) = delete; StreamHandle(StreamHandle &&) = default; StreamHandle &operator=(StreamHandle &&) = default; ~StreamHandle() = default; bool is_valid() const { return response != nullptr && error == Error::Success; } ssize_t read(char *buf, size_t len); void parse_trailers_if_needed(); Error get_read_error() const { return body_reader_.last_error; } bool has_read_error() const { return body_reader_.has_error(); } bool trailers_parsed_ = false; private: friend class ClientImpl; ssize_t read_with_decompression(char *buf, size_t len); std::unique_ptr connection_; std::unique_ptr socket_stream_; Stream *stream_ = nullptr; detail::BodyReader body_reader_; std::unique_ptr decompressor_; std::string decompress_buffer_; size_t decompress_offset_ = 0; size_t decompressed_bytes_read_ = 0; }; // clang-format off Result Get(const std::string &path, DownloadProgress progress = nullptr); Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Get(const std::string &path, const Params ¶ms, const Headers &headers, DownloadProgress progress = nullptr); Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Head(const std::string &path); Result Head(const std::string &path, const Headers &headers); Result Post(const std::string &path); Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Post(const std::string &path, const Params ¶ms); Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers); Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, const Params ¶ms); Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Put(const std::string &path); Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Put(const std::string &path, const Params ¶ms); Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers); Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, const Params ¶ms); Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Patch(const std::string &path); Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Params ¶ms); Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, const Params ¶ms); Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Delete(const std::string &path, DownloadProgress progress = nullptr); Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); Result Delete(const std::string &path, const Params ¶ms, DownloadProgress progress = nullptr); Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); Result Delete(const std::string &path, const Headers &headers, const Params ¶ms, DownloadProgress progress = nullptr); Result Options(const std::string &path); Result Options(const std::string &path, const Headers &headers); // clang-format on // Streaming API: Open a stream for reading response body incrementally // Socket ownership is transferred to StreamHandle for true streaming // Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE, etc.) StreamHandle open_stream(const std::string &method, const std::string &path, const Params ¶ms = {}, const Headers &headers = {}, const std::string &body = {}, const std::string &content_type = {}); bool send(Request &req, Response &res, Error &error); Result send(const Request &req); void stop(); std::string host() const; int port() const; size_t is_socket_open() const; socket_t socket() const; void set_hostname_addr_map(std::map addr_map); void set_default_headers(Headers headers); void set_header_writer(std::function const &writer); 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); template void set_connection_timeout(const std::chrono::duration &duration); void set_read_timeout(time_t sec, time_t usec = 0); template void set_read_timeout(const std::chrono::duration &duration); void set_write_timeout(time_t sec, time_t usec = 0); template void set_write_timeout(const std::chrono::duration &duration); void set_max_timeout(time_t msec); template void set_max_timeout(const std::chrono::duration &duration); void set_basic_auth(const std::string &username, const std::string &password); void set_bearer_token_auth(const std::string &token); void set_keep_alive(bool on); void set_follow_location(bool on); void set_path_encode(bool on); void set_compress(bool on); void set_decompress(bool on); void set_payload_max_length(size_t length); void set_interface(const std::string &intf); void set_proxy(const std::string &host, int port); void set_proxy_basic_auth(const std::string &username, const std::string &password); void set_proxy_bearer_token_auth(const std::string &token); void set_logger(Logger logger); void set_error_logger(ErrorLogger error_logger); protected: struct Socket { socket_t sock = INVALID_SOCKET; // For Mbed TLS compatibility: start_time for request timeout tracking std::chrono::time_point start_time_; bool is_open() const { return sock != INVALID_SOCKET; } #ifdef CPPHTTPLIB_SSL_ENABLED tls::session_t ssl = nullptr; #endif }; virtual bool create_and_connect_socket(Socket &socket, Error &error); virtual bool ensure_socket_connection(Socket &socket, Error &error); // All of: // shutdown_ssl // shutdown_socket // close_socket // should ONLY be called when socket_mutex_ is locked. // Also, shutdown_ssl and close_socket should also NOT be called concurrently // with a DIFFERENT thread sending requests using that socket. virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); void shutdown_socket(Socket &socket) const; void close_socket(Socket &socket); bool process_request(Stream &strm, Request &req, Response &res, bool close_connection, Error &error); bool write_content_with_provider(Stream &strm, const Request &req, Error &error) const; void copy_settings(const ClientImpl &rhs); void output_log(const Request &req, const Response &res) const; void output_error_log(const Error &err, const Request *req) const; // Socket endpoint information const std::string host_; const int port_; // Current open socket Socket socket_; mutable std::mutex socket_mutex_; std::recursive_mutex request_mutex_; // These are all protected under socket_mutex size_t socket_requests_in_flight_ = 0; std::thread::id socket_requests_are_from_thread_ = std::thread::id(); bool socket_should_be_closed_when_request_is_done_ = false; // Hostname-IP map std::map addr_map_; // Default headers Headers default_headers_; // Header writer std::function header_writer_ = detail::write_headers; // Settings std::string client_cert_path_; std::string client_key_path_; time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; time_t read_timeout_sec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND; time_t read_timeout_usec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND; time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND; time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND; time_t max_timeout_msec_ = CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND; std::string basic_auth_username_; std::string basic_auth_password_; std::string bearer_token_auth_token_; bool keep_alive_ = false; bool follow_location_ = false; bool path_encode_ = true; int address_family_ = AF_UNSPEC; bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY; SocketOptions socket_options_ = nullptr; bool compress_ = false; bool decompress_ = true; size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; bool has_payload_max_length_ = false; std::string interface_; std::string proxy_host_; int proxy_port_ = -1; std::string proxy_basic_auth_username_; std::string proxy_basic_auth_password_; std::string proxy_bearer_token_auth_token_; mutable std::mutex logger_mutex_; Logger logger_; ErrorLogger error_logger_; private: bool send_(Request &req, Response &res, Error &error); Result send_(Request &&req); socket_t create_client_socket(Error &error) const; bool read_response_line(Stream &strm, const Request &req, Response &res, bool skip_100_continue = true) const; bool write_request(Stream &strm, Request &req, bool close_connection, Error &error, bool skip_body = false); bool write_request_body(Stream &strm, Request &req, Error &error); void prepare_default_headers(Request &r, bool for_stream, const std::string &ct); bool redirect(Request &req, Response &res, Error &error); bool create_redirect_client(const std::string &scheme, const std::string &host, int port, Request &req, Response &res, const std::string &path, const std::string &location, Error &error); template void setup_redirect_client(ClientType &client); bool handle_request(Stream &strm, Request &req, Response &res, bool close_connection, Error &error); std::unique_ptr send_with_content_provider_and_receiver( Request &req, const char *body, size_t content_length, ContentProvider content_provider, ContentProviderWithoutLength content_provider_without_length, const std::string &content_type, ContentReceiver content_receiver, Error &error); Result send_with_content_provider_and_receiver( const std::string &method, const std::string &path, const Headers &headers, const char *body, size_t content_length, ContentProvider content_provider, ContentProviderWithoutLength content_provider_without_length, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress); ContentProviderWithoutLength get_multipart_content_provider( const std::string &boundary, const UploadFormDataItems &items, const FormDataProviderItems &provider_items) const; virtual bool process_socket(const Socket &socket, std::chrono::time_point start_time, std::function callback); virtual bool is_ssl() const; void transfer_socket_ownership_to_handle(StreamHandle &handle); #ifdef CPPHTTPLIB_SSL_ENABLED public: void set_digest_auth(const std::string &username, const std::string &password); void set_proxy_digest_auth(const std::string &username, const std::string &password); void set_ca_cert_path(const std::string &ca_cert_file_path, const std::string &ca_cert_dir_path = std::string()); void enable_server_certificate_verification(bool enabled); void enable_server_hostname_verification(bool enabled); protected: std::string digest_auth_username_; std::string digest_auth_password_; std::string proxy_digest_auth_username_; std::string proxy_digest_auth_password_; std::string ca_cert_file_path_; std::string ca_cert_dir_path_; bool server_certificate_verification_ = true; bool server_hostname_verification_ = true; std::string ca_cert_pem_; // Store CA cert PEM for redirect transfer int last_ssl_error_ = 0; unsigned long last_backend_error_ = 0; #endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT public: [[deprecated("Use load_ca_cert_store() instead")]] void set_ca_cert_store(X509_STORE *ca_cert_store); [[deprecated("Use tls::create_ca_store() instead")]] X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size) const; [[deprecated("Use set_server_certificate_verifier(VerifyCallback) instead")]] virtual void set_server_certificate_verifier( std::function verifier); #endif }; class Client { public: // Universal interface explicit Client(const std::string &scheme_host_port); explicit Client(const std::string &scheme_host_port, const std::string &client_cert_path, const std::string &client_key_path); // HTTP only interface explicit Client(const std::string &host, int port); explicit Client(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path); Client(Client &&) = default; Client &operator=(Client &&) = default; ~Client(); bool is_valid() const; // clang-format off Result Get(const std::string &path, DownloadProgress progress = nullptr); Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Get(const std::string &path, const Params ¶ms, const Headers &headers, DownloadProgress progress = nullptr); Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Head(const std::string &path); Result Head(const std::string &path, const Headers &headers); Result Post(const std::string &path); Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Post(const std::string &path, const Params ¶ms); Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers); Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, const Params ¶ms); Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Put(const std::string &path); Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Put(const std::string &path, const Params ¶ms); Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers); Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, const Params ¶ms); Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Patch(const std::string &path); Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Params ¶ms); Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers); Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, const Params ¶ms); Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Delete(const std::string &path, DownloadProgress progress = nullptr); Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); Result Delete(const std::string &path, const Params ¶ms, DownloadProgress progress = nullptr); Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); Result Delete(const std::string &path, const Headers &headers, const Params ¶ms, DownloadProgress progress = nullptr); Result Options(const std::string &path); Result Options(const std::string &path, const Headers &headers); // clang-format on // Streaming API: Open a stream for reading response body incrementally // Socket ownership is transferred to StreamHandle for true streaming // Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE, etc.) ClientImpl::StreamHandle open_stream(const std::string &method, const std::string &path, const Params ¶ms = {}, const Headers &headers = {}, const std::string &body = {}, const std::string &content_type = {}); bool send(Request &req, Response &res, Error &error); Result send(const Request &req); void stop(); std::string host() const; int port() const; size_t is_socket_open() const; socket_t socket() const; void set_hostname_addr_map(std::map addr_map); void set_default_headers(Headers headers); void set_header_writer(std::function const &writer); void set_address_family(int family); void set_tcp_nodelay(bool on); void set_socket_options(SocketOptions socket_options); void set_connection_timeout(time_t sec, time_t usec = 0); template void set_connection_timeout(const std::chrono::duration &duration); void set_read_timeout(time_t sec, time_t usec = 0); template void set_read_timeout(const std::chrono::duration &duration); void set_write_timeout(time_t sec, time_t usec = 0); template void set_write_timeout(const std::chrono::duration &duration); void set_max_timeout(time_t msec); template void set_max_timeout(const std::chrono::duration &duration); void set_basic_auth(const std::string &username, const std::string &password); void set_bearer_token_auth(const std::string &token); void set_keep_alive(bool on); void set_follow_location(bool on); void set_path_encode(bool on); void set_url_encode(bool on); void set_compress(bool on); void set_decompress(bool on); void set_payload_max_length(size_t length); void set_interface(const std::string &intf); void set_proxy(const std::string &host, int port); void set_proxy_basic_auth(const std::string &username, const std::string &password); void set_proxy_bearer_token_auth(const std::string &token); void set_logger(Logger logger); void set_error_logger(ErrorLogger error_logger); private: std::unique_ptr cli_; #ifdef CPPHTTPLIB_SSL_ENABLED public: void set_digest_auth(const std::string &username, const std::string &password); void set_proxy_digest_auth(const std::string &username, const std::string &password); void enable_server_certificate_verification(bool enabled); void enable_server_hostname_verification(bool enabled); void set_ca_cert_path(const std::string &ca_cert_file_path, const std::string &ca_cert_dir_path = std::string()); void set_ca_cert_store(tls::ca_store_t ca_cert_store); void load_ca_cert_store(const char *ca_cert, std::size_t size); void set_server_certificate_verifier(tls::VerifyCallback verifier); void set_session_verifier( std::function verifier); tls::ctx_t tls_context() const; #if defined(_WIN32) && \ !defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE) void enable_windows_certificate_verification(bool enabled); #endif private: bool is_ssl_ = false; #endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT public: [[deprecated("Use tls_context() instead")]] SSL_CTX *ssl_context() const; [[deprecated("Use set_session_verifier(session_t) instead")]] void set_server_certificate_verifier( std::function verifier); [[deprecated("Use Result::ssl_backend_error() instead")]] long get_verify_result() const; #endif }; #ifdef CPPHTTPLIB_SSL_ENABLED class SSLServer : public Server { public: SSLServer(const char *cert_path, const char *private_key_path, const char *client_ca_cert_file_path = nullptr, const char *client_ca_cert_dir_path = nullptr, const char *private_key_password = nullptr); struct PemMemory { const char *cert_pem; size_t cert_pem_len; const char *key_pem; size_t key_pem_len; const char *client_ca_pem; size_t client_ca_pem_len; const char *private_key_password; }; explicit SSLServer(const PemMemory &pem); // The callback receives the ctx_t handle which can be cast to the // appropriate backend type (SSL_CTX* for OpenSSL, // tls::impl::MbedTlsContext* for Mbed TLS) explicit SSLServer(const tls::ContextSetupCallback &setup_callback); ~SSLServer() override; bool is_valid() const override; bool update_certs_pem(const char *cert_pem, const char *key_pem, const char *client_ca_pem = nullptr, const char *password = nullptr); tls::ctx_t tls_context() const { return ctx_; } int ssl_last_error() const { return last_ssl_error_; } private: bool process_and_close_socket(socket_t sock) override; tls::ctx_t ctx_ = nullptr; std::mutex ctx_mutex_; int last_ssl_error_ = 0; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT public: [[deprecated("Use SSLServer(PemMemory) or " "SSLServer(ContextSetupCallback) instead")]] SSLServer(X509 *cert, EVP_PKEY *private_key, X509_STORE *client_ca_cert_store = nullptr); [[deprecated("Use SSLServer(ContextSetupCallback) instead")]] SSLServer( const std::function &setup_ssl_ctx_callback); [[deprecated("Use tls_context() instead")]] SSL_CTX *ssl_context() const; [[deprecated("Use update_certs_pem() instead")]] void update_certs(X509 *cert, EVP_PKEY *private_key, X509_STORE *client_ca_cert_store = nullptr); #endif }; class SSLClient final : public ClientImpl { public: explicit SSLClient(const std::string &host); explicit SSLClient(const std::string &host, int port); explicit SSLClient(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path, const std::string &private_key_password = std::string()); struct PemMemory { const char *cert_pem; size_t cert_pem_len; const char *key_pem; size_t key_pem_len; const char *private_key_password; }; explicit SSLClient(const std::string &host, int port, const PemMemory &pem); ~SSLClient() override; bool is_valid() const override; void set_ca_cert_store(tls::ca_store_t ca_cert_store); void load_ca_cert_store(const char *ca_cert, std::size_t size); void set_server_certificate_verifier(tls::VerifyCallback verifier); // Post-handshake session verifier (backend-independent) void set_session_verifier( std::function verifier); tls::ctx_t tls_context() const { return ctx_; } #if defined(_WIN32) && \ !defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE) void enable_windows_certificate_verification(bool enabled); #endif private: bool create_and_connect_socket(Socket &socket, Error &error) override; bool ensure_socket_connection(Socket &socket, Error &error) override; void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully); bool process_socket(const Socket &socket, std::chrono::time_point start_time, std::function callback) override; bool is_ssl() const override; bool connect_with_proxy( Socket &sock, std::chrono::time_point start_time, Response &res, bool &success, Error &error); bool initialize_ssl(Socket &socket, Error &error); bool load_certs(); tls::ctx_t ctx_ = nullptr; std::mutex ctx_mutex_; std::once_flag initialize_cert_; long verify_result_ = 0; std::function session_verifier_; #if defined(_WIN32) && \ !defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE) bool enable_windows_cert_verification_ = true; #endif friend class ClientImpl; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT public: [[deprecated("Use SSLClient(host, port, PemMemory) instead")]] 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")]] long get_verify_result() const; [[deprecated("Use tls_context() instead")]] SSL_CTX *ssl_context() const; [[deprecated("Use set_session_verifier(session_t) instead")]] void set_server_certificate_verifier( std::function verifier) override; private: bool verify_host(X509 *server_cert) const; bool verify_host_with_subject_alt_name(X509 *server_cert) const; bool verify_host_with_common_name(X509 *server_cert) const; #endif }; #endif // CPPHTTPLIB_SSL_ENABLED namespace detail { template inline void duration_to_sec_and_usec(const T &duration, U callback) { auto sec = std::chrono::duration_cast(duration).count(); auto usec = std::chrono::duration_cast( duration - std::chrono::seconds(sec)) .count(); callback(static_cast(sec), static_cast(usec)); } template inline constexpr size_t str_len(const char (&)[N]) { return N - 1; } inline bool is_numeric(const std::string &str) { return !str.empty() && std::all_of(str.cbegin(), str.cend(), [](unsigned char c) { return std::isdigit(c); }); } inline size_t get_header_value_u64(const Headers &headers, const std::string &key, size_t def, size_t id, bool &is_invalid_value) { is_invalid_value = false; auto rng = headers.equal_range(key); auto it = rng.first; std::advance(it, static_cast(id)); if (it != rng.second) { if (is_numeric(it->second)) { return std::strtoull(it->second.data(), nullptr, 10); } else { is_invalid_value = true; } } return def; } inline size_t get_header_value_u64(const Headers &headers, const std::string &key, size_t def, size_t id) { auto dummy = false; return get_header_value_u64(headers, key, def, id, dummy); } } // namespace detail template inline Server & Server::set_read_timeout(const std::chrono::duration &duration) { detail::duration_to_sec_and_usec( duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); return *this; } template inline Server & Server::set_write_timeout(const std::chrono::duration &duration) { detail::duration_to_sec_and_usec( duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); return *this; } template inline Server & Server::set_idle_interval(const std::chrono::duration &duration) { detail::duration_to_sec_and_usec( duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); return *this; } template inline void ClientImpl::set_connection_timeout( const std::chrono::duration &duration) { detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { set_connection_timeout(sec, usec); }); } template inline void ClientImpl::set_read_timeout( const std::chrono::duration &duration) { detail::duration_to_sec_and_usec( duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); } template inline void ClientImpl::set_write_timeout( const std::chrono::duration &duration) { detail::duration_to_sec_and_usec( duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); } template inline void ClientImpl::set_max_timeout( const std::chrono::duration &duration) { auto msec = std::chrono::duration_cast(duration).count(); set_max_timeout(msec); } template inline void Client::set_connection_timeout( const std::chrono::duration &duration) { cli_->set_connection_timeout(duration); } template inline void Client::set_read_timeout(const std::chrono::duration &duration) { cli_->set_read_timeout(duration); } template inline void Client::set_write_timeout(const std::chrono::duration &duration) { cli_->set_write_timeout(duration); } inline void Client::set_max_timeout(time_t msec) { cli_->set_max_timeout(msec); } template inline void Client::set_max_timeout(const std::chrono::duration &duration) { cli_->set_max_timeout(duration); } /* * Forward declarations and types that will be part of the .h file if split into * .h + .cc. */ std::string hosted_at(const std::string &hostname); void hosted_at(const std::string &hostname, std::vector &addrs); // JavaScript-style URL encoding/decoding functions std::string encode_uri_component(const std::string &value); std::string encode_uri(const std::string &value); std::string decode_uri_component(const std::string &value); std::string decode_uri(const std::string &value); // RFC 3986 compliant URL component encoding/decoding functions std::string encode_path_component(const std::string &component); std::string decode_path_component(const std::string &component); std::string encode_query_component(const std::string &component, bool space_as_plus = true); std::string decode_query_component(const std::string &component, bool plus_as_space = true); std::string append_query_params(const std::string &path, const Params ¶ms); std::pair make_range_header(const Ranges &ranges); std::pair make_basic_authentication_header(const std::string &username, const std::string &password, bool is_proxy = false); namespace detail { #if defined(_WIN32) inline std::wstring u8string_to_wstring(const char *s) { if (!s) { return std::wstring(); } auto len = static_cast(strlen(s)); if (!len) { return std::wstring(); } auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0); if (!wlen) { return std::wstring(); } std::wstring ws; ws.resize(wlen); wlen = ::MultiByteToWideChar( CP_UTF8, 0, s, len, const_cast(reinterpret_cast(ws.data())), wlen); if (wlen != static_cast(ws.size())) { ws.clear(); } return ws; } #endif struct FileStat { FileStat(const std::string &path); bool is_file() const; bool is_dir() const; time_t mtime() const; size_t size() const; private: #if defined(_WIN32) struct _stat st_; #else struct stat st_; #endif int ret_ = -1; }; std::string make_host_and_port_string(const std::string &host, int port, bool is_ssl); std::string trim_copy(const std::string &s); void divide( const char *data, std::size_t size, char d, std::function fn); void divide( const std::string &str, char d, std::function fn); void split(const char *b, const char *e, char d, std::function fn); void split(const char *b, const char *e, char d, size_t m, std::function fn); bool process_client_socket( socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec, time_t max_timeout_msec, std::chrono::time_point start_time, std::function callback); 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, const char *def, size_t id); std::string params_to_query_str(const Params ¶ms); void parse_query_text(const char *data, std::size_t size, Params ¶ms); void parse_query_text(const std::string &s, Params ¶ms); bool parse_multipart_boundary(const std::string &content_type, std::string &boundary); bool parse_range_header(const std::string &s, Ranges &ranges); bool parse_accept_header(const std::string &s, std::vector &content_types); int close_socket(socket_t sock); ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); enum class EncodingType { None = 0, Gzip, Brotli, Zstd }; EncodingType encoding_type(const Request &req, const Response &res); class BufferStream final : public Stream { public: BufferStream() = default; ~BufferStream() override = default; bool is_readable() const override; bool wait_readable() const override; bool wait_writable() const override; ssize_t read(char *ptr, size_t size) override; ssize_t write(const char *ptr, size_t size) override; void get_remote_ip_and_port(std::string &ip, int &port) const override; void get_local_ip_and_port(std::string &ip, int &port) const override; socket_t socket() const override; time_t duration() const override; const std::string &get_buffer() const; private: std::string buffer; size_t position = 0; }; class compressor { public: virtual ~compressor() = default; typedef std::function Callback; virtual bool compress(const char *data, size_t data_length, bool last, Callback callback) = 0; }; class decompressor { public: virtual ~decompressor() = default; virtual bool is_valid() const = 0; typedef std::function Callback; virtual bool decompress(const char *data, size_t data_length, Callback callback) = 0; }; class nocompressor final : public compressor { public: ~nocompressor() override = default; bool compress(const char *data, size_t data_length, bool /*last*/, Callback callback) override; }; #ifdef CPPHTTPLIB_ZLIB_SUPPORT class gzip_compressor final : public compressor { public: gzip_compressor(); ~gzip_compressor() override; bool compress(const char *data, size_t data_length, bool last, Callback callback) override; private: bool is_valid_ = false; z_stream strm_; }; class gzip_decompressor final : public decompressor { public: gzip_decompressor(); ~gzip_decompressor() override; bool is_valid() const override; bool decompress(const char *data, size_t data_length, Callback callback) override; private: bool is_valid_ = false; z_stream strm_; }; #endif #ifdef CPPHTTPLIB_BROTLI_SUPPORT class brotli_compressor final : public compressor { public: brotli_compressor(); ~brotli_compressor(); bool compress(const char *data, size_t data_length, bool last, Callback callback) override; private: BrotliEncoderState *state_ = nullptr; }; class brotli_decompressor final : public decompressor { public: brotli_decompressor(); ~brotli_decompressor(); bool is_valid() const override; bool decompress(const char *data, size_t data_length, Callback callback) override; private: BrotliDecoderResult decoder_r; BrotliDecoderState *decoder_s = nullptr; }; #endif #ifdef CPPHTTPLIB_ZSTD_SUPPORT class zstd_compressor : public compressor { public: zstd_compressor(); ~zstd_compressor(); bool compress(const char *data, size_t data_length, bool last, Callback callback) override; private: ZSTD_CCtx *ctx_ = nullptr; }; class zstd_decompressor : public decompressor { public: zstd_decompressor(); ~zstd_decompressor(); bool is_valid() const override; bool decompress(const char *data, size_t data_length, Callback callback) override; private: ZSTD_DCtx *ctx_ = nullptr; }; #endif // NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` // to store data. The call can set memory on stack for performance. class stream_line_reader { public: stream_line_reader(Stream &strm, char *fixed_buffer, size_t fixed_buffer_size); const char *ptr() const; size_t size() const; bool end_with_crlf() const; bool getline(); private: void append(char c); Stream &strm_; char *fixed_buffer_; const size_t fixed_buffer_size_; size_t fixed_buffer_used_size_ = 0; std::string growable_buffer_; }; bool parse_trailers(stream_line_reader &line_reader, Headers &dest, const Headers &src_headers); struct ChunkedDecoder { Stream &strm; size_t chunk_remaining = 0; bool finished = false; char line_buf[64]; size_t last_chunk_total = 0; size_t last_chunk_offset = 0; explicit ChunkedDecoder(Stream &s); ssize_t read_payload(char *buf, size_t len, size_t &out_chunk_offset, size_t &out_chunk_total); bool parse_trailers_into(Headers &dest, const Headers &src_headers); }; class mmap { public: mmap(const char *path); ~mmap(); bool open(const char *path); void close(); bool is_open() const; size_t size() const; const char *data() const; private: #if defined(_WIN32) HANDLE hFile_ = NULL; HANDLE hMapping_ = NULL; #else int fd_ = -1; #endif size_t size_ = 0; void *addr_ = nullptr; bool is_open_empty_file = false; }; // NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5 namespace fields { bool is_token_char(char c); bool is_token(const std::string &s); bool is_field_name(const std::string &s); bool is_vchar(char c); bool is_obs_text(char c); bool is_field_vchar(char c); bool is_field_content(const std::string &s); bool is_field_value(const std::string &s); } // namespace fields } // namespace detail /* * TLS Abstraction Layer Declarations */ #ifdef CPPHTTPLIB_SSL_ENABLED // TLS abstraction layer - backend-specific type declarations #ifdef CPPHTTPLIB_MBEDTLS_SUPPORT namespace tls { namespace impl { // Mbed TLS context wrapper (holds config, entropy, DRBG, CA chain, own // cert/key). This struct is accessible via tls::impl for use in SSL context // setup callbacks (cast ctx_t to tls::impl::MbedTlsContext*). struct MbedTlsContext { mbedtls_ssl_config conf; mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; mbedtls_x509_crt ca_chain; mbedtls_x509_crt own_cert; mbedtls_pk_context own_key; bool is_server = false; bool verify_client = false; bool has_verify_callback = false; MbedTlsContext(); ~MbedTlsContext(); MbedTlsContext(const MbedTlsContext &) = delete; MbedTlsContext &operator=(const MbedTlsContext &) = delete; }; } // namespace impl } // namespace tls #endif #endif // CPPHTTPLIB_SSL_ENABLED namespace stream { class Result { public: Result(); explicit Result(ClientImpl::StreamHandle &&handle, size_t chunk_size = 8192); Result(Result &&other) noexcept; Result &operator=(Result &&other) noexcept; Result(const Result &) = delete; Result &operator=(const Result &) = delete; // Response info bool is_valid() const; explicit operator bool() const; int status() const; const Headers &headers() const; std::string get_header_value(const std::string &key, const char *def = "") const; bool has_header(const std::string &key) const; Error error() const; Error read_error() const; bool has_read_error() const; // Stream reading bool next(); const char *data() const; size_t size() const; std::string read_all(); private: ClientImpl::StreamHandle handle_; std::string buffer_; size_t current_size_ = 0; size_t chunk_size_; bool finished_ = false; }; // GET template inline Result Get(ClientType &cli, const std::string &path, size_t chunk_size = 8192) { return Result{cli.open_stream("GET", path), chunk_size}; } template inline Result Get(ClientType &cli, const std::string &path, const Headers &headers, size_t chunk_size = 8192) { return Result{cli.open_stream("GET", path, {}, headers), chunk_size}; } template inline Result Get(ClientType &cli, const std::string &path, const Params ¶ms, size_t chunk_size = 8192) { return Result{cli.open_stream("GET", path, params), chunk_size}; } template inline Result Get(ClientType &cli, const std::string &path, const Params ¶ms, const Headers &headers, size_t chunk_size = 8192) { return Result{cli.open_stream("GET", path, params, headers), chunk_size}; } // POST template inline Result Post(ClientType &cli, const std::string &path, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) { return Result{cli.open_stream("POST", path, {}, {}, body, content_type), chunk_size}; } template inline Result Post(ClientType &cli, const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) { return Result{cli.open_stream("POST", path, {}, headers, body, content_type), chunk_size}; } template inline Result Post(ClientType &cli, const std::string &path, const Params ¶ms, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) { return Result{cli.open_stream("POST", path, params, {}, body, content_type), chunk_size}; } template inline Result Post(ClientType &cli, const std::string &path, const Params ¶ms, const Headers &headers, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) { return Result{ cli.open_stream("POST", path, params, headers, body, content_type), chunk_size}; } // PUT template inline Result Put(ClientType &cli, const std::string &path, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) { return Result{cli.open_stream("PUT", path, {}, {}, body, content_type), chunk_size}; } template inline Result Put(ClientType &cli, const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) { return Result{cli.open_stream("PUT", path, {}, headers, body, content_type), chunk_size}; } template inline Result Put(ClientType &cli, const std::string &path, const Params ¶ms, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) { return Result{cli.open_stream("PUT", path, params, {}, body, content_type), chunk_size}; } template inline Result Put(ClientType &cli, const std::string &path, const Params ¶ms, const Headers &headers, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) { return Result{ cli.open_stream("PUT", path, params, headers, body, content_type), chunk_size}; } // PATCH template inline Result Patch(ClientType &cli, const std::string &path, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) { return Result{cli.open_stream("PATCH", path, {}, {}, body, content_type), chunk_size}; } template inline Result Patch(ClientType &cli, const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) { return Result{cli.open_stream("PATCH", path, {}, headers, body, content_type), chunk_size}; } template inline Result Patch(ClientType &cli, const std::string &path, const Params ¶ms, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) { return Result{cli.open_stream("PATCH", path, params, {}, body, content_type), chunk_size}; } template inline Result Patch(ClientType &cli, const std::string &path, const Params ¶ms, const Headers &headers, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) { return Result{ cli.open_stream("PATCH", path, params, headers, body, content_type), chunk_size}; } // DELETE template inline Result Delete(ClientType &cli, const std::string &path, size_t chunk_size = 8192) { return Result{cli.open_stream("DELETE", path), chunk_size}; } template inline Result Delete(ClientType &cli, const std::string &path, const Headers &headers, size_t chunk_size = 8192) { return Result{cli.open_stream("DELETE", path, {}, headers), chunk_size}; } template inline Result Delete(ClientType &cli, const std::string &path, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) { return Result{cli.open_stream("DELETE", path, {}, {}, body, content_type), chunk_size}; } template inline Result Delete(ClientType &cli, const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) { return Result{ cli.open_stream("DELETE", path, {}, headers, body, content_type), chunk_size}; } template inline Result Delete(ClientType &cli, const std::string &path, const Params ¶ms, size_t chunk_size = 8192) { return Result{cli.open_stream("DELETE", path, params), chunk_size}; } template inline Result Delete(ClientType &cli, const std::string &path, const Params ¶ms, const Headers &headers, size_t chunk_size = 8192) { return Result{cli.open_stream("DELETE", path, params, headers), chunk_size}; } template inline Result Delete(ClientType &cli, const std::string &path, const Params ¶ms, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) { return Result{cli.open_stream("DELETE", path, params, {}, body, content_type), chunk_size}; } template inline Result Delete(ClientType &cli, const std::string &path, const Params ¶ms, const Headers &headers, const std::string &body, const std::string &content_type, size_t chunk_size = 8192) { return Result{ cli.open_stream("DELETE", path, params, headers, body, content_type), chunk_size}; } // HEAD template inline Result Head(ClientType &cli, const std::string &path, size_t chunk_size = 8192) { return Result{cli.open_stream("HEAD", path), chunk_size}; } template inline Result Head(ClientType &cli, const std::string &path, const Headers &headers, size_t chunk_size = 8192) { return Result{cli.open_stream("HEAD", path, {}, headers), chunk_size}; } template inline Result Head(ClientType &cli, const std::string &path, const Params ¶ms, size_t chunk_size = 8192) { return Result{cli.open_stream("HEAD", path, params), chunk_size}; } template inline Result Head(ClientType &cli, const std::string &path, const Params ¶ms, const Headers &headers, size_t chunk_size = 8192) { return Result{cli.open_stream("HEAD", path, params, headers), chunk_size}; } // OPTIONS template inline Result Options(ClientType &cli, const std::string &path, size_t chunk_size = 8192) { return Result{cli.open_stream("OPTIONS", path), chunk_size}; } template inline Result Options(ClientType &cli, const std::string &path, const Headers &headers, size_t chunk_size = 8192) { return Result{cli.open_stream("OPTIONS", path, {}, headers), chunk_size}; } template inline Result Options(ClientType &cli, const std::string &path, const Params ¶ms, size_t chunk_size = 8192) { return Result{cli.open_stream("OPTIONS", path, params), chunk_size}; } template inline Result Options(ClientType &cli, const std::string &path, const Params ¶ms, const Headers &headers, size_t chunk_size = 8192) { return Result{cli.open_stream("OPTIONS", path, params, headers), chunk_size}; } } // namespace stream namespace sse { struct SSEMessage { std::string event; // Event type (default: "message") std::string data; // Event payload std::string id; // Event ID for Last-Event-ID header SSEMessage(); void clear(); }; class SSEClient { public: using MessageHandler = std::function; using ErrorHandler = std::function; using OpenHandler = std::function; SSEClient(Client &client, const std::string &path); SSEClient(Client &client, const std::string &path, const Headers &headers); ~SSEClient(); SSEClient(const SSEClient &) = delete; SSEClient &operator=(const SSEClient &) = delete; // Event handlers SSEClient &on_message(MessageHandler handler); SSEClient &on_event(const std::string &type, MessageHandler handler); SSEClient &on_open(OpenHandler handler); SSEClient &on_error(ErrorHandler handler); SSEClient &set_reconnect_interval(int ms); SSEClient &set_max_reconnect_attempts(int n); // State accessors bool is_connected() const; const std::string &last_event_id() const; // Blocking start - runs event loop with auto-reconnect void start(); // Non-blocking start - runs in background thread void start_async(); // Stop the client (thread-safe) void stop(); private: bool parse_sse_line(const std::string &line, SSEMessage &msg, int &retry_ms); void run_event_loop(); void dispatch_event(const SSEMessage &msg); bool should_reconnect(int count) const; void wait_for_reconnect(); // Client and path Client &client_; std::string path_; Headers headers_; // Callbacks MessageHandler on_message_; std::map event_handlers_; OpenHandler on_open_; ErrorHandler on_error_; // Configuration int reconnect_interval_ms_ = 3000; int max_reconnect_attempts_ = 0; // 0 = unlimited // State std::atomic running_{false}; std::atomic connected_{false}; std::string last_event_id_; // Async support std::thread async_thread_; }; } // namespace sse } // namespace httplib #endif // CPPHTTPLIB_HTTPLIB_H