Safetensors
GGUF
Turkish
llama
Llama-3
instruct
finetune
chatml
gpt4
synthetic data
distillation
function calling
json mode
axolotl
roleplaying
chat
Instructions to use tda45/TdAI with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- llama-cpp-python
How to use tda45/TdAI with llama-cpp-python:
# !pip install llama-cpp-python from llama_cpp import Llama llm = Llama.from_pretrained( repo_id="tda45/TdAI", filename="llama.cpp/models/ggml-vocab-aquila.gguf", )
output = llm( "Once upon a time,", max_tokens=512, echo=True ) print(output)
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use tda45/TdAI with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf tda45/TdAI # Run inference directly in the terminal: llama cli -hf tda45/TdAI
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf tda45/TdAI # Run inference directly in the terminal: llama cli -hf tda45/TdAI
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf tda45/TdAI # Run inference directly in the terminal: ./llama-cli -hf tda45/TdAI
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf tda45/TdAI # Run inference directly in the terminal: ./build/bin/llama-cli -hf tda45/TdAI
Use Docker
docker model run hf.co/tda45/TdAI
- LM Studio
- Jan
- Ollama
How to use tda45/TdAI with Ollama:
ollama run hf.co/tda45/TdAI
- Unsloth Studio
How to use tda45/TdAI with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for tda45/TdAI to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for tda45/TdAI to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for tda45/TdAI to start chatting
- Atomic Chat new
- Docker Model Runner
How to use tda45/TdAI with Docker Model Runner:
docker model run hf.co/tda45/TdAI
- Lemonade
How to use tda45/TdAI with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull tda45/TdAI
Run and chat with the model
lemonade run user.TdAI-{{QUANT_TAG}}List all available models
lemonade list
| // | |
| // httplib.h | |
| // | |
| // Copyright (c) 2026 Yuji Hirose. All rights reserved. | |
| // MIT License | |
| // | |
| /* | |
| * Configuration | |
| */ | |
| /* | |
| * Headers | |
| */ | |
| using ssize_t = __int64; | |
| // afunix.h uses types declared in winsock2.h, so has to be included after it. | |
| using nfds_t = unsigned long; | |
| using socket_t = SOCKET; | |
| using socklen_t = int; | |
| using socket_t = int; | |
| // On macOS with a TLS backend, enable Keychain root certificates by default | |
| // unless the user explicitly opts out. Not enabled on iOS/tvOS/watchOS since | |
| // the SecTrustSettings APIs used to enumerate anchor certificates are macOS | |
| // only; on those platforms the user must provide a CA bundle explicitly. | |
| // On Windows, enable Schannel certificate verification by default | |
| // unless the user explicitly opts out. | |
| // these are defined in wincrypt.h and it breaks compilation if BoringSSL is | |
| // used | |
| // Mbed TLS 3.x API compatibility | |
| // Fallback definitions for older wolfSSL versions (e.g., 5.6.6) | |
| // Define CPPHTTPLIB_SSL_ENABLED if any SSL backend is available | |
| /* | |
| * Declaration | |
| */ | |
| namespace httplib { | |
| namespace ws { | |
| class WebSocket; | |
| } // namespace ws | |
| 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 <class T, class... Args> | |
| typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type | |
| make_unique(Args &&...args) { | |
| return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); | |
| } | |
| template <class T> | |
| typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type | |
| make_unique(std::size_t n) { | |
| typedef typename std::remove_extent<T>::type RT; | |
| return std::unique_ptr<T>(new RT[n]); | |
| } | |
| // Locale-independent ASCII character classification. The <cctype> | |
| // counterparts (std::isalnum, std::isdigit, ...) consult the global C locale, | |
| // so e.g. std::isalnum(0xC5) can return true once an embedder calls | |
| // setlocale(). HTTP grammars are defined over ASCII, so raw bytes must be | |
| // classified without regard to the locale. | |
| inline bool is_ascii_digit(char c) { return '0' <= c && c <= '9'; } | |
| inline bool is_ascii_alpha(char c) { | |
| return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); | |
| } | |
| inline bool is_ascii_alnum(char c) { | |
| return is_ascii_digit(c) || is_ascii_alpha(c); | |
| } | |
| 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 std::string to_lower(const std::string &s) { | |
| std::string result = s; | |
| std::transform( | |
| result.begin(), result.end(), result.begin(), | |
| [](unsigned char c) { return static_cast<char>(to_lower(c)); }); | |
| return result; | |
| } | |
| 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<size_t>::max)() >> 6) & | |
| h * 33) ^ | |
| static_cast<unsigned char>(to_lower(*s))); | |
| } | |
| }; | |
| template <typename T> | |
| using unordered_set = std::unordered_set<T, detail::case_ignore::hash, | |
| detail::case_ignore::equal_to>; | |
| } // 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<void(void)> &&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<void(void)> exit_function; | |
| bool execute_on_destruction; | |
| }; | |
| // Simple from_chars implementation for integer and double types (C++17 | |
| // substitute) | |
| template <typename T> struct from_chars_result { | |
| const char *ptr; | |
| std::errc ec; | |
| }; | |
| template <typename T> | |
| inline from_chars_result<T> 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 (is_ascii_digit(c)) { | |
| 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<T>::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 (hand-written, locale-independent) | |
| // | |
| // The only double consumed by this library is the HTTP quality value, whose | |
| // grammar is (RFC 9110 12.4.2): | |
| // qvalue = ( "0" [ "." 0*3DIGIT ] ) / ( "1" [ "." 0*3("0") ] ) | |
| // i.e. a non-negative decimal with no sign, exponent, "inf"/"nan", or wide | |
| // magnitude. So this parser recognizes exactly 1*DIGIT [ "." *DIGIT ] with | |
| // '.' always the decimal separator (std::strtod would instead read it from the | |
| // global C locale, mis-parsing q-values once an embedder calls | |
| // setlocale(LC_ALL, "") into a comma-decimal locale). The caller range-checks | |
| // the result to [0, 1], so inputs outside that range need not be distinguished | |
| // here. Allocation-free, single pass, and free of the overflow/rounding edge | |
| // cases that exponent and wide-range handling would introduce. | |
| inline from_chars_result<double> from_chars(const char *first, const char *last, | |
| double &value) { | |
| value = 0.0; | |
| const char *p = first; | |
| // Each 1eN is exactly representable, so a single final division by the | |
| // matching entry yields a correctly-rounded result. | |
| static const double powers_of_ten[] = { | |
| 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, | |
| 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18}; | |
| const int max_frac_digits = | |
| static_cast<int>(sizeof(powers_of_ten) / sizeof(powers_of_ten[0])) - 1; | |
| // Accumulate digits into a 64-bit integer and remember how many were | |
| // fractional. Two independent caps keep this bounded and safe: | |
| // * accumulation saturates before mantissa could overflow uint64_t, and | |
| // * frac_digits is capped at max_frac_digits so it is always a valid index | |
| // into powers_of_ten (without this an input like "0.000...0" would never | |
| // grow mantissa, so the saturation cap alone would not bound it). | |
| // Both caps only drop digits far beyond the precision a q-value needs; any | |
| // value they would change is well outside [0, 1] and rejected by the caller. | |
| uint64_t mantissa = 0; | |
| int frac_digits = 0; | |
| bool seen_digit = false; | |
| const uint64_t limit = ((std::numeric_limits<uint64_t>::max)() - 9) / 10; | |
| auto accumulate = [&](char c) { | |
| if (mantissa <= limit) { | |
| mantissa = mantissa * 10 + static_cast<uint64_t>(c - '0'); | |
| return true; | |
| } | |
| return false; | |
| }; | |
| for (; p != last && is_ascii_digit(*p); ++p) { | |
| seen_digit = true; | |
| accumulate(*p); | |
| } | |
| if (p != last && *p == '.') { | |
| ++p; | |
| for (; p != last && is_ascii_digit(*p); ++p) { | |
| seen_digit = true; | |
| if (frac_digits < max_frac_digits && accumulate(*p)) { ++frac_digits; } | |
| } | |
| } | |
| if (!seen_digit) { return {first, std::errc::invalid_argument}; } | |
| value = static_cast<double>(mantissa) / powers_of_ten[frac_digits]; | |
| return {p, std::errc{}}; | |
| } | |
| inline bool parse_port(const char *s, size_t len, int &port) { | |
| int val = 0; | |
| auto r = from_chars(s, s + len, val); | |
| if (r.ec != std::errc{} || val < 1 || val > 65535) { return false; } | |
| port = val; | |
| return true; | |
| } | |
| inline bool parse_port(const std::string &s, int &port) { | |
| return parse_port(s.data(), s.size(), port); | |
| } | |
| struct UrlComponents { | |
| std::string scheme; | |
| std::string host; | |
| std::string port; | |
| std::string path; | |
| std::string query; | |
| }; | |
| inline bool parse_url(const std::string &url, UrlComponents &uc) { | |
| uc = {}; | |
| size_t pos = 0; | |
| auto sep = url.find("://"); | |
| if (sep != std::string::npos) { | |
| uc.scheme = url.substr(0, sep); | |
| // Scheme must be [a-z]+ only | |
| if (uc.scheme.empty()) { return false; } | |
| for (auto c : uc.scheme) { | |
| if (c < 'a' || c > 'z') { return false; } | |
| } | |
| pos = sep + 3; | |
| } else if (url.compare(0, 2, "//") == 0) { | |
| pos = 2; | |
| } | |
| auto has_authority_prefix = pos > 0; | |
| auto has_authority = has_authority_prefix || (!url.empty() && url[0] != '/' && | |
| url[0] != '?' && url[0] != '#'); | |
| if (has_authority) { | |
| if (pos < url.size() && url[pos] == '[') { | |
| auto close = url.find(']', pos); | |
| if (close == std::string::npos) { return false; } | |
| uc.host = url.substr(pos + 1, close - pos - 1); | |
| // IPv6 host must be [a-fA-F0-9:]+ only | |
| if (uc.host.empty()) { return false; } | |
| for (auto c : uc.host) { | |
| if (!(is_ascii_digit(c) || (c >= 'a' && c <= 'f') || | |
| (c >= 'A' && c <= 'F') || c == ':')) { | |
| return false; | |
| } | |
| } | |
| pos = close + 1; | |
| } else { | |
| auto end = url.find_first_of(":/?#", pos); | |
| if (end == std::string::npos) { end = url.size(); } | |
| uc.host = url.substr(pos, end - pos); | |
| pos = end; | |
| } | |
| if (pos < url.size() && url[pos] == ':') { | |
| ++pos; | |
| auto end = url.find_first_of("/?#", pos); | |
| if (end == std::string::npos) { end = url.size(); } | |
| uc.port = url.substr(pos, end - pos); | |
| pos = end; | |
| } | |
| // Without :// or //, the entire input must be consumed as host[:port]. | |
| // If there is leftover (path, query, etc.), this is not a valid | |
| // host[:port] string — clear and reparse as a plain path. | |
| if (!has_authority_prefix && pos < url.size()) { | |
| uc.host.clear(); | |
| uc.port.clear(); | |
| pos = 0; | |
| } | |
| } | |
| if (pos < url.size() && url[pos] != '?' && url[pos] != '#') { | |
| auto end = url.find_first_of("?#", pos); | |
| if (end == std::string::npos) { end = url.size(); } | |
| uc.path = url.substr(pos, end - pos); | |
| pos = end; | |
| } | |
| if (pos < url.size() && url[pos] == '?') { | |
| auto end = url.find('#', pos); | |
| if (end == std::string::npos) { end = url.size(); } | |
| uc.query = url.substr(pos, end - pos); | |
| } | |
| return true; | |
| } | |
| } // namespace detail | |
| enum class 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 | |
| }; | |
| // System CA loading policy for SSL clients. Auto (the default) loads system | |
| // CA certs only when no custom CA is configured; enable_system_ca() switches | |
| // to an explicit policy. | |
| enum class SystemCAMode { Auto, Enabled, Disabled }; | |
| 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<std::string, std::string, detail::case_ignore::hash, | |
| detail::case_ignore::equal_to>; | |
| using Params = std::multimap<std::string, std::string>; | |
| using Match = std::smatch; | |
| using DownloadProgress = std::function<bool(size_t current, size_t total)>; | |
| using UploadProgress = std::function<bool(size_t current, size_t total)>; | |
| /* | |
| * detail: type-erased storage used by UserData. | |
| * ABI-stable regardless of C++ standard — always uses this custom | |
| * implementation instead of std::any. | |
| */ | |
| namespace detail { | |
| using any_type_id = const void *; | |
| template <typename T> any_type_id any_typeid() noexcept { | |
| static const char id = 0; | |
| return &id; | |
| } | |
| struct any_storage { | |
| virtual ~any_storage() = default; | |
| virtual std::unique_ptr<any_storage> clone() const = 0; | |
| virtual any_type_id type_id() const noexcept = 0; | |
| }; | |
| template <typename T> struct any_value final : any_storage { | |
| T value; | |
| template <typename U> explicit any_value(U &&v) : value(std::forward<U>(v)) {} | |
| std::unique_ptr<any_storage> clone() const override { | |
| return std::unique_ptr<any_storage>(new any_value<T>(value)); | |
| } | |
| any_type_id type_id() const noexcept override { return any_typeid<T>(); } | |
| }; | |
| } // namespace detail | |
| class UserData { | |
| public: | |
| UserData() = default; | |
| UserData(UserData &&) noexcept = default; | |
| UserData &operator=(UserData &&) noexcept = default; | |
| UserData(const UserData &o) { | |
| for (const auto &e : o.entries_) { | |
| if (e.second) { entries_[e.first] = e.second->clone(); } | |
| } | |
| } | |
| UserData &operator=(const UserData &o) { | |
| if (this != &o) { | |
| entries_.clear(); | |
| for (const auto &e : o.entries_) { | |
| if (e.second) { entries_[e.first] = e.second->clone(); } | |
| } | |
| } | |
| return *this; | |
| } | |
| template <typename T> void set(const std::string &key, T &&value) { | |
| using D = typename std::decay<T>::type; | |
| entries_[key].reset(new detail::any_value<D>(std::forward<T>(value))); | |
| } | |
| template <typename T> T *get(const std::string &key) noexcept { | |
| auto it = entries_.find(key); | |
| if (it == entries_.end() || !it->second) { return nullptr; } | |
| if (it->second->type_id() != detail::any_typeid<T>()) { return nullptr; } | |
| return &static_cast<detail::any_value<T> *>(it->second.get())->value; | |
| } | |
| template <typename T> const T *get(const std::string &key) const noexcept { | |
| auto it = entries_.find(key); | |
| if (it == entries_.end() || !it->second) { return nullptr; } | |
| if (it->second->type_id() != detail::any_typeid<T>()) { return nullptr; } | |
| return &static_cast<const detail::any_value<T> *>(it->second.get())->value; | |
| } | |
| bool has(const std::string &key) const noexcept { | |
| return entries_.find(key) != entries_.end(); | |
| } | |
| void erase(const std::string &key) { entries_.erase(key); } | |
| void clear() noexcept { entries_.clear(); } | |
| private: | |
| std::unordered_map<std::string, std::unique_ptr<detail::any_storage>> | |
| entries_; | |
| }; | |
| struct Response; | |
| using ResponseHandler = std::function<bool(const Response &response)>; | |
| 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<std::string, FormField>; | |
| using FormFiles = std::multimap<std::string, FormData>; | |
| 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<std::string> 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<FormData> 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<UploadFormData>; | |
| 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<bool(const char *data, size_t data_len)> write; | |
| std::function<bool()> is_writable; | |
| std::function<void()> done; | |
| std::function<void(const Headers &trailer)> 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 { | |
| if (sink_.write(s, static_cast<size_t>(n))) { return n; } | |
| return 0; | |
| } | |
| private: | |
| DataSink &sink_; | |
| }; | |
| data_sink_streambuf sb_; | |
| }; | |
| using ContentProvider = | |
| std::function<bool(size_t offset, size_t length, DataSink &sink)>; | |
| using ContentProviderWithoutLength = | |
| std::function<bool(size_t offset, DataSink &sink)>; | |
| using ContentProviderResourceReleaser = std::function<void(bool success)>; | |
| struct FormDataProvider { | |
| std::string name; | |
| ContentProviderWithoutLength provider; | |
| std::string filename; | |
| std::string content_type; | |
| }; | |
| using FormDataProviderItems = std::vector<FormDataProvider>; | |
| inline FormDataProvider | |
| make_file_provider(const std::string &name, const std::string &filepath, | |
| const std::string &filename = std::string(), | |
| const std::string &content_type = std::string()) { | |
| FormDataProvider fdp; | |
| fdp.name = name; | |
| fdp.filename = filename.empty() ? filepath : filename; | |
| fdp.content_type = content_type; | |
| fdp.provider = [filepath](size_t offset, DataSink &sink) -> bool { | |
| std::ifstream f(filepath, std::ios::binary); | |
| if (!f) { return false; } | |
| if (offset > 0) { | |
| f.seekg(static_cast<std::streamoff>(offset)); | |
| if (!f.good()) { | |
| sink.done(); | |
| return true; | |
| } | |
| } | |
| char buf[8192]; | |
| f.read(buf, sizeof(buf)); | |
| auto n = static_cast<size_t>(f.gcount()); | |
| if (n > 0) { return sink.write(buf, n); } | |
| sink.done(); // EOF | |
| return true; | |
| }; | |
| return fdp; | |
| } | |
| inline std::pair<size_t, ContentProvider> | |
| make_file_body(const std::string &filepath) { | |
| size_t size = 0; | |
| { | |
| std::ifstream f(filepath, std::ios::binary | std::ios::ate); | |
| if (!f) { return {0, ContentProvider{}}; } | |
| size = static_cast<size_t>(f.tellg()); | |
| } | |
| ContentProvider provider = [filepath](size_t offset, size_t length, | |
| DataSink &sink) -> bool { | |
| std::ifstream f(filepath, std::ios::binary); | |
| if (!f) { return false; } | |
| f.seekg(static_cast<std::streamoff>(offset)); | |
| if (!f.good()) { return false; } | |
| char buf[8192]; | |
| while (length > 0) { | |
| auto to_read = (std::min)(sizeof(buf), length); | |
| f.read(buf, static_cast<std::streamsize>(to_read)); | |
| auto n = static_cast<size_t>(f.gcount()); | |
| if (n == 0) { break; } | |
| if (!sink.write(buf, n)) { return false; } | |
| length -= n; | |
| } | |
| return true; | |
| }; | |
| return {size, std::move(provider)}; | |
| } | |
| using ContentReceiverWithProgress = std::function<bool( | |
| const char *data, size_t data_length, size_t offset, size_t total_length)>; | |
| using ContentReceiver = | |
| std::function<bool(const char *data, size_t data_length)>; | |
| using FormDataHeader = std::function<bool(const FormData &file)>; | |
| class ContentReader { | |
| public: | |
| using Reader = std::function<bool(ContentReceiver receiver)>; | |
| using FormDataReader = | |
| std::function<bool(FormDataHeader header, ContentReceiver receiver)>; | |
| 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<ssize_t, ssize_t>; | |
| using Ranges = std::vector<Range>; | |
| // 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<SanEntry> sans() const; | |
| bool validity(time_t ¬_before, time_t ¬_after) const; | |
| std::string serial() const; | |
| }; | |
| using VerifyCallback = std::function<bool(const VerifyContext &ctx)>; | |
| // 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<SanEntry> 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<bool(ctx_t ctx)>; | |
| } // namespace tls | |
| 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<std::string, std::string> path_params; | |
| std::function<bool()> is_connection_closed = []() { return true; }; | |
| // for client | |
| std::vector<std::string> 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; | |
| std::vector<std::string> get_param_values(const std::string &key) const; | |
| size_t get_param_value_count(const std::string &key) const; | |
| bool is_multipart_form_data() const; | |
| // private members... | |
| bool body_consumed_ = false; | |
| 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<std::chrono::steady_clock> start_time_ = | |
| (std::chrono::steady_clock::time_point::min)(); | |
| tls::const_session_t ssl = nullptr; | |
| tls::PeerCert peer_cert() const; | |
| std::string sni() const; | |
| }; | |
| struct Response { | |
| std::string version; | |
| int status = -1; | |
| std::string reason; | |
| Headers headers; | |
| Headers trailers; | |
| std::string body; | |
| std::string location; // Redirect location | |
| // User-defined context — set by pre-routing/pre-request handlers and read | |
| // by route handlers to pass arbitrary data (e.g. decoded auth tokens). | |
| UserData user_data; | |
| 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 bool is_peer_alive() const { return wait_writable(); } | |
| 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; | |
| virtual void set_read_timeout(time_t sec, time_t usec = 0) { | |
| (void)sec; | |
| (void)usec; | |
| } | |
| 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<void()> fn) = 0; | |
| virtual void shutdown() = 0; | |
| virtual void on_idle() {} | |
| }; | |
| class ThreadPool final : public TaskQueue { | |
| public: | |
| explicit ThreadPool( | |
| size_t n, size_t max_n = 0, size_t mqr = 0, | |
| time_t idle_timeout_sec = CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT); | |
| ThreadPool(const ThreadPool &) = delete; | |
| ~ThreadPool() override = default; | |
| bool enqueue(std::function<void()> fn) override; | |
| void shutdown() override; | |
| private: | |
| void worker(bool is_dynamic); | |
| void move_to_finished(std::thread::id id); | |
| void cleanup_finished_threads(); | |
| size_t base_thread_count_; | |
| size_t max_thread_count_; | |
| size_t max_queued_requests_; | |
| time_t idle_timeout_sec_; | |
| size_t idle_thread_count_; | |
| bool shutdown_; | |
| std::list<std::function<void()>> jobs_; | |
| std::vector<std::thread> threads_; // base threads | |
| std::list<std::thread> dynamic_threads_; // dynamic threads | |
| std::vector<std::thread> | |
| finished_threads_; // exited dynamic threads awaiting join | |
| std::condition_variable cond_; | |
| std::mutex mutex_; | |
| }; | |
| using Logger = std::function<void(const Request &, const Response &)>; | |
| // Forward declaration for Error type | |
| enum class Error; | |
| using ErrorLogger = std::function<void(const Error &, const Request *)>; | |
| using SocketOptions = std::function<void(socket_t sock)>; | |
| void default_socket_options(socket_t sock); | |
| bool set_socket_opt(socket_t sock, int level, int optname, int optval); | |
| 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<std::string> static_fragments_; | |
| // Stores the names of the path parameters to be used as keys in the | |
| // Request::path_params map | |
| std::vector<std::string> 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) noexcept; | |
| 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); | |
| size_t get_multipart_content_length(const UploadFormDataItems &items, | |
| const std::string &boundary); | |
| ContentProvider | |
| make_multipart_content_provider(const UploadFormDataItems &items, | |
| const std::string &boundary); | |
| } // namespace detail | |
| bool is_valid_multipart_boundary(const std::string &boundary); | |
| // Serializer for multipart/form-data request bodies. The boundary is owned | |
| // by the writer so that per-part framing and the final terminator always | |
| // agree. Field names and filenames are escaped following the WHATWG HTML | |
| // standard ('"' -> %22, CR -> %0D, LF -> %0A); CR and LF are also escaped | |
| // in content types. | |
| class MultipartFormDataWriter { | |
| public: | |
| MultipartFormDataWriter(); | |
| // precondition: is_valid_multipart_boundary(boundary) | |
| explicit MultipartFormDataWriter(std::string boundary); | |
| const std::string &boundary() const; | |
| std::string content_type() const; | |
| // In-memory items -> whole body (known length) | |
| std::string serialize(const UploadFormDataItems &items) const; | |
| size_t content_length(const UploadFormDataItems &items) const; | |
| // Per-part framing for streaming via a content provider | |
| std::string item_begin(const UploadFormData &item) const; | |
| static std::string item_end(); | |
| std::string finish() const; | |
| private: | |
| std::string boundary_; | |
| }; | |
| class Server { | |
| public: | |
| using Handler = std::function<void(const Request &, Response &)>; | |
| using ExceptionHandler = | |
| std::function<void(const Request &, Response &, std::exception_ptr ep)>; | |
| enum class HandlerResponse { | |
| Handled, | |
| Unhandled, | |
| }; | |
| using HandlerWithResponse = | |
| std::function<HandlerResponse(const Request &, Response &)>; | |
| using HandlerWithContentReader = std::function<void( | |
| const Request &, Response &, const ContentReader &content_reader)>; | |
| using Expect100ContinueHandler = | |
| std::function<int(const Request &, Response &)>; | |
| using StartHandler = std::function<void()>; | |
| using WebSocketHandler = | |
| std::function<void(const Request &, ws::WebSocket &)>; | |
| using SubProtocolSelector = | |
| std::function<std::string(const std::vector<std::string> &protocols)>; | |
| 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); | |
| Server &WebSocket(const std::string &pattern, WebSocketHandler handler); | |
| Server &WebSocket(const std::string &pattern, WebSocketHandler handler, | |
| SubProtocolSelector sub_protocol_selector); | |
| 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 <class ErrorHandlerFunc> | |
| Server &set_error_handler(ErrorHandlerFunc &&handler) { | |
| return set_error_handler_core( | |
| std::forward<ErrorHandlerFunc>(handler), | |
| std::is_convertible<ErrorHandlerFunc, HandlerWithResponse>{}); | |
| } | |
| 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_start_handler(StartHandler 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<ssize_t(Stream &, Headers &)> const &writer); | |
| Server &set_trusted_proxies(const std::vector<std::string> &proxies); | |
| Server &set_keep_alive_max_count(size_t count); | |
| Server &set_keep_alive_timeout(time_t sec); | |
| template <class Rep, class Period> | |
| Server & | |
| set_keep_alive_timeout(const std::chrono::duration<Rep, Period> &duration); | |
| Server &set_read_timeout(time_t sec, time_t usec = 0); | |
| template <class Rep, class Period> | |
| Server &set_read_timeout(const std::chrono::duration<Rep, Period> &duration); | |
| Server &set_write_timeout(time_t sec, time_t usec = 0); | |
| template <class Rep, class Period> | |
| Server &set_write_timeout(const std::chrono::duration<Rep, Period> &duration); | |
| Server &set_idle_interval(time_t sec, time_t usec = 0); | |
| template <class Rep, class Period> | |
| Server &set_idle_interval(const std::chrono::duration<Rep, Period> &duration); | |
| Server &set_payload_max_length(size_t length); | |
| Server &set_websocket_ping_interval(time_t sec); | |
| template <class Rep, class Period> | |
| Server &set_websocket_ping_interval( | |
| const std::chrono::duration<Rep, Period> &duration); | |
| Server &set_websocket_max_missed_pongs(int count); | |
| 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() noexcept; | |
| void decommission(); | |
| std::function<TaskQueue *(void)> 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<void(Request &)> &setup_request, | |
| bool *websocket_upgraded = nullptr); | |
| std::atomic<socket_t> svr_sock_{INVALID_SOCKET}; | |
| std::vector<std::string> 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; | |
| time_t websocket_ping_interval_sec_ = | |
| CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND; | |
| int websocket_max_missed_pongs_ = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS; | |
| private: | |
| using Handlers = | |
| std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, Handler>>; | |
| using HandlersForContentReader = | |
| std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, | |
| HandlerWithContentReader>>; | |
| static std::unique_ptr<detail::MatcherBase> | |
| make_matcher(const std::string &pattern); | |
| template <typename H> | |
| Server &add_handler( | |
| std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, H>> &handlers, | |
| const std::string &pattern, H handler) { | |
| handlers.emplace_back(make_matcher(pattern), std::move(handler)); | |
| return *this; | |
| } | |
| 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, | |
| Stream &strm); | |
| 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<bool> is_running_{false}; | |
| std::atomic<bool> is_decommissioned{false}; | |
| struct MountPointEntry { | |
| std::string mount_point; | |
| std::string base_dir; | |
| std::string resolved_base_dir; | |
| Headers headers; | |
| }; | |
| std::vector<MountPointEntry> base_dirs_; | |
| std::map<std::string, std::string> 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_; | |
| struct WebSocketHandlerEntry { | |
| std::unique_ptr<detail::MatcherBase> matcher; | |
| WebSocketHandler handler; | |
| SubProtocolSelector sub_protocol_selector; | |
| }; | |
| using WebSocketHandlers = std::vector<WebSocketHandlerEntry>; | |
| WebSocketHandlers websocket_handlers_; | |
| HandlerWithResponse error_handler_; | |
| ExceptionHandler exception_handler_; | |
| HandlerWithResponse pre_routing_handler_; | |
| Handler post_routing_handler_; | |
| HandlerWithResponse pre_request_handler_; | |
| Expect100ContinueHandler expect_100_continue_handler_; | |
| StartHandler start_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<ssize_t(Stream &, Headers &)> header_writer_ = | |
| detail::write_headers; | |
| }; | |
| class Result { | |
| public: | |
| Result() = default; | |
| Result(std::unique_ptr<Response> &&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<Response> res_; | |
| Error err_ = Error::Unknown; | |
| Headers request_headers_; | |
| public: | |
| Result(std::unique_ptr<Response> &&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<Response> &&res, Error err, Headers &&request_headers, | |
| int ssl_error, uint64_t 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_; } | |
| uint64_t ssl_backend_error() const { return ssl_backend_error_; } | |
| private: | |
| int ssl_error_ = 0; | |
| uint64_t ssl_backend_error_ = 0; | |
| }; | |
| 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) | |
| , | |
| session(other.session) | |
| { | |
| other.sock = INVALID_SOCKET; | |
| other.session = nullptr; | |
| } | |
| ClientConnection &operator=(ClientConnection &&other) noexcept { | |
| if (this != &other) { | |
| sock = other.sock; | |
| other.sock = INVALID_SOCKET; | |
| session = other.session; | |
| other.session = nullptr; | |
| } | |
| return *this; | |
| } | |
| tls::session_t session = nullptr; | |
| }; | |
| 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<ChunkedDecoder> 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; | |
| enum class NoProxyKind { | |
| Wildcard, // "*" | |
| HostnameSuffix, // "example.com" or ".example.com" | |
| IPv4Cidr, // "10.0.0.0/8" (or single IP, treated as /32) | |
| IPv6Cidr, // "fe80::/10" (or single IP, treated as /128) | |
| }; | |
| // Unified 16-byte buffer holding either a v4 (first 4 bytes) or v6 address. | |
| // Lets one CIDR matcher cover both families. | |
| using IPBytes = std::array<uint8_t, 16>; | |
| struct NoProxyEntry { | |
| NoProxyKind kind = NoProxyKind::Wildcard; | |
| std::string hostname_pattern; // lowercased, leading/trailing dot stripped | |
| IPBytes net{}; | |
| int prefix_bits = 0; | |
| }; | |
| struct NormalizedTarget { | |
| std::string hostname; // lowercase; brackets and trailing dot removed | |
| bool is_ipv4 = false; | |
| bool is_ipv6 = false; | |
| IPBytes ip{}; | |
| }; | |
| } // 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> 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<ClientConnection> connection_; | |
| std::unique_ptr<Stream> socket_stream_; | |
| Stream *stream_ = nullptr; | |
| detail::BodyReader body_reader_; | |
| std::unique_ptr<detail::decompressor> 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<std::string, std::string> addr_map); | |
| void set_default_headers(Headers headers); | |
| void | |
| set_header_writer(std::function<ssize_t(Stream &, Headers &)> 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 <class Rep, class Period> | |
| void | |
| set_connection_timeout(const std::chrono::duration<Rep, Period> &duration); | |
| void set_read_timeout(time_t sec, time_t usec = 0); | |
| template <class Rep, class Period> | |
| void set_read_timeout(const std::chrono::duration<Rep, Period> &duration); | |
| void set_write_timeout(time_t sec, time_t usec = 0); | |
| template <class Rep, class Period> | |
| void set_write_timeout(const std::chrono::duration<Rep, Period> &duration); | |
| void set_max_timeout(time_t msec); | |
| template <class Rep, class Period> | |
| void set_max_timeout(const std::chrono::duration<Rep, Period> &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_no_proxy(const std::vector<std::string> &patterns); | |
| 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<std::chrono::steady_clock> start_time_; | |
| bool is_open() const { return sock != INVALID_SOCKET; } | |
| tls::session_t ssl = nullptr; | |
| }; | |
| virtual bool create_and_connect_socket(Socket &socket, Error &error); | |
| virtual bool ensure_socket_connection(Socket &socket, Error &error); | |
| virtual bool setup_proxy_connection( | |
| Socket &socket, | |
| std::chrono::time_point<std::chrono::steady_clock> start_time, | |
| Response &res, bool &success, Error &error); | |
| bool is_proxy_enabled_for_host(const std::string &host) const; | |
| // All of: | |
| // shutdown_ssl | |
| // shutdown_socket | |
| // close_socket | |
| // disconnect | |
| // should ONLY be called when socket_mutex_ is locked, and only when | |
| // no other thread is using the socket. | |
| virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); | |
| void shutdown_socket(Socket &socket) const; | |
| void close_socket(Socket &socket); | |
| void disconnect(bool gracefully); | |
| 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<std::string, std::string> addr_map_; | |
| // Default headers | |
| Headers default_headers_; | |
| // Header writer | |
| std::function<ssize_t(Stream &, Headers &)> 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_; | |
| std::vector<detail::NoProxyEntry> no_proxy_entries_; | |
| mutable detail::NormalizedTarget host_normalized_; | |
| mutable bool host_normalized_valid_ = false; | |
| 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 <typename ClientType> void setup_redirect_client(ClientType &client); | |
| bool handle_request(Stream &strm, Request &req, Response &res, | |
| bool close_connection, Error &error); | |
| std::unique_ptr<Response> 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<std::chrono::steady_clock> start_time, | |
| std::function<bool(Stream &strm)> callback); | |
| virtual bool is_ssl() const; | |
| void transfer_socket_ownership_to_handle(StreamHandle &handle); | |
| 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); | |
| void enable_system_ca(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; | |
| SystemCAMode system_ca_mode_ = SystemCAMode::Auto; | |
| std::string ca_cert_pem_; // Store CA cert PEM for redirect transfer | |
| int last_ssl_error_ = 0; | |
| uint64_t last_backend_error_ = 0; | |
| }; | |
| 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<std::string, std::string> addr_map); | |
| void set_default_headers(Headers headers); | |
| void | |
| set_header_writer(std::function<ssize_t(Stream &, Headers &)> 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 <class Rep, class Period> | |
| void | |
| set_connection_timeout(const std::chrono::duration<Rep, Period> &duration); | |
| void set_read_timeout(time_t sec, time_t usec = 0); | |
| template <class Rep, class Period> | |
| void set_read_timeout(const std::chrono::duration<Rep, Period> &duration); | |
| void set_write_timeout(time_t sec, time_t usec = 0); | |
| template <class Rep, class Period> | |
| void set_write_timeout(const std::chrono::duration<Rep, Period> &duration); | |
| void set_max_timeout(time_t msec); | |
| template <class Rep, class Period> | |
| void set_max_timeout(const std::chrono::duration<Rep, Period> &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_no_proxy(const std::vector<std::string> &patterns); | |
| void set_logger(Logger logger); | |
| void set_error_logger(ErrorLogger error_logger); | |
| private: | |
| std::unique_ptr<ClientImpl> cli_; | |
| 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 enable_system_ca(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<SSLVerifierResponse(tls::session_t)> verifier); | |
| tls::ctx_t tls_context() const; | |
| void enable_windows_certificate_verification(bool enabled); | |
| private: | |
| bool is_ssl_ = false; | |
| }; | |
| 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; | |
| }; | |
| 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<SSLVerifierResponse(tls::session_t)> verifier); | |
| tls::ctx_t tls_context() const { return ctx_; } | |
| void enable_windows_certificate_verification(bool enabled); | |
| 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<std::chrono::steady_clock> start_time, | |
| std::function<bool(Stream &strm)> callback) override; | |
| bool is_ssl() const override; | |
| bool setup_proxy_connection( | |
| Socket &socket, | |
| std::chrono::time_point<std::chrono::steady_clock> start_time, | |
| Response &res, bool &success, Error &error) override; | |
| bool connect_with_proxy( | |
| Socket &sock, | |
| std::chrono::time_point<std::chrono::steady_clock> start_time, | |
| Response &res, bool &success, Error &error); | |
| bool initialize_ssl(Socket &socket, Error &error); | |
| void init_ctx(); | |
| void reset_ctx_on_error(); | |
| bool load_certs(); | |
| tls::ctx_t ctx_ = nullptr; | |
| std::mutex ctx_mutex_; | |
| std::once_flag initialize_cert_; | |
| // Tracks whether a custom CA store was applied via set_ca_cert_store(), | |
| // since the store handle itself is owned by ctx_ and leaves no other trace. | |
| // Used to keep custom CA configuration exclusive with system CA loading. | |
| bool ca_cert_store_set_ = false; | |
| long verify_result_ = 0; | |
| std::function<SSLVerifierResponse(tls::session_t)> session_verifier_; | |
| bool enable_windows_cert_verification_ = true; | |
| friend class ClientImpl; | |
| }; | |
| namespace detail { | |
| template <typename T, typename U> | |
| inline void duration_to_sec_and_usec(const T &duration, U callback) { | |
| auto sec = std::chrono::duration_cast<std::chrono::seconds>(duration).count(); | |
| auto usec = std::chrono::duration_cast<std::chrono::microseconds>( | |
| duration - std::chrono::seconds(sec)) | |
| .count(); | |
| callback(static_cast<time_t>(sec), static_cast<time_t>(usec)); | |
| } | |
| template <size_t N> 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(), is_ascii_digit); | |
| } | |
| 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<ssize_t>(id)); | |
| if (it != rng.second) { | |
| if (is_numeric(it->second)) { | |
| return static_cast<size_t>(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 <class Rep, class Period> | |
| inline Server & | |
| Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) { | |
| detail::duration_to_sec_and_usec( | |
| duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); | |
| return *this; | |
| } | |
| template <class Rep, class Period> | |
| inline Server & | |
| Server::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) { | |
| detail::duration_to_sec_and_usec( | |
| duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); | |
| return *this; | |
| } | |
| template <class Rep, class Period> | |
| inline Server & | |
| Server::set_idle_interval(const std::chrono::duration<Rep, Period> &duration) { | |
| detail::duration_to_sec_and_usec( | |
| duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); | |
| return *this; | |
| } | |
| template <class Rep, class Period> | |
| inline void ClientImpl::set_connection_timeout( | |
| const std::chrono::duration<Rep, Period> &duration) { | |
| detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { | |
| set_connection_timeout(sec, usec); | |
| }); | |
| } | |
| template <class Rep, class Period> | |
| inline void ClientImpl::set_read_timeout( | |
| const std::chrono::duration<Rep, Period> &duration) { | |
| detail::duration_to_sec_and_usec( | |
| duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); | |
| } | |
| template <class Rep, class Period> | |
| inline void ClientImpl::set_write_timeout( | |
| const std::chrono::duration<Rep, Period> &duration) { | |
| detail::duration_to_sec_and_usec( | |
| duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); | |
| } | |
| template <class Rep, class Period> | |
| inline void ClientImpl::set_max_timeout( | |
| const std::chrono::duration<Rep, Period> &duration) { | |
| auto msec = | |
| std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(); | |
| set_max_timeout(msec); | |
| } | |
| template <class Rep, class Period> | |
| inline void Client::set_connection_timeout( | |
| const std::chrono::duration<Rep, Period> &duration) { | |
| cli_->set_connection_timeout(duration); | |
| } | |
| template <class Rep, class Period> | |
| inline void | |
| Client::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) { | |
| cli_->set_read_timeout(duration); | |
| } | |
| template <class Rep, class Period> | |
| inline void | |
| Client::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) { | |
| cli_->set_write_timeout(duration); | |
| } | |
| inline void Client::set_max_timeout(time_t msec) { | |
| cli_->set_max_timeout(msec); | |
| } | |
| template <class Rep, class Period> | |
| inline void | |
| Client::set_max_timeout(const std::chrono::duration<Rep, Period> &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<std::string> &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 sanitize_filename(const std::string &filename); | |
| std::string append_query_params(const std::string &path, const Params ¶ms); | |
| std::pair<std::string, std::string> make_range_header(const Ranges &ranges); | |
| std::pair<std::string, std::string> | |
| make_basic_authentication_header(const std::string &username, | |
| const std::string &password, | |
| bool is_proxy = false); | |
| namespace detail { | |
| inline std::wstring u8string_to_wstring(const char *s) { | |
| if (!s) { return std::wstring(); } | |
| auto len = static_cast<int>(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<LPWSTR>(reinterpret_cast<LPCWSTR>(ws.data())), wlen); | |
| if (wlen != static_cast<int>(ws.size())) { ws.clear(); } | |
| return ws; | |
| } | |
| struct FileStat { | |
| FileStat(const std::string &path); | |
| bool is_file() const; | |
| bool is_dir() const; | |
| time_t mtime() const; | |
| size_t size() const; | |
| private: | |
| struct _stat st_; | |
| struct stat st_; | |
| 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<void(const char *, std::size_t, const char *, std::size_t)> | |
| fn); | |
| void divide( | |
| const std::string &str, char d, | |
| std::function<void(const char *, std::size_t, const char *, std::size_t)> | |
| fn); | |
| void split(const char *b, const char *e, char d, | |
| std::function<void(const char *, const char *)> fn); | |
| void split(const char *b, const char *e, char d, size_t m, | |
| std::function<void(const char *, const char *)> 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<std::chrono::steady_clock> start_time, | |
| std::function<bool(Stream &)> callback); | |
| socket_t create_client_socket(const std::string &host, const std::string &ip, | |
| int port, int address_family, bool tcp_nodelay, | |
| 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<std::string> &content_types); | |
| 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<bool(const char *data, size_t data_len)> 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<bool(const char *data, size_t data_len)> 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; | |
| }; | |
| 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_; | |
| }; | |
| 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; | |
| }; | |
| 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; | |
| }; | |
| // 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: | |
| HANDLE hFile_ = NULL; | |
| HANDLE hMapping_ = NULL; | |
| int fd_ = -1; | |
| 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 | |
| */ | |
| // TLS abstraction layer - backend-specific type declarations | |
| 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 | |
| namespace tls { | |
| namespace impl { | |
| // wolfSSL context wrapper (holds WOLFSSL_CTX and related state). | |
| // This struct is accessible via tls::impl for use in SSL context | |
| // setup callbacks (cast ctx_t to tls::impl::WolfSSLContext*). | |
| struct WolfSSLContext { | |
| WOLFSSL_CTX *ctx = nullptr; | |
| bool is_server = false; | |
| bool verify_client = false; | |
| bool has_verify_callback = false; | |
| std::string ca_pem_data_; // accumulated PEM for get_ca_names/get_ca_certs | |
| WolfSSLContext(); | |
| ~WolfSSLContext(); | |
| WolfSSLContext(const WolfSSLContext &) = delete; | |
| WolfSSLContext &operator=(const WolfSSLContext &) = delete; | |
| }; | |
| // CA store for wolfSSL: holds raw PEM bytes to allow reloading into any ctx | |
| struct WolfSSLCAStore { | |
| std::string pem_data; | |
| }; | |
| } // namespace impl | |
| } // namespace tls | |
| 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 <typename ClientType> | |
| inline Result Get(ClientType &cli, const std::string &path, | |
| size_t chunk_size = 8192) { | |
| return Result{cli.open_stream("GET", path), chunk_size}; | |
| } | |
| template <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| inline Result Delete(ClientType &cli, const std::string &path, | |
| size_t chunk_size = 8192) { | |
| return Result{cli.open_stream("DELETE", path), chunk_size}; | |
| } | |
| template <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| inline Result Head(ClientType &cli, const std::string &path, | |
| size_t chunk_size = 8192) { | |
| return Result{cli.open_stream("HEAD", path), chunk_size}; | |
| } | |
| template <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| inline Result Options(ClientType &cli, const std::string &path, | |
| size_t chunk_size = 8192) { | |
| return Result{cli.open_stream("OPTIONS", path), chunk_size}; | |
| } | |
| template <typename ClientType> | |
| 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 <typename ClientType> | |
| 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 <typename ClientType> | |
| 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<void(const SSEMessage &)>; | |
| using ErrorHandler = std::function<void(Error)>; | |
| using OpenHandler = std::function<void()>; | |
| 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); | |
| // Update headers (thread-safe) | |
| SSEClient &set_headers(const Headers &headers); | |
| // 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_; | |
| mutable std::mutex headers_mutex_; | |
| // Callbacks | |
| MessageHandler on_message_; | |
| std::map<std::string, MessageHandler> event_handlers_; | |
| OpenHandler on_open_; | |
| ErrorHandler on_error_; | |
| // Configuration | |
| int reconnect_interval_ms_ = 3000; | |
| int max_reconnect_attempts_ = 0; // 0 = unlimited | |
| // State | |
| std::atomic<bool> running_{false}; | |
| std::atomic<bool> connected_{false}; | |
| std::string last_event_id_; | |
| // Async support | |
| std::thread async_thread_; | |
| }; | |
| } // namespace sse | |
| namespace ws { | |
| enum class Opcode : uint8_t { | |
| Continuation = 0x0, | |
| Text = 0x1, | |
| Binary = 0x2, | |
| Close = 0x8, | |
| Ping = 0x9, | |
| Pong = 0xA, | |
| }; | |
| enum class CloseStatus : uint16_t { | |
| Normal = 1000, | |
| GoingAway = 1001, | |
| ProtocolError = 1002, | |
| UnsupportedData = 1003, | |
| NoStatus = 1005, | |
| Abnormal = 1006, | |
| InvalidPayload = 1007, | |
| PolicyViolation = 1008, | |
| MessageTooBig = 1009, | |
| MandatoryExtension = 1010, | |
| InternalError = 1011, | |
| }; | |
| enum ReadResult : int { Fail = 0, Text = 1, Binary = 2 }; | |
| class WebSocket { | |
| public: | |
| WebSocket(const WebSocket &) = delete; | |
| WebSocket &operator=(const WebSocket &) = delete; | |
| ~WebSocket(); | |
| ReadResult read(std::string &msg); | |
| bool send(const std::string &data); | |
| bool send(const char *data, size_t len); | |
| void close(CloseStatus status = CloseStatus::Normal, | |
| const std::string &reason = ""); | |
| const Request &request() const; | |
| bool is_open() const; | |
| private: | |
| friend class httplib::Server; | |
| friend class WebSocketClient; | |
| WebSocket( | |
| Stream &strm, const Request &req, bool is_server, | |
| time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND, | |
| int max_missed_pongs = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS) | |
| : strm_(strm), req_(req), is_server_(is_server), | |
| ping_interval_sec_(ping_interval_sec), | |
| max_missed_pongs_(max_missed_pongs) { | |
| start_heartbeat(); | |
| } | |
| WebSocket( | |
| std::unique_ptr<Stream> &&owned_strm, const Request &req, bool is_server, | |
| time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND, | |
| int max_missed_pongs = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS) | |
| : strm_(*owned_strm), owned_strm_(std::move(owned_strm)), req_(req), | |
| is_server_(is_server), ping_interval_sec_(ping_interval_sec), | |
| max_missed_pongs_(max_missed_pongs) { | |
| start_heartbeat(); | |
| } | |
| void start_heartbeat(); | |
| bool send_frame(Opcode op, const char *data, size_t len, bool fin = true); | |
| Stream &strm_; | |
| std::unique_ptr<Stream> owned_strm_; | |
| Request req_; | |
| bool is_server_; | |
| time_t ping_interval_sec_; | |
| int max_missed_pongs_; | |
| int unacked_pings_ = 0; | |
| std::atomic<bool> closed_{false}; | |
| std::mutex write_mutex_; | |
| std::thread ping_thread_; | |
| std::mutex ping_mutex_; | |
| std::condition_variable ping_cv_; | |
| }; | |
| class WebSocketClient { | |
| public: | |
| explicit WebSocketClient(const std::string &scheme_host_port_path, | |
| const Headers &headers = {}); | |
| ~WebSocketClient(); | |
| WebSocketClient(const WebSocketClient &) = delete; | |
| WebSocketClient &operator=(const WebSocketClient &) = delete; | |
| bool is_valid() const; | |
| bool connect(); | |
| ReadResult read(std::string &msg); | |
| bool send(const std::string &data); | |
| bool send(const char *data, size_t len); | |
| void close(CloseStatus status = CloseStatus::Normal, | |
| const std::string &reason = ""); | |
| bool is_open() const; | |
| const std::string &subprotocol() const; | |
| void set_read_timeout(time_t sec, time_t usec = 0); | |
| void set_write_timeout(time_t sec, time_t usec = 0); | |
| void set_websocket_ping_interval(time_t sec); | |
| void set_websocket_max_missed_pongs(int count); | |
| void set_tcp_nodelay(bool on); | |
| void set_address_family(int family); | |
| void set_ipv6_v6only(bool on); | |
| void set_socket_options(SocketOptions socket_options); | |
| void set_connection_timeout(time_t sec, time_t usec = 0); | |
| void set_interface(const std::string &intf); | |
| void set_hostname_addr_map(std::map<std::string, std::string> addr_map); | |
| void set_ca_cert_path(const std::string &path); | |
| void set_ca_cert_store(tls::ca_store_t store); | |
| void load_ca_cert_store(const char *ca_cert, std::size_t size); | |
| void enable_server_certificate_verification(bool enabled); | |
| void enable_system_ca(bool enabled); | |
| private: | |
| void shutdown_and_close(); | |
| bool create_stream(std::unique_ptr<Stream> &strm); | |
| std::string host_; | |
| int port_; | |
| std::string path_; | |
| Headers headers_; | |
| std::string subprotocol_; | |
| bool is_valid_ = false; | |
| socket_t sock_ = INVALID_SOCKET; | |
| std::unique_ptr<WebSocket> ws_; | |
| time_t read_timeout_sec_ = CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND; | |
| time_t read_timeout_usec_ = 0; | |
| time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND; | |
| time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND; | |
| time_t websocket_ping_interval_sec_ = | |
| CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND; | |
| int websocket_max_missed_pongs_ = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS; | |
| int address_family_ = AF_UNSPEC; | |
| bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; | |
| bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY; | |
| SocketOptions socket_options_ = nullptr; | |
| time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; | |
| time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; | |
| std::string interface_; | |
| // Hostname-IP map | |
| std::map<std::string, std::string> addr_map_; | |
| bool is_ssl_ = false; | |
| tls::ctx_t tls_ctx_ = nullptr; | |
| tls::session_t tls_session_ = nullptr; | |
| std::string ca_cert_file_path_; | |
| bool custom_ca_loaded_ = false; | |
| bool certs_loaded_ = false; | |
| SystemCAMode system_ca_mode_ = SystemCAMode::Auto; | |
| bool server_certificate_verification_ = true; | |
| }; | |
| namespace impl { | |
| bool is_valid_utf8(const std::string &s); | |
| bool read_websocket_frame(Stream &strm, Opcode &opcode, std::string &payload, | |
| bool &fin, bool expect_masked, size_t max_len); | |
| } // namespace impl | |
| } // namespace ws | |
| } // namespace httplib | |