commit 8587e5c84a02112ff630f5474be46bbac725c90a Author: Quella777 <2892744389@qq.com> Date: Sun Sep 28 16:03:54 2025 +0800 finnal diff --git a/ApCreate/NetraLib/README.md b/ApCreate/NetraLib/README.md new file mode 100644 index 0000000..c680108 --- /dev/null +++ b/ApCreate/NetraLib/README.md @@ -0,0 +1,128 @@ +# NetraLib +c/c++基本开发库 + +# TCP 服务端操作 +包括多线程客户端连接,指定客户端数据的收发等等功能 + +# Linux 中屏蔽所有信号操作 +屏蔽所有信号,以防止意外退出 + + +# 写文件操作 +允许原文本进行覆盖写,追加写 +允许二进制进行覆盖写,追加写 +允许在特定位置后面进行插入覆盖操作 +允许删除特定字段后面所有内容在进行写操作 +可以根据需要计算特定符号最后一个字节或者第一个字节所在位置所在位置 + +所有操作都添加mutex锁机制 ,保障线程安全 + + +# 读文件操作 +支持全文读取(文本和二进制模式) +支持按行读取文本内容 +支持按指定字节数读取数据 +支持计算第一个指定字节序列结束位置(包含该序列本身)的字节数 +提供文件是否存在和文件大小查询 +支持重置文件读取位置,实现多次读取 + +所有操作都添加mutex锁机制 ,保障线程安全 + + +# 字符串操作 +支持左右空格删除 +支持格式化输出 + + +# Http请求 +提供基于 `cpp-httplib` 的简易 HTTP 客户端封装 `NetRequest`,支持: +1. 同步/异步 GET、POST(JSON、表单) +2. 连接/读写超时设置、Keep-Alive +3. 并发请求上限控制 +4. 可选内存缓存(GET 命中时不发起网络请求) +5. 简单日志回调与性能统计 +6. 断点续传下载到本地文件 + +使用步骤: + +1) 引入头文件,配置目标地址 +```cpp +#include "NetRequest.hpp" + +ntq::RequestOptions opt; +opt.scheme = "http"; // 或 https +opt.host = "127.0.0.1"; // 服务器地址 +opt.port = 8080; // 端口(https 一般 443) +opt.base_path = "/api"; // 可选统一前缀 +opt.connect_timeout_ms = 3000; +opt.read_timeout_ms = 8000; +opt.write_timeout_ms = 8000; +opt.default_headers = { {"Authorization", "Bearer TOKEN"} }; // 可选 + +ntq::NetRequest req(opt); +req.setMaxConcurrentRequests(4); +req.enableCache(std::chrono::seconds(10)); +``` + +2) 发送 GET 请求 +```cpp +auto r = req.Get("/info"); +if (r && r->status == 200) { + // r->body 为返回内容 +} + +// 带查询参数与额外请求头 +httplib::Params q = {{"q","hello"},{"page","1"}}; +httplib::Headers h = {{"X-Req-Id","123"}}; +auto r2 = req.Get("/search", q, h); +``` + +3) 发送 POST 请求 +```cpp +// JSON(Content-Type: application/json) +std::string json = R"({"name":"orangepi","mode":"demo"})"; +auto p1 = req.PostJson("/set", json); + +// 表单(application/x-www-form-urlencoded) +httplib::Params form = {{"user","abc"},{"pwd","123"}}; +auto p2 = req.PostForm("/login", form); +``` + +4) 异步调用 +```cpp +auto fut = req.GetAsync("/info"); +auto res = fut.get(); // 与同步用法一致 +``` + +5) 下载到本地(支持断点续传) +```cpp +bool ok = req.DownloadToFile("/files/pkg.bin", "/tmp/pkg.bin", {}, /*resume*/true); +``` + +6) 统计信息 +```cpp +auto s = req.getStats(); +// s.total_requests / s.total_errors / s.last_latency_ms / s.avg_latency_ms +``` + +说明: +- 若使用 HTTPS,需在编译时添加 `-DCPPHTTPLIB_OPENSSL_SUPPORT` 并链接 `-lssl -lcrypto`,且将 `opt.scheme` 设为 `"https"`、端口通常为 `443`。 +- `base_path` 与各函数传入的 `path` 会自动合并,例如 `base_path="/api"` 且 `Get("/info")` 实际请求路径为 `/api/info`。 + +7) 快速用法(无需实例化) +```cpp +using ntq::NetRequest; + +// 直接传完整 URL 发起 GET +auto g = NetRequest::QuickGet("http://127.0.0.1:8080/api/info"); + +// 直接传完整 URL 发起 POST(JSON) +auto p1 = NetRequest::QuickPostJson( + "http://127.0.0.1:8080/api/set", + R"({"name":"orangepi","mode":"demo"})" +); + +// 直接传完整 URL 发起 POST(表单) +httplib::Params form = {{"user","abc"},{"pwd","123"}}; +auto p2 = NetRequest::QuickPostForm("http://127.0.0.1:8080/login", form); +``` diff --git a/ApCreate/NetraLib/include/NetRequest.hpp b/ApCreate/NetraLib/include/NetRequest.hpp new file mode 100644 index 0000000..3cb473c --- /dev/null +++ b/ApCreate/NetraLib/include/NetRequest.hpp @@ -0,0 +1,344 @@ +/* +本文件 +网络请求类需要实现以下功能: +1. 发送网络请求 +2. 接收网络响应 +3. 处理网络请求和响应 +4. 实现网络请求和响应的回调函数 +5. 实现网络请求和响应的错误处理 +6. 实现网络请求和响应的日志记录 +7. 实现网络请求和响应的性能统计 +8. 实现网络请求和响应的并发控制 +9. 实现网络请求和响应的缓存管理 +10. 实现网络请求和响应的断点续传 +11. 实现网络请求和响应的断点续传 +12. 实现网络请求和响应的断点续传 +13. 实现网络请求和响应的断点续传 +14. 实现网络请求和响应的断点续传 +*/ +#pragma once +#include "httplib.h" +#include +#include +#include +#include + +// C++17/14 可选类型回退适配:统一使用 ntq::optional / ntq::nullopt +#if defined(__has_include) + #if __has_include() + #include + // 仅当启用了 C++17 或库声明了 optional 功能时,才使用 std::optional + #if defined(__cpp_lib_optional) || (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) + namespace ntq { template using optional = std::optional; } + namespace ntq { constexpr auto nullopt = std::nullopt; using nullopt_t = decltype(std::nullopt); } + #elif __has_include() + #include + namespace ntq { template using optional = std::experimental::optional; } + namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); } + #else + #include + namespace ntq { + struct nullopt_t { explicit constexpr nullopt_t(int) {} }; + static constexpr nullopt_t nullopt{0}; + template + class optional { + public: + optional() : has_(false) {} + optional(nullopt_t) : has_(false) {} + optional(const T &v) : has_(true), value_(v) {} + optional(T &&v) : has_(true), value_(std::move(v)) {} + optional(const optional &o) : has_(o.has_), value_(o.has_ ? o.value_ : T{}) {} + optional(optional &&o) noexcept : has_(o.has_), value_(std::move(o.value_)) {} + optional &operator=(nullopt_t) { has_ = false; return *this; } + optional &operator=(const T &v) { value_ = v; has_ = true; return *this; } + optional &operator=(T &&v) { value_ = std::move(v); has_ = true; return *this; } + explicit operator bool() const { return has_; } + bool has_value() const { return has_; } + T &value() { return value_; } + const T &value() const { return value_; } + T &operator*() { return value_; } + const T &operator*() const { return value_; } + private: + bool has_ = false; + T value_{}; + }; + } + #endif + #elif __has_include() + #include + namespace ntq { template using optional = std::experimental::optional; } + namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); } + #else + #include + namespace ntq { + struct nullopt_t { explicit constexpr nullopt_t(int) {} }; + static constexpr nullopt_t nullopt{0}; + template + class optional { + public: + optional() : has_(false) {} + optional(nullopt_t) : has_(false) {} + optional(const T &v) : has_(true), value_(v) {} + optional(T &&v) : has_(true), value_(std::move(v)) {} + optional(const optional &o) : has_(o.has_), value_(o.has_ ? o.value_ : T{}) {} + optional(optional &&o) noexcept : has_(o.has_), value_(std::move(o.value_)) {} + optional &operator=(nullopt_t) { has_ = false; return *this; } + optional &operator=(const T &v) { value_ = v; has_ = true; return *this; } + optional &operator=(T &&v) { value_ = std::move(v); has_ = true; return *this; } + explicit operator bool() const { return has_; } + bool has_value() const { return has_; } + T &value() { return value_; } + const T &value() const { return value_; } + T &operator*() { return value_; } + const T &operator*() const { return value_; } + private: + bool has_ = false; + T value_{}; + }; + } + #endif +#else + // 无 __has_include:按语言级别判断 + #if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) + #include + namespace ntq { template using optional = std::optional; } + namespace ntq { constexpr auto nullopt = std::nullopt; using nullopt_t = decltype(std::nullopt); } + #else + #include + namespace ntq { template using optional = std::experimental::optional; } + namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); } + #endif +#endif + +namespace ntq +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @brief HTTP 响应对象 + * + * 用于承载一次请求返回的状态码、正文与响应头; + * 当 from_cache 为 true 时,表示该响应来自本地缓存(而非实时网络)。 + */ + struct HttpResponse + { + int status = 0; ///< HTTP 状态码(例如 200, 404 等) + std::string body; ///< 响应正文 + httplib::Headers headers; ///< 响应头(大小写不敏感) + bool from_cache = false; ///< 是否来自缓存 + }; + + /** + * @brief 错误码定义 + */ + enum class ErrorCode + { + None = 0, ///< 无错误 + Network, ///< 网络错误(连接失败/发送失败/接收失败等) + Timeout, ///< 超时 + Canceled, ///< 被取消 + InvalidURL, ///< URL 非法 + IOError, ///< 本地 IO 错误(如写文件失败) + SSL, ///< SSL/HTTPS 相关错误 + Unknown ///< 未分类错误 + }; + + /** + * @brief 请求级别的参数配置 + * + * 包含基础连接信息(协议、主机、端口)与默认头部、超时设置等。 + */ + struct RequestOptions + { + std::string scheme = "http"; ///< 协议:http 或 https + std::string host; ///< 目标主机名或 IP(必填) + int port = 80; ///< 端口,http 默认为 80,https 通常为 443 + std::string base_path; ///< 可选的统一前缀(例如 "/api/v1") + + int connect_timeout_ms = 5000; ///< 连接超时(毫秒) + int read_timeout_ms = 10000; ///< 读取超时(毫秒) + int write_timeout_ms = 10000; ///< 写入超时(毫秒) + bool keep_alive = true; ///< 是否保持连接(Keep-Alive) + + httplib::Headers default_headers; ///< 默认头部,随所有请求发送 + }; + + /** + * @class NetRequest + * @brief HTTP 客户端封装(基于 cpp-httplib) + * + * 提供同步和异步的 GET/POST 请求、并发限制、可选缓存、日志回调、 + * 性能统计以及断点续传下载到文件等能力。 + */ + class NetRequest + { + public: + using LogCallback = std::function; ///< 日志回调类型 + + /** + * @brief 运行时统计数据 + */ + struct Stats + { + uint64_t total_requests = 0; ///< 累计请求次数 + uint64_t total_errors = 0; ///< 累计失败次数 + double last_latency_ms = 0.0;///< 最近一次请求耗时(毫秒) + double avg_latency_ms = 0.0; ///< 指数平滑后的平均耗时(毫秒) + }; + + /** + * @brief 构造函数 + * @param options 请求参数配置(目标地址、超时、默认头部等) + */ + explicit NetRequest(const RequestOptions &options); + /** + * @brief 析构函数 + */ + ~NetRequest(); + + /** + * @brief 设置日志回调 + * @param logger 回调函数,接受一行日志字符串 + */ + void setLogger(LogCallback logger); + /** + * @brief 设置最大并发请求数 + * @param n 并发上限(最小值为 1) + */ + void setMaxConcurrentRequests(size_t n); + /** + * @brief 启用内存缓存 + * @param ttl 缓存有效期(超时后自动失效) + */ + void enableCache(std::chrono::milliseconds ttl); + /** + * @brief 禁用并清空缓存 + */ + void disableCache(); + + /** + * @brief 发送同步 GET 请求 + * @param path 资源路径(会与 base_path 合并) + * @param query 查询参数(会拼接为 ?k=v&...) + * @param headers 额外请求头(与默认头部合并) + * @param err 可选错误码输出 + * @return 成功返回响应对象,失败返回 std::nullopt + */ + ntq::optional Get(const std::string &path, + const httplib::Params &query = {}, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 发送同步 POST JSON 请求 + * @param path 资源路径 + * @param json JSON 字符串(Content-Type: application/json) + * @param headers 额外头部 + * @param err 可选错误码输出 + * @return 成功返回响应对象,失败返回 std::nullopt + */ + ntq::optional PostJson(const std::string &path, + const std::string &json, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 发送同步 POST 表单请求(application/x-www-form-urlencoded) + * @param path 资源路径 + * @param form 表单参数 + * @param headers 额外头部 + * @param err 可选错误码输出 + * @return 成功返回响应对象,失败返回 std::nullopt + */ + ntq::optional PostForm(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 异步 GET 请求 + * @return std::future,用于获取响应结果 + */ + std::future> GetAsync(const std::string &path, + const httplib::Params &query = {}, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 异步 POST JSON 请求 + * @return std::future,用于获取响应结果 + */ + std::future> PostJsonAsync(const std::string &path, + const std::string &json, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 异步 POST 表单请求 + * @return std::future,用于获取响应结果 + */ + std::future> PostFormAsync(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 下载文件到本地,支持断点续传 + * @param path 资源路径 + * @param local_file 本地保存路径 + * @param headers 额外头部 + * @param resume 是否启用续传(Range) + * @param chunk_size 预留参数:分块大小(当前由 httplib 内部回调驱动) + * @param err 可选错误码输出 + * @return true 下载成功(200 或 206),false 失败 + */ + bool DownloadToFile(const std::string &path, + const std::string &local_file, + const httplib::Headers &headers = {}, + bool resume = true, + size_t chunk_size = 1 << 15, + ErrorCode *err = nullptr); + + /** + * @brief 获取统计数据快照 + */ + Stats getStats() const; + + /** + * @brief 便捷:直接用完整 URL 发起 GET(无需显式实例化) + * @param url 形如 http://host:port/path?x=1 或 https://host/path + * @param headers 额外头部 + * @param err 可选错误码输出 + */ + static ntq::optional QuickGet(const std::string &url, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 便捷:直接用完整 URL 发起 POST JSON(无需显式实例化) + * @param url 形如 http://host:port/path?x=1 或 https://host/path + * @param json JSON 字符串(Content-Type: application/json) + * @param headers 额外头部 + * @param err 可选错误码输出 + */ + static ntq::optional QuickPostJson(const std::string &url, + const std::string &json, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + /** + * @brief 便捷:直接用完整 URL 发起 POST 表单(无需显式实例化) + * @param url 形如 http://host:port/path?x=1 或 https://host/path + * @param form 表单参数 + * @param headers 额外头部 + * @param err 可选错误码输出 + */ + static ntq::optional QuickPostForm(const std::string &url, + const httplib::Params &form, + const httplib::Headers &headers = {}, + ErrorCode *err = nullptr); + + private: + struct Impl; + Impl *impl_; + }; +} \ No newline at end of file diff --git a/ApCreate/NetraLib/include/Netra.hpp b/ApCreate/NetraLib/include/Netra.hpp new file mode 100644 index 0000000..85aba11 --- /dev/null +++ b/ApCreate/NetraLib/include/Netra.hpp @@ -0,0 +1,411 @@ +#pragma once +#include "QCL_Include.hpp" + +namespace QCL +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @class TcpServer + * @brief 简单的多线程TCP服务器类,支持多个客户端连接,数据收发及断开处理 + * + * 该类使用一个线程专门用于监听客户端连接, + * 每当有客户端连接成功时,为其创建一个独立线程处理该客户端的数据收发。 + * 线程安全地管理所有客户端Socket句柄。 + */ + class TcpServer + { + public: + /** + * @brief 构造函数,指定监听端口 + * @param port 服务器监听端口号 + */ + TcpServer(int port); + + /** + * @brief 析构函数,自动调用 stop() 停止服务器并清理资源 + */ + ~TcpServer(); + + /** + * @brief 启动服务器,创建监听socket,开启监听线程 + * @return 启动成功返回true,失败返回false + */ + bool start(); + + /** + * @brief 停止服务器,关闭所有连接,释放资源,等待所有线程退出 + */ + void stop(); + + /** + * @brief 发送消息给指定客户端 + * @param clientSock 客户端Socket描述符 + * @param message 发送的字符串消息 + */ + void sendToClient(int clientSock, const std::string &message); + + /** + * @brief 从指定客户端接收数据(单次调用) + * @param clientSock 客户端Socket描述符 + * @param flag:false 非阻塞模式,true 阻塞模式 + */ + std::string receiveFromClient(int clientSock, bool flag = true); + + /** + * @brief 获取连接客户端的IP和端口 + * @param clientSock 客户端Socket描述符 + */ + char *getClientIPAndPort(int clientSock); + + /** + * @brief 获取当前所有已连接客户端Socket的副本 + * @return 包含所有客户端Socket的vector,线程安全 + */ + std::vector getClientSockets(); + + private: + /** + * @brief 监听并接受新的客户端连接(运行在独立线程中) + */ + void acceptClients(); + + private: + int serverSock_; ///< 服务器监听Socket描述符 + int port_; ///< 服务器监听端口 + std::atomic running_; ///< 服务器运行状态标志(线程安全) + std::vector clientThreads_; ///< 用于处理每个客户端的线程集合 + std::thread acceptThread_; ///< 负责监听新连接的线程 + std::mutex clientsMutex_; ///< 保护clientSockets_的互斥锁 + std::vector clientSockets_; ///< 当前所有连接的客户端Socket集合 + }; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @brief 文件写入工具类(线程安全) + * + * 该类支持多种文件写入方式: + * - 覆盖写文本 + * - 追加写文本 + * - 按位置覆盖原文写入 + * - 二进制覆盖写 + * - 二进制追加写 + * + * 特点: + * - 文件不存在时自动创建 + * - 支持覆盖和追加两种模式 + * - 支持二进制模式,适合写入非文本数据 + * - 内部使用 std::mutex 实现线程安全 + */ + class WriteFile + { + public: + /** + * @brief 构造函数 + * @param filePath 文件路径 + */ + explicit WriteFile(const std::string &filePath); + + /** + * @brief 覆盖写文本文件(线程安全) + * @param content 要写入的文本内容 + * @return true 写入成功 + * @return false 写入失败 + */ + bool overwriteText(const std::string &content); + + /** + * @brief 追加写文本文件(线程安全) + * @param content 要写入的文本内容 + * @return true 写入成功 + * @return false 写入失败 + */ + bool appendText(const std::string &content); + + /** + * @brief 覆盖写二进制文件(线程安全) + * @param data 要写入的二进制数据 + * @return true 写入成功 + * @return false 写入失败 + */ + bool overwriteBinary(const std::vector &data); + + /** + * @brief 追加写二进制文件(线程安全) + * @param data 要写入的二进制数据 + * @return true 写入成功 + * @return false 写入失败 + */ + bool appendBinary(const std::vector &data); + + /** + * @brief 计算第一个指定字节序列前的字节数 + * @param pattern 要查找的字节序列 + * @param includePattern true 表示返回值包含 pattern 自身长度,false 表示不包含 + * @return size_t 字节数,如果文件没打开或 pattern 为空则返回 0 + */ + size_t countBytesPattern(const std::string &pattern, bool includePattern = false); + + /** + * @brief 在文件中查找指定字节序列并在其后写入内容,如果不存在则追加到文件末尾 + * @param pattern 要查找的字节序列 + * @param content 要写入的内容 + * @return true 写入成功,false 文件打开失败 + * + * 功能说明: + * 1. 若文件中存在 pattern,则删除 pattern 之后的所有内容,并在其后插入 content。 + * 2. 若文件中不存在 pattern,则在文件末尾追加 content,若末尾无换行符则先补充换行。 + */ + bool writeAfterPatternOrAppend(const std::string &pattern, const std::string &content); + + /** + * @brief 在文件指定位置之后插入内容 + * @param content 要插入的内容 + * @param pos 插入位置(从文件开头算起的字节偏移量) + * @param length 插入的长度(>= content.size() 时,多余部分用空字节填充;< content.size() 时只截取前 length 个字节) + * @return true 插入成功,false 文件打开失败或参数不合法 + * + * 功能说明: + * 1. 不会覆盖原有数据,而是将 pos 之后的内容整体向后移动 length 个字节。 + * 2. 如果 length > content.size(),则在 content 后补充 '\0'(或空格,可按需求改)。 + * 3. 如果 length < content.size(),则只写入 content 的前 length 个字节。 + * 4. 文件整体大小会增加 length 个字节。 + * + * 举例: + * 原始文件内容: "ABCDEFG" + * insertAfterPos("XY", 2, 3) // 在索引 2 后插入 + * 结果: "ABX Y\0CDEFG" (这里 \0 代表补充的空字节) + */ + bool insertAfterPos(const std::string &content, size_t pos, size_t length); + + /** + * @brief 在文件指定位置覆盖写入内容 + * @param content 要写入的内容 + * @param pos 覆盖起始位置(从文件开头算起的字节偏移量) + * @param length 覆盖长度 + * @return true 覆盖成功,false 文件打开失败或 pos 越界 + * + * 功能说明: + * 1. 从 pos 开始覆盖 length 个字节,不会移动或增加文件大小。 + * 2. 如果 content.size() >= length,则只写入前 length 个字节。 + * 3. 如果 content.size() < length,则写入 content,并用 '\0' 补齐至 length。 + * 4. 如果 pos + length 超过文件末尾,则只覆盖到文件尾部,不会越界。 + * + * 举例: + * 原始文件内容: "ABCDEFG" + * overwriteAtPos("XY", 2, 3) + * 结果: "ABXYEFG" (原 "CDE" 被 "XY\0" 覆盖,\0 实际不可见) + */ + bool overwriteAtPos(const std::string &content, size_t pos, size_t length); + + private: + std::string filePath_; ///< 文件路径 + std::mutex writeMutex_; ///< 线程锁,保证多线程写入安全 + + /** + * @brief 通用文本写入接口(线程安全) + * @param content 要写入的内容 + * @param mode 打开模式(追加/覆盖等) + * @return true 写入成功 + * @return false 写入失败 + */ + bool writeToFile(const std::string &content, std::ios::openmode mode); + + /** + * @brief 通用二进制写入接口(线程安全) + * @param data 要写入的二进制数据 + * @param mode 打开模式(追加/覆盖等) + * @return true 写入成功 + * @return false 写入失败 + */ + bool writeBinary(const std::vector &data, std::ios::openmode mode); + }; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @brief ReadFile 类 - 读文件操作工具类 + * + * 功能: + * 1. 读取全文(文本 / 二进制) + * 2. 按行读取 + * 3. 按字节数读取 + * 4. 获取指定字节序列前的字节数(包含该字节序列) + * 5. 检查文件是否存在 + * 6. 获取文件大小 + * + * 设计: + * - 与 WriteFile 类风格保持一致 + * - 支持文本文件与二进制文件 + * - 自动关闭文件(析构时) + * - 内部使用 std::mutex 实现线程安全 + */ + class ReadFile + { + public: + /** + * @brief 构造函数 + * @param filename 文件路径 + */ + explicit ReadFile(const std::string &filename); + + /** + * @brief 析构函数,自动关闭文件 + */ + ~ReadFile(); + + /** + * @brief 打开文件(以二进制方式) + * @return true 打开成功 + * @return false 打开失败 + */ + bool Open(); + + /** + * @brief 关闭文件 + */ + void Close(); + + /** + * @brief 文件是否已经打开 + */ + bool IsOpen() const; + + /** + * @brief 读取全文(文本模式) + * @return 文件内容字符串 + */ + std::string ReadAllText(); + + /** + * @brief 读取全文(二进制模式) + * @return 文件内容字节数组 + */ + std::vector ReadAllBinary(); + + /** + * @brief 按行读取文本 + * @return 每行作为一个字符串的 vector + */ + std::vector ReadLines(); + + /** + * @brief 读取指定字节数 + * @param count 要读取的字节数 + * @return 实际读取到的字节数据 + */ + std::vector ReadBytes(size_t count); + + /** + * @brief 获取指定字节序列前的字节数(包含该字节序列) + * @param marker 要查找的字节序列(可能不止一个字节) + * @return 如果找到,返回前面部分字节数;找不到返回0 + */ + size_t GetBytesBefore(const std::string &marker, bool includeMarker = false); + + /** + * @brief 从指定位置读取指定字节数,默认读取到文件末尾 + * @param pos 起始位置(字节偏移) + * @param count 要读取的字节数,默认为0表示读取到文件末尾 + * @return 读取到的字节数据 + */ + std::vector ReadBytesFrom(size_t pos, size_t count = 0); + + /** + * @brief 检查文件是否存在 + */ + bool FileExists() const; + + /** + * @brief 获取文件大小(字节数) + */ + size_t GetFileSize() const; + + /** + * @brief 重置读取位置到文件开头 + */ + void Reset(); + + private: + std::string filename_; // 文件路径 + std::ifstream file_; // 文件流对象 + mutable std::mutex mtx_; // 可变,保证 const 方法也能加锁 + }; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // 屏蔽所有信号 + void blockAllSignals(); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // 字符串操作 + // 去除字符串的左空格 + std::string Ltrim(const std::string &s); + + // 去除字符串右侧的空格 + std::string Rtrim(const std::string &s); + + // 去除字符串左右两侧的空格 + std::string LRtrim(const std::string &s); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // c++进行格式化输出 + // 通用类型转字符串 + template + std::string to_string_any(const T &value) + { + std::ostringstream oss; + oss << value; + return oss.str(); + } + + // 递归获取 tuple 中 index 对应参数 + template + std::string get_tuple_arg(const Tuple &tup, std::size_t index) + { + if constexpr (I < std::tuple_size_v) + { + if (I == index) + return to_string_any(std::get(tup)); + else + return get_tuple_arg(tup, index); + } + else + { + throw std::runtime_error("Too few arguments for format string"); + } + } + + // format 函数 + template + std::string format(const std::string &fmt, const Args &...args) + { + std::ostringstream oss; + std::tuple tup(args...); + size_t pos = 0; + size_t arg_idx = 0; + + while (pos < fmt.size()) + { + if (fmt[pos] == '{' && pos + 1 < fmt.size() && fmt[pos + 1] == '{') + { + oss << '{'; + pos += 2; + } + else if (fmt[pos] == '}' && pos + 1 < fmt.size() && fmt[pos + 1] == '}') + { + oss << '}'; + pos += 2; + } + else if (fmt[pos] == '{' && pos + 1 < fmt.size() && fmt[pos + 1] == '}') + { + oss << get_tuple_arg(tup, arg_idx++); + pos += 2; + } + else + { + oss << fmt[pos++]; + } + } + + if (arg_idx < sizeof...(Args)) + throw std::runtime_error("Too many arguments for format string"); + + return oss.str(); + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +} diff --git a/ApCreate/NetraLib/include/QCL_Include.hpp b/ApCreate/NetraLib/include/QCL_Include.hpp new file mode 100644 index 0000000..52a20fb --- /dev/null +++ b/ApCreate/NetraLib/include/QCL_Include.hpp @@ -0,0 +1,55 @@ +/** + * @file qcl_include.hpp + * @brief 通用C++开发头文件集合(Linux环境) + * @note 使用前确保目标平台支持C++17标准 + */ +#ifndef QCL_INCLUDE_HPP +#define QCL_INCLUDE_HPP + +// ==================== C/C++基础运行时库 ==================== +#include // 标准输入输出流(cin/cout/cerr) +#include // std::string类及相关操作 +#include // C风格字符串操作(strcpy/strcmp等) +#include // 通用工具函数(atoi/rand/malloc等) +#include // C风格IO(printf/scanf) +#include // 断言宏(调试期检查) +#include // 数学函数(sin/pow等) +#include // 时间处理(time/clock) +#include // 信号处理(signal/kill) +#include // 智能指针 + +// ==================== STL容器与算法 ==================== +#include // 动态数组(连续内存容器) +#include // 双向链表 +#include // 双端队列 +#include // 有序键值对(红黑树实现) +#include // 有序集合 +#include // 哈希表实现的键值对 +#include // 哈希表实现的集合 +#include // 栈适配器(LIFO) +#include // 队列适配器(FIFO) +#include // 通用算法(sort/find等) +#include // 数值算法(accumulate等) +#include // 迭代器相关 + +// ==================== 字符串与流处理 ==================== +#include // 字符串流(内存IO) +#include // 文件流(文件IO) +#include // 流格式控制(setw/setprecision) +#include // 正则表达式 +#include // 文件系统(C++17) +#include + +// ==================== 并发编程支持 ==================== +#include // 线程管理(std::thread) +#include // 互斥锁(mutex/lock_guard) +#include // 原子操作(线程安全变量) +#include // 条件变量(线程同步) + +// ==================== Linux网络编程 ==================== +#include // 套接字基础API(socket/bind) +#include // IPV4/IPV6地址结构体 +#include // 地址转换函数(inet_pton等) +#include // POSIX API(close/read/write) + +#endif // QCL_INCLUDE_HPP diff --git a/ApCreate/NetraLib/include/README.md b/ApCreate/NetraLib/include/README.md new file mode 100644 index 0000000..984ab89 --- /dev/null +++ b/ApCreate/NetraLib/include/README.md @@ -0,0 +1,850 @@ +cpp-httplib +=========== + +[![](https://github.com/yhirose/cpp-httplib/workflows/test/badge.svg)](https://github.com/yhirose/cpp-httplib/actions) + +A C++11 single-file header-only cross platform HTTP/HTTPS library. + +It's extremely easy to setup. Just include the **httplib.h** file in your code! + +NOTE: This library uses 'blocking' socket I/O. If you are looking for a library with 'non-blocking' socket I/O, this is not the one that you want. + +Simple examples +--------------- + +#### Server (Multi-threaded) + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// HTTP +httplib::Server svr; + +// HTTPS +httplib::SSLServer svr; + +svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) { + res.set_content("Hello World!", "text/plain"); +}); + +svr.listen("0.0.0.0", 8080); +``` + +#### Client + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// HTTP +httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co"); + +// HTTPS +httplib::Client cli("https://cpp-httplib-server.yhirose.repl.co"); + +auto res = cli.Get("/hi"); +res->status; +res->body; +``` + +SSL Support +----------- + +SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked. + +NOTE: cpp-httplib currently supports only version 1.1.1 and 3.0. + +NOTE for macOS: cpp-httplib now can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`. + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +// Server +httplib::SSLServer svr("./cert.pem", "./key.pem"); + +// Client +httplib::Client cli("https://localhost:1234"); // scheme + host +httplib::SSLClient cli("localhost:1234"); // host +httplib::SSLClient cli("localhost", 1234); // host, port + +// Use your CA bundle +cli.set_ca_cert_path("./ca-bundle.crt"); + +// Disable cert verification +cli.enable_server_certificate_verification(false); +``` + +NOTE: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself. + +Server +------ + +```c++ +#include + +int main(void) +{ + using namespace httplib; + + Server svr; + + svr.Get("/hi", [](const Request& req, Response& res) { + res.set_content("Hello World!", "text/plain"); + }); + + svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) { + auto numbers = req.matches[1]; + res.set_content(numbers, "text/plain"); + }); + + svr.Get("/body-header-param", [](const Request& req, Response& res) { + if (req.has_header("Content-Length")) { + auto val = req.get_header_value("Content-Length"); + } + if (req.has_param("key")) { + auto val = req.get_param_value("key"); + } + res.set_content(req.body, "text/plain"); + }); + + svr.Get("/stop", [&](const Request& req, Response& res) { + svr.stop(); + }); + + svr.listen("localhost", 1234); +} +``` + +`Post`, `Put`, `Delete` and `Options` methods are also supported. + +### Bind a socket to multiple interfaces and any available port + +```cpp +int port = svr.bind_to_any_port("0.0.0.0"); +svr.listen_after_bind(); +``` + +### Static File Server + +```cpp +// Mount / to ./www directory +auto ret = svr.set_mount_point("/", "./www"); +if (!ret) { + // The specified base directory doesn't exist... +} + +// Mount /public to ./www directory +ret = svr.set_mount_point("/public", "./www"); + +// Mount /public to ./www1 and ./www2 directories +ret = svr.set_mount_point("/public", "./www1"); // 1st order to search +ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search + +// Remove mount / +ret = svr.remove_mount_point("/"); + +// Remove mount /public +ret = svr.remove_mount_point("/public"); +``` + +```cpp +// User defined file extension and MIME type mappings +svr.set_file_extension_and_mimetype_mapping("cc", "text/x-c"); +svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c"); +svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h"); +``` + +The followings are built-in mappings: + +| Extension | MIME Type | Extension | MIME Type | +| :--------- | :-------------------------- | :--------- | :-------------------------- | +| css | text/css | mpga | audio/mpeg | +| csv | text/csv | weba | audio/webm | +| txt | text/plain | wav | audio/wave | +| vtt | text/vtt | otf | font/otf | +| html, htm | text/html | ttf | font/ttf | +| apng | image/apng | woff | font/woff | +| avif | image/avif | woff2 | font/woff2 | +| bmp | image/bmp | 7z | application/x-7z-compressed | +| gif | image/gif | atom | application/atom+xml | +| png | image/png | pdf | application/pdf | +| svg | image/svg+xml | mjs, js | application/javascript | +| webp | image/webp | json | application/json | +| ico | image/x-icon | rss | application/rss+xml | +| tif | image/tiff | tar | application/x-tar | +| tiff | image/tiff | xhtml, xht | application/xhtml+xml | +| jpeg, jpg | image/jpeg | xslt | application/xslt+xml | +| mp4 | video/mp4 | xml | application/xml | +| mpeg | video/mpeg | gz | application/gzip | +| webm | video/webm | zip | application/zip | +| mp3 | audio/mp3 | wasm | application/wasm | + +### File request handler + +```cpp +// The handler is called right before the response is sent to a client +svr.set_file_request_handler([](const Request &req, Response &res) { + ... +}); +``` + +NOTE: These static file server methods are not thread-safe. + +### Logging + +```cpp +svr.set_logger([](const auto& req, const auto& res) { + your_logger(req, res); +}); +``` + +### Error handler + +```cpp +svr.set_error_handler([](const auto& req, auto& res) { + auto fmt = "

Error Status: %d

"; + char buf[BUFSIZ]; + snprintf(buf, sizeof(buf), fmt, res.status); + res.set_content(buf, "text/html"); +}); +``` + +### Exception handler +The exception handler gets called if a user routing handler throws an error. + +```cpp +svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep) { + auto fmt = "

Error 500

%s

"; + char buf[BUFSIZ]; + try { + std::rethrow_exception(ep); + } catch (std::exception &e) { + snprintf(buf, sizeof(buf), fmt, e.what()); + } catch (...) { // See the following NOTE + snprintf(buf, sizeof(buf), fmt, "Unknown Exception"); + } + res.set_content(buf, "text/html"); + res.status = 500; +}); +``` + +NOTE: if you don't provide the `catch (...)` block for a rethrown exception pointer, an uncaught exception will end up causing the server crash. Be careful! + +### Pre routing handler + +```cpp +svr.set_pre_routing_handler([](const auto& req, auto& res) { + if (req.path == "/hello") { + res.set_content("world", "text/html"); + return Server::HandlerResponse::Handled; + } + return Server::HandlerResponse::Unhandled; +}); +``` + +### Post routing handler + +```cpp +svr.set_post_routing_handler([](const auto& req, auto& res) { + res.set_header("ADDITIONAL_HEADER", "value"); +}); +``` + +### 'multipart/form-data' POST data + +```cpp +svr.Post("/multipart", [&](const auto& req, auto& res) { + auto size = req.files.size(); + auto ret = req.has_file("name1"); + const auto& file = req.get_file_value("name1"); + // file.filename; + // file.content_type; + // file.content; +}); +``` + +### Receive content with a content receiver + +```cpp +svr.Post("/content_receiver", + [&](const Request &req, Response &res, const ContentReader &content_reader) { + if (req.is_multipart_form_data()) { + // NOTE: `content_reader` is blocking until every form data field is read + MultipartFormDataItems files; + content_reader( + [&](const MultipartFormData &file) { + files.push_back(file); + return true; + }, + [&](const char *data, size_t data_length) { + files.back().content.append(data, data_length); + return true; + }); + } else { + std::string body; + content_reader([&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; + }); + } + }); +``` + +### Send content with the content provider + +```cpp +const size_t DATA_CHUNK_SIZE = 4; + +svr.Get("/stream", [&](const Request &req, Response &res) { + auto data = new std::string("abcdefg"); + + res.set_content_provider( + data->size(), // Content length + "text/plain", // Content type + [data](size_t offset, size_t length, DataSink &sink) { + const auto &d = *data; + sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE)); + return true; // return 'false' if you want to cancel the process. + }, + [data](bool success) { delete data; }); +}); +``` + +Without content length: + +```cpp +svr.Get("/stream", [&](const Request &req, Response &res) { + res.set_content_provider( + "text/plain", // Content type + [&](size_t offset, DataSink &sink) { + if (/* there is still data */) { + std::vector data; + // prepare data... + sink.write(data.data(), data.size()); + } else { + sink.done(); // No more data + } + return true; // return 'false' if you want to cancel the process. + }); +}); +``` + +### Chunked transfer encoding + +```cpp +svr.Get("/chunked", [&](const Request& req, Response& res) { + res.set_chunked_content_provider( + "text/plain", + [](size_t offset, DataSink &sink) { + sink.write("123", 3); + sink.write("345", 3); + sink.write("789", 3); + sink.done(); // No more data + return true; // return 'false' if you want to cancel the process. + } + ); +}); +``` + +With trailer: + +```cpp +svr.Get("/chunked", [&](const Request& req, Response& res) { + res.set_header("Trailer", "Dummy1, Dummy2"); + res.set_chunked_content_provider( + "text/plain", + [](size_t offset, DataSink &sink) { + sink.write("123", 3); + sink.write("345", 3); + sink.write("789", 3); + sink.done_with_trailer({ + {"Dummy1", "DummyVal1"}, + {"Dummy2", "DummyVal2"} + }); + return true; + } + ); +}); +``` + +### 'Expect: 100-continue' handler + +By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header. + +```cpp +// Send a '417 Expectation Failed' response. +svr.set_expect_100_continue_handler([](const Request &req, Response &res) { + return 417; +}); +``` + +```cpp +// Send a final status without reading the message body. +svr.set_expect_100_continue_handler([](const Request &req, Response &res) { + return res.status = 401; +}); +``` + +### Keep-Alive connection + +```cpp +svr.set_keep_alive_max_count(2); // Default is 5 +svr.set_keep_alive_timeout(10); // Default is 5 +``` + +### Timeout + +```c++ +svr.set_read_timeout(5, 0); // 5 seconds +svr.set_write_timeout(5, 0); // 5 seconds +svr.set_idle_interval(0, 100000); // 100 milliseconds +``` + +### Set maximum payload length for reading a request body + +```c++ +svr.set_payload_max_length(1024 * 1024 * 512); // 512MB +``` + +### Server-Sent Events + +Please see [Server example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssesvr.cc) and [Client example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssecli.cc). + +### Default thread pool support + +`ThreadPool` is used as a **default** task queue, and the default thread count is 8, or `std::thread::hardware_concurrency()`. You can change it with `CPPHTTPLIB_THREAD_POOL_COUNT`. + +If you want to set the thread count at runtime, there is no convenient way... But here is how. + +```cpp +svr.new_task_queue = [] { return new ThreadPool(12); }; +``` + +### Override the default thread pool with yours + +You can supply your own thread pool implementation according to your need. + +```cpp +class YourThreadPoolTaskQueue : public TaskQueue { +public: + YourThreadPoolTaskQueue(size_t n) { + pool_.start_with_thread_count(n); + } + + virtual void enqueue(std::function fn) override { + pool_.enqueue(fn); + } + + virtual void shutdown() override { + pool_.shutdown_gracefully(); + } + +private: + YourThreadPool pool_; +}; + +svr.new_task_queue = [] { + return new YourThreadPoolTaskQueue(12); +}; +``` + +Client +------ + +```c++ +#include +#include + +int main(void) +{ + httplib::Client cli("localhost", 1234); + + if (auto res = cli.Get("/hi")) { + if (res->status == 200) { + std::cout << res->body << std::endl; + } + } else { + auto err = res.error(); + std::cout << "HTTP error: " << httplib::to_string(err) << std::endl; + } +} +``` + +NOTE: Constructor with scheme-host-port string is now supported! + +```c++ +httplib::Client cli("localhost"); +httplib::Client cli("localhost:8080"); +httplib::Client cli("http://localhost"); +httplib::Client cli("http://localhost:8080"); +httplib::Client cli("https://localhost"); +httplib::SSLClient cli("localhost"); +``` + +### Error code + +Here is the list of errors from `Result::error()`. + +```c++ +enum Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, +}; +``` + +### GET with HTTP headers + +```c++ +httplib::Headers headers = { + { "Accept-Encoding", "gzip, deflate" } +}; +auto res = cli.Get("/hi", headers); +``` +or +```c++ +auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}}); +``` +or +```c++ +cli.set_default_headers({ + { "Accept-Encoding", "gzip, deflate" } +}); +auto res = cli.Get("/hi"); +``` + +### POST + +```c++ +res = cli.Post("/post", "text", "text/plain"); +res = cli.Post("/person", "name=john1¬e=coder", "application/x-www-form-urlencoded"); +``` + +### POST with parameters + +```c++ +httplib::Params params; +params.emplace("name", "john"); +params.emplace("note", "coder"); + +auto res = cli.Post("/post", params); +``` + or + +```c++ +httplib::Params params{ + { "name", "john" }, + { "note", "coder" } +}; + +auto res = cli.Post("/post", params); +``` + +### POST with Multipart Form Data + +```c++ +httplib::MultipartFormDataItems items = { + { "text1", "text default", "", "" }, + { "text2", "aωb", "", "" }, + { "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" }, + { "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" }, + { "file3", "", "", "application/octet-stream" }, +}; + +auto res = cli.Post("/multipart", items); +``` + +### PUT + +```c++ +res = cli.Put("/resource/foo", "text", "text/plain"); +``` + +### DELETE + +```c++ +res = cli.Delete("/resource/foo"); +``` + +### OPTIONS + +```c++ +res = cli.Options("*"); +res = cli.Options("/resource/foo"); +``` + +### Timeout + +```c++ +cli.set_connection_timeout(0, 300000); // 300 milliseconds +cli.set_read_timeout(5, 0); // 5 seconds +cli.set_write_timeout(5, 0); // 5 seconds +``` + +### Receive content with a content receiver + +```c++ +std::string body; + +auto res = cli.Get("/large-data", + [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; + }); +``` + +```cpp +std::string body; + +auto res = cli.Get( + "/stream", Headers(), + [&](const Response &response) { + EXPECT_EQ(200, response.status); + return true; // return 'false' if you want to cancel the request. + }, + [&](const char *data, size_t data_length) { + body.append(data, data_length); + return true; // return 'false' if you want to cancel the request. + }); +``` + +### Send content with a content provider + +```cpp +std::string body = ...; + +auto res = cli.Post( + "/stream", body.size(), + [](size_t offset, size_t length, DataSink &sink) { + sink.write(body.data() + offset, length); + return true; // return 'false' if you want to cancel the request. + }, + "text/plain"); +``` + +### Chunked transfer encoding + +```cpp +auto res = cli.Post( + "/stream", + [](size_t offset, DataSink &sink) { + sink.os << "chunked data 1"; + sink.os << "chunked data 2"; + sink.os << "chunked data 3"; + sink.done(); + return true; // return 'false' if you want to cancel the request. + }, + "text/plain"); +``` + +### With Progress Callback + +```cpp +httplib::Client client(url, port); + +// prints: 0 / 000 bytes => 50% complete +auto res = cli.Get("/", [](uint64_t len, uint64_t total) { + printf("%lld / %lld bytes => %d%% complete\n", + len, total, + (int)(len*100/total)); + return true; // return 'false' if you want to cancel the request. +} +); +``` + +![progress](https://user-images.githubusercontent.com/236374/33138910-495c4ecc-cf86-11e7-8693-2fc6d09615c4.gif) + +### Authentication + +```cpp +// Basic Authentication +cli.set_basic_auth("user", "pass"); + +// Digest Authentication +cli.set_digest_auth("user", "pass"); + +// Bearer Token Authentication +cli.set_bearer_token_auth("token"); +``` + +NOTE: OpenSSL is required for Digest Authentication. + +### Proxy server support + +```cpp +cli.set_proxy("host", port); + +// Basic Authentication +cli.set_proxy_basic_auth("user", "pass"); + +// Digest Authentication +cli.set_proxy_digest_auth("user", "pass"); + +// Bearer Token Authentication +cli.set_proxy_bearer_token_auth("pass"); +``` + +NOTE: OpenSSL is required for Digest Authentication. + +### Range + +```cpp +httplib::Client cli("httpbin.org"); + +auto res = cli.Get("/range/32", { + httplib::make_range_header({{1, 10}}) // 'Range: bytes=1-10' +}); +// res->status should be 206. +// res->body should be "bcdefghijk". +``` + +```cpp +httplib::make_range_header({{1, 10}, {20, -1}}) // 'Range: bytes=1-10, 20-' +httplib::make_range_header({{100, 199}, {500, 599}}) // 'Range: bytes=100-199, 500-599' +httplib::make_range_header({{0, 0}, {-1, 1}}) // 'Range: bytes=0-0, -1' +``` + +### Keep-Alive connection + +```cpp +httplib::Client cli("localhost", 1234); + +cli.Get("/hello"); // with "Connection: close" + +cli.set_keep_alive(true); +cli.Get("/world"); + +cli.set_keep_alive(false); +cli.Get("/last-request"); // with "Connection: close" +``` + +### Redirect + +```cpp +httplib::Client cli("yahoo.com"); + +auto res = cli.Get("/"); +res->status; // 301 + +cli.set_follow_location(true); +res = cli.Get("/"); +res->status; // 200 +``` + +### Use a specific network interface + +NOTE: This feature is not available on Windows, yet. + +```cpp +cli.set_interface("eth0"); // Interface name, IP address or host name +``` + +Compression +----------- + +The server can apply compression to the following MIME type contents: + + * all text types except text/event-stream + * image/svg+xml + * application/javascript + * application/json + * application/xml + * application/xhtml+xml + +### Zlib Support + +'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. `libz` should be linked. + +### Brotli Support + +Brotli compression is available with `CPPHTTPLIB_BROTLI_SUPPORT`. Necessary libraries should be linked. +Please see https://github.com/google/brotli for more detail. + +### Compress request body on client + +```c++ +cli.set_compress(true); +res = cli.Post("/resource/foo", "...", "text/plain"); +``` + +### Compress response body on client + +```c++ +cli.set_decompress(false); +res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}}); +res->body; // Compressed data +``` + +Use `poll` instead of `select` +------------------------------ + +`select` system call is used as default since it's more widely supported. If you want to let cpp-httplib use `poll` instead, you can do so with `CPPHTTPLIB_USE_POLL`. + + +Split httplib.h into .h and .cc +------------------------------- + +```console +$ ./split.py -h +usage: split.py [-h] [-e EXTENSION] [-o OUT] + +This script splits httplib.h into .h and .cc parts. + +optional arguments: + -h, --help show this help message and exit + -e EXTENSION, --extension EXTENSION + extension of the implementation file (default: cc) + -o OUT, --out OUT where to write the files (default: out) + +$ ./split.py +Wrote out/httplib.h and out/httplib.cc +``` + +NOTE +---- + +### g++ + +g++ 4.8 and below cannot build this library since `` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions). + +### Windows + +Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32_LEAN_AND_MEAN` beforehand. + +```cpp +#include +#include +``` + +```cpp +#define WIN32_LEAN_AND_MEAN +#include +#include +``` + +NOTE: cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance. + +NOTE: Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin on Windows are not supported. + +License +------- + +MIT license (© 2023 Yuji Hirose) + +Special Thanks To +----------------- + +[These folks](https://github.com/yhirose/cpp-httplib/graphs/contributors) made great contributions to polish this library to totally another level from a simple toy! diff --git a/ApCreate/NetraLib/include/encrypt.hpp b/ApCreate/NetraLib/include/encrypt.hpp new file mode 100644 index 0000000..42ea793 --- /dev/null +++ b/ApCreate/NetraLib/include/encrypt.hpp @@ -0,0 +1,14 @@ +#pragma once + +/* +主要是用于各种加密 +*/ + +#include "QCL_Include.hpp" + +using namespace std; + +namespace encrypt +{ + string MD5(const string &info); +} \ No newline at end of file diff --git a/ApCreate/NetraLib/include/httplib.h b/ApCreate/NetraLib/include/httplib.h new file mode 100644 index 0000000..1bdef69 --- /dev/null +++ b/ApCreate/NetraLib/include/httplib.h @@ -0,0 +1,8794 @@ +// +// httplib.h +// +// Copyright (c) 2023 Yuji Hirose. All rights reserved. +// MIT License +// + +#ifndef CPPHTTPLIB_HTTPLIB_H +#define CPPHTTPLIB_HTTPLIB_H + +#define CPPHTTPLIB_VERSION "0.12.2" + +/* + * Configuration + */ + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT +#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND +#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND +#ifdef _WIN32 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 +#else +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 +#endif +#endif + +#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH +#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH +#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT +#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 +#endif + +#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT +#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) +#endif + +#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_TCP_NODELAY +#define CPPHTTPLIB_TCP_NODELAY false +#endif + +#ifndef CPPHTTPLIB_RECV_BUFSIZ +#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u) +#endif + +#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ +#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_THREAD_POOL_COUNT +#define CPPHTTPLIB_THREAD_POOL_COUNT \ + ((std::max)(8u, std::thread::hardware_concurrency() > 0 \ + ? std::thread::hardware_concurrency() - 1 \ + : 0)) +#endif + +#ifndef CPPHTTPLIB_RECV_FLAGS +#define CPPHTTPLIB_RECV_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_SEND_FLAGS +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_LISTEN_BACKLOG +#define CPPHTTPLIB_LISTEN_BACKLOG 5 +#endif + +/* + * Headers + */ + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif //_CRT_SECURE_NO_WARNINGS + +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif //_CRT_NONSTDC_NO_DEPRECATE + +#if defined(_MSC_VER) +#if _MSC_VER < 1900 +#error Sorry, Visual Studio versions prior to 2015 are not supported +#endif + +#pragma comment(lib, "ws2_32.lib") + +#ifdef _WIN64 +using ssize_t = __int64; +#else +using ssize_t = long; +#endif +#endif // _MSC_VER + +#ifndef S_ISREG +#define S_ISREG(m) (((m)&S_IFREG) == S_IFREG) +#endif // S_ISREG + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR) +#endif // S_ISDIR + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include +#include +#include + +#ifndef WSA_FLAG_NO_HANDLE_INHERIT +#define WSA_FLAG_NO_HANDLE_INHERIT 0x80 +#endif + +#ifndef strcasecmp +#define strcasecmp _stricmp +#endif // strcasecmp + +using socket_t = SOCKET; +#ifdef CPPHTTPLIB_USE_POLL +#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) +#endif + +#else // not _WIN32 + +#include +#ifndef _AIX +#include +#endif +#include +#include +#include +#ifdef __linux__ +#include +#endif +#include +#ifdef CPPHTTPLIB_USE_POLL +#include +#endif +#include +#include +#include +#include +#include +#include + +using socket_t = int; +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif +#endif //_WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +#include + +// these are defined in wincrypt.h and it breaks compilation if BoringSSL is +// used +#undef X509_NAME +#undef X509_CERT_PAIR +#undef X509_EXTENSIONS +#undef PKCS7_SIGNER_INFO + +#ifdef _MSC_VER +#pragma comment(lib, "crypt32.lib") +#pragma comment(lib, "cryptui.lib") +#endif +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#include +#if TARGET_OS_OSX +#include +#include +#endif // TARGET_OS_OSX +#endif // _WIN32 + +#include +#include +#include +#include + +#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) +#include +#endif + +#include +#include + +#if OPENSSL_VERSION_NUMBER < 0x1010100fL +#error Sorry, OpenSSL versions prior to 1.1.1 are not supported +#elif OPENSSL_VERSION_NUMBER < 0x30000000L +#define SSL_get1_peer_certificate SSL_get_peer_certificate +#endif + +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +#include +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +#include +#include +#endif + +/* + * Declaration + */ +namespace httplib { + +namespace detail { + +/* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(Args &&...args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(std::size_t n) { + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); +} + +struct ci { + bool operator()(const std::string &s1, const std::string &s2) const { + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), + s2.end(), + [](unsigned char c1, unsigned char c2) { + return ::tolower(c1) < ::tolower(c2); + }); + } +}; + +// This is based on +// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + +struct scope_exit { + explicit scope_exit(std::function &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} + + scope_exit(scope_exit &&rhs) + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } + + ~scope_exit() { + if (execute_on_destruction) { this->exit_function(); } + } + + void release() { this->execute_on_destruction = false; } + +private: + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; + + std::function exit_function; + bool execute_on_destruction; +}; + +} // namespace detail + +using Headers = std::multimap; + +using Params = std::multimap; +using Match = std::smatch; + +using Progress = std::function; + +struct Response; +using ResponseHandler = std::function; + +struct MultipartFormData { + std::string name; + std::string content; + std::string filename; + std::string content_type; +}; +using MultipartFormDataItems = std::vector; +using MultipartFormDataMap = std::multimap; + +class DataSink { +public: + DataSink() : os(&sb_), sb_(*this) {} + + DataSink(const DataSink &) = delete; + DataSink &operator=(const DataSink &) = delete; + DataSink(DataSink &&) = delete; + DataSink &operator=(DataSink &&) = delete; + + std::function write; + std::function done; + std::function done_with_trailer; + std::ostream os; + +private: + class data_sink_streambuf : public std::streambuf { + public: + explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {} + + protected: + std::streamsize xsputn(const char *s, std::streamsize n) { + sink_.write(s, static_cast(n)); + return n; + } + + private: + DataSink &sink_; + }; + + data_sink_streambuf sb_; +}; + +using ContentProvider = + std::function; + +using ContentProviderWithoutLength = + std::function; + +using ContentProviderResourceReleaser = std::function; + +struct MultipartFormDataProvider { + std::string name; + ContentProviderWithoutLength provider; + std::string filename; + std::string content_type; +}; +using MultipartFormDataProviderItems = std::vector; + +using ContentReceiverWithProgress = + std::function; + +using ContentReceiver = + std::function; + +using MultipartContentHeader = + std::function; + +class ContentReader { +public: + using Reader = std::function; + using MultipartReader = std::function; + + ContentReader(Reader reader, MultipartReader multipart_reader) + : reader_(std::move(reader)), + multipart_reader_(std::move(multipart_reader)) {} + + bool operator()(MultipartContentHeader header, + ContentReceiver receiver) const { + return multipart_reader_(std::move(header), std::move(receiver)); + } + + bool operator()(ContentReceiver receiver) const { + return reader_(std::move(receiver)); + } + + Reader reader_; + MultipartReader multipart_reader_; +}; + +using Range = std::pair; +using Ranges = std::vector; + +struct Request { + std::string method; + std::string path; + Headers headers; + 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; + Params params; + MultipartFormDataMap files; + Ranges ranges; + Match matches; + + // for client + ResponseHandler response_handler; + ContentReceiverWithProgress content_receiver; + Progress progress; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + const SSL *ssl = nullptr; +#endif + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; + template + T get_header_value(const std::string &key, 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_param(const std::string &key) const; + std::string get_param_value(const std::string &key, size_t id = 0) const; + size_t get_param_value_count(const std::string &key) const; + + bool is_multipart_form_data() const; + + bool has_file(const std::string &key) const; + MultipartFormData get_file_value(const std::string &key) const; + std::vector get_file_values(const std::string &key) const; + + // private members... + size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; + size_t content_length_ = 0; + ContentProvider content_provider_; + bool is_chunked_content_provider_ = false; + size_t authorization_count_ = 0; +}; + +struct Response { + std::string version; + int status = -1; + std::string reason; + Headers headers; + std::string body; + std::string location; // Redirect location + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; + template + T get_header_value(const std::string &key, 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); + + void set_redirect(const std::string &url, int status = 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_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); + + 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; +}; + +class Stream { +public: + virtual ~Stream() = default; + + virtual bool is_readable() const = 0; + virtual bool is_writable() const = 0; + + virtual ssize_t read(char *ptr, size_t size) = 0; + virtual ssize_t write(const char *ptr, size_t size) = 0; + virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; + virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0; + virtual socket_t socket() const = 0; + + template + ssize_t write_format(const char *fmt, const Args &...args); + ssize_t write(const char *ptr); + ssize_t write(const std::string &s); +}; + +class TaskQueue { +public: + TaskQueue() = default; + virtual ~TaskQueue() = default; + + virtual void enqueue(std::function fn) = 0; + virtual void shutdown() = 0; + + virtual void on_idle() {} +}; + +class ThreadPool : public TaskQueue { +public: + explicit ThreadPool(size_t n) : shutdown_(false) { + while (n) { + threads_.emplace_back(worker(*this)); + n--; + } + } + + ThreadPool(const ThreadPool &) = delete; + ~ThreadPool() override = default; + + void enqueue(std::function fn) override { + { + std::unique_lock lock(mutex_); + jobs_.push_back(std::move(fn)); + } + + cond_.notify_one(); + } + + void shutdown() override { + // Stop all worker threads... + { + std::unique_lock lock(mutex_); + shutdown_ = true; + } + + cond_.notify_all(); + + // Join... + for (auto &t : threads_) { + t.join(); + } + } + +private: + struct worker { + explicit worker(ThreadPool &pool) : pool_(pool) {} + + void operator()() { + for (;;) { + std::function fn; + { + std::unique_lock lock(pool_.mutex_); + + pool_.cond_.wait( + lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); + + if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } + + fn = std::move(pool_.jobs_.front()); + pool_.jobs_.pop_front(); + } + + assert(true == static_cast(fn)); + fn(); + } + } + + ThreadPool &pool_; + }; + friend struct worker; + + std::vector threads_; + std::list> jobs_; + + bool shutdown_; + + std::condition_variable cond_; + std::mutex mutex_; +}; + +using Logger = std::function; + +using SocketOptions = std::function; + +void default_socket_options(socket_t sock); + +class Server { +public: + using Handler = std::function; + + using ExceptionHandler = + std::function; + + enum class HandlerResponse { + Handled, + Unhandled, + }; + using HandlerWithResponse = + std::function; + + using HandlerWithContentReader = std::function; + + using Expect100ContinueHandler = + std::function; + + Server(); + + virtual ~Server(); + + virtual bool is_valid() const; + + Server &Get(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, HandlerWithContentReader handler); + Server &Put(const std::string &pattern, Handler handler); + Server &Put(const std::string &pattern, HandlerWithContentReader handler); + Server &Patch(const std::string &pattern, Handler handler); + Server &Patch(const std::string &pattern, HandlerWithContentReader handler); + Server &Delete(const std::string &pattern, Handler handler); + Server &Delete(const std::string &pattern, HandlerWithContentReader handler); + Server &Options(const std::string &pattern, Handler handler); + + bool set_base_dir(const std::string &dir, + const std::string &mount_point = std::string()); + bool set_mount_point(const std::string &mount_point, const std::string &dir, + Headers headers = Headers()); + bool remove_mount_point(const std::string &mount_point); + Server &set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime); + Server &set_file_request_handler(Handler handler); + + Server &set_error_handler(HandlerWithResponse handler); + Server &set_error_handler(Handler handler); + Server &set_exception_handler(ExceptionHandler handler); + Server &set_pre_routing_handler(HandlerWithResponse handler); + Server &set_post_routing_handler(Handler handler); + + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); + Server &set_logger(Logger logger); + + Server &set_address_family(int family); + Server &set_tcp_nodelay(bool on); + Server &set_socket_options(SocketOptions socket_options); + + Server &set_default_headers(Headers headers); + + Server &set_keep_alive_max_count(size_t count); + Server &set_keep_alive_timeout(time_t sec); + + Server &set_read_timeout(time_t sec, time_t usec = 0); + template + Server &set_read_timeout(const std::chrono::duration &duration); + + Server &set_write_timeout(time_t sec, time_t usec = 0); + template + Server &set_write_timeout(const std::chrono::duration &duration); + + Server &set_idle_interval(time_t sec, time_t usec = 0); + template + Server &set_idle_interval(const std::chrono::duration &duration); + + Server &set_payload_max_length(size_t length); + + bool bind_to_port(const std::string &host, int port, int socket_flags = 0); + int bind_to_any_port(const std::string &host, int socket_flags = 0); + bool listen_after_bind(); + + bool listen(const std::string &host, int port, int socket_flags = 0); + + bool is_running() const; + void wait_until_ready() const; + void stop(); + + std::function new_task_queue; + +protected: + bool process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request); + + std::atomic svr_sock_{INVALID_SOCKET}; + 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_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; + time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; + size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; + +private: + using Handlers = std::vector>; + using HandlersForContentReader = + std::vector>; + + 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(const Request &req, Response &res, + bool head = false); + bool dispatch_request(Request &req, Response &res, const Handlers &handlers); + bool + dispatch_request_for_content_reader(Request &req, Response &res, + ContentReader content_reader, + const HandlersForContentReader &handlers); + + bool parse_request_line(const char *s, Request &req); + void apply_ranges(const Request &req, Response &res, + std::string &content_type, std::string &boundary); + bool write_response(Stream &strm, bool close_connection, const 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, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + bool read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + + virtual bool process_and_close_socket(socket_t sock); + + struct MountPointEntry { + std::string mount_point; + std::string base_dir; + Headers headers; + }; + std::vector base_dirs_; + + std::atomic is_running_{false}; + std::atomic done_{false}; + std::map file_extension_and_mimetype_map_; + Handler file_request_handler_; + Handlers get_handlers_; + Handlers post_handlers_; + HandlersForContentReader post_handlers_for_content_reader_; + Handlers put_handlers_; + HandlersForContentReader put_handlers_for_content_reader_; + Handlers patch_handlers_; + HandlersForContentReader patch_handlers_for_content_reader_; + Handlers delete_handlers_; + HandlersForContentReader delete_handlers_for_content_reader_; + Handlers options_handlers_; + HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; + HandlerWithResponse pre_routing_handler_; + Handler post_routing_handler_; + Logger logger_; + Expect100ContinueHandler expect_100_continue_handler_; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = default_socket_options; + + Headers default_headers_; +}; + +enum class Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, + + // For internal use only + SSLPeerCouldBeClosed_, +}; + +std::string to_string(const Error error); + +std::ostream &operator<<(std::ostream &os, const Error &obj); + +class Result { +public: + Result(std::unique_ptr &&res, Error err, + Headers &&request_headers = Headers{}) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)) {} + // Response + operator bool() const { return res_ != nullptr; } + bool operator==(std::nullptr_t) const { return res_ == nullptr; } + bool operator!=(std::nullptr_t) const { return res_ != nullptr; } + const Response &value() const { return *res_; } + Response &value() { return *res_; } + const Response &operator*() const { return *res_; } + Response &operator*() { return *res_; } + const Response *operator->() const { return res_.get(); } + Response *operator->() { return res_.get(); } + + // Error + Error error() const { return err_; } + + // Request Headers + bool has_request_header(const std::string &key) const; + std::string get_request_header_value(const std::string &key, + size_t id = 0) const; + template + T get_request_header_value(const std::string &key, size_t id = 0) const; + size_t get_request_header_value_count(const std::string &key) const; + +private: + std::unique_ptr res_; + Error err_; + Headers request_headers_; +}; + +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; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress 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 Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + size_t is_socket_open() const; + + socket_t socket() const; + + void stop(); + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + 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); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + 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(X509_STORE *ca_cert_store); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + +protected: + struct Socket { + socket_t sock = INVALID_SOCKET; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSL *ssl = nullptr; +#endif + + bool is_open() const { return sock != INVALID_SOCKET; } + }; + + virtual bool create_and_connect_socket(Socket &socket, Error &error); + + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // should ONLY be called when socket_mutex_ is locked. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. + virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); + void shutdown_socket(Socket &socket); + void close_socket(Socket &socket); + + bool process_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + + bool write_content_with_provider(Stream &strm, const Request &req, + Error &error); + + void copy_settings(const ClientImpl &rhs); + + // Socket endpoint information + const std::string host_; + const int port_; + const std::string host_and_port_; + + // Current open socket + Socket socket_; + mutable std::mutex socket_mutex_; + std::recursive_mutex request_mutex_; + + // These are all protected under socket_mutex + size_t socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Hostname-IP map + std::map addr_map_; + + // Default headers + Headers default_headers_; + + // 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_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + + std::string basic_auth_username_; + std::string basic_auth_password_; + std::string bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string digest_auth_username_; + std::string digest_auth_password_; +#endif + + bool keep_alive_ = false; + bool follow_location_ = false; + + bool url_encode_ = true; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = nullptr; + + bool compress_ = false; + bool decompress_ = true; + + 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_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string proxy_digest_auth_username_; + std::string proxy_digest_auth_password_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string ca_cert_file_path_; + std::string ca_cert_dir_path_; + + X509_STORE *ca_cert_store_ = nullptr; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool server_certificate_verification_ = true; +#endif + + Logger 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 write_request(Stream &strm, Request &req, bool close_connection, + Error &error); + bool redirect(Request &req, Response &res, Error &error); + bool handle_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + std::unique_ptr send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error); + Result send_with_content_provider( + 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); + ContentProviderWithoutLength get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + std::string adjust_host_string(const std::string &host) const; + + virtual bool process_socket(const Socket &socket, + std::function callback); + virtual bool is_ssl() const; +}; + +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(); + + bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress 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 Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + size_t is_socket_open() const; + + socket_t socket() const; + + void stop(); + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + 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); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + + // SSL +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + 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(X509_STORE *ca_cert_store); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; +#endif + +private: + std::unique_ptr cli_; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool is_ssl_ = false; +#endif +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +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); + + SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + + SSLServer( + const std::function &setup_ssl_ctx_callback); + + ~SSLServer() override; + + bool is_valid() const override; + + SSL_CTX *ssl_context() const; + +private: + bool process_and_close_socket(socket_t sock) override; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; +}; + +class SSLClient : 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); + + explicit SSLClient(const std::string &host, int port, X509 *client_cert, + EVP_PKEY *client_key); + + ~SSLClient() override; + + bool is_valid() const override; + + void set_ca_cert_store(X509_STORE *ca_cert_store); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; + +private: + bool create_and_connect_socket(Socket &socket, Error &error) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + void shutdown_ssl_impl(Socket &socket, bool shutdown_socket); + + bool process_socket(const Socket &socket, + std::function callback) override; + bool is_ssl() const override; + + bool connect_with_proxy(Socket &sock, Response &res, bool &success, + Error &error); + bool initialize_ssl(Socket &socket, Error &error); + + bool load_certs(); + + bool verify_host(X509 *server_cert) const; + bool verify_host_with_subject_alt_name(X509 *server_cert) const; + bool verify_host_with_common_name(X509 *server_cert) const; + bool check_host_name(const char *pattern, size_t pattern_len) const; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; + std::once_flag initialize_cert_; + + std::vector host_components_; + + long verify_result_ = 0; + + friend class ClientImpl; +}; +#endif + +/* + * Implementation of template methods. + */ + +namespace detail { + +template +inline void duration_to_sec_and_usec(const T &duration, U callback) { + auto sec = std::chrono::duration_cast(duration).count(); + auto usec = std::chrono::duration_cast( + duration - std::chrono::seconds(sec)) + .count(); + callback(static_cast(sec), static_cast(usec)); +} + +template +inline T get_header_value(const Headers & /*headers*/, + const std::string & /*key*/, size_t /*id*/ = 0, + uint64_t /*def*/ = 0) {} + +template <> +inline uint64_t get_header_value(const Headers &headers, + const std::string &key, size_t id, + uint64_t def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + return std::strtoull(it->second.data(), nullptr, 10); + } + return def; +} + +} // namespace detail + +template +inline T Request::get_header_value(const std::string &key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + +template +inline T Response::get_header_value(const std::string &key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + +template +inline ssize_t Stream::write_format(const char *fmt, const Args &...args) { + const auto bufsiz = 2048; + std::array buf{}; + + auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); + if (sn <= 0) { return sn; } + + auto n = static_cast(sn); + + if (n >= buf.size() - 1) { + std::vector glowable_buf(buf.size()); + + while (n >= glowable_buf.size() - 1) { + glowable_buf.resize(glowable_buf.size() * 2); + n = static_cast( + snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); + } + return write(&glowable_buf[0], n); + } else { + return write(buf.data(), n); + } +} + +inline void default_socket_options(socket_t sock) { + int yes = 1; +#ifdef _WIN32 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + reinterpret_cast(&yes), sizeof(yes)); +#else +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), + sizeof(yes)); +#else + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); +#endif +#endif +} + +template +inline Server & +Server::set_read_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_write_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_idle_interval(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); + return *this; +} + +inline std::string to_string(const Error error) { + switch (error) { + case Error::Success: return "Success (no error)"; + case Error::Connection: return "Could not establish connection"; + case Error::BindIPAddress: return "Failed to bind IP address"; + case Error::Read: return "Failed to read connection"; + case Error::Write: return "Failed to write connection"; + case Error::ExceedRedirectCount: return "Maximum redirect count exceeded"; + case Error::Canceled: return "Connection handling canceled"; + case Error::SSLConnection: return "SSL connection failed"; + case Error::SSLLoadingCerts: return "SSL certificate loading failed"; + case Error::SSLServerVerification: return "SSL server verification failed"; + case Error::UnsupportedMultipartBoundaryChars: + return "Unsupported HTTP multipart boundary characters"; + case Error::Compression: return "Compression failed"; + case Error::ConnectionTimeout: return "Connection timed out"; + case Error::Unknown: return "Unknown"; + default: break; + } + + return "Invalid"; +} + +inline std::ostream &operator<<(std::ostream &os, const Error &obj) { + os << to_string(obj); + os << " (" << static_cast::type>(obj) << ')'; + return os; +} + +template +inline T Result::get_request_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, 0); +} + +template +inline void ClientImpl::set_connection_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { + set_connection_timeout(sec, usec); + }); +} + +template +inline void ClientImpl::set_read_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_write_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); +} + +template +inline void Client::set_connection_timeout( + const std::chrono::duration &duration) { + cli_->set_connection_timeout(duration); +} + +template +inline void +Client::set_read_timeout(const std::chrono::duration &duration) { + cli_->set_read_timeout(duration); +} + +template +inline void +Client::set_write_timeout(const std::chrono::duration &duration) { + cli_->set_write_timeout(duration); +} + +/* + * Forward declarations and types that will be part of the .h file if split into + * .h + .cc. + */ + +std::string hosted_at(const std::string &hostname); + +void hosted_at(const std::string &hostname, std::vector &addrs); + +std::string append_query_params(const std::string &path, const Params ¶ms); + +std::pair make_range_header(Ranges ranges); + +std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy = false); + +namespace detail { + +std::string encode_query_param(const std::string &value); + +std::string decode_url(const std::string &s, bool convert_plus_to_space); + +void read_file(const std::string &path, std::string &out); + +std::string trim_copy(const std::string &s); + +void split(const char *b, const char *e, char d, + std::function fn); + +bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback); + +socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error); + +const char *get_header_value(const Headers &headers, const std::string &key, + size_t id = 0, const char *def = nullptr); + +std::string params_to_query_str(const 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); + +int close_socket(socket_t sock); + +ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); + +ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); + +enum class EncodingType { None = 0, Gzip, Brotli }; + +EncodingType encoding_type(const Request &req, const Response &res); + +class BufferStream : public Stream { +public: + BufferStream() = default; + ~BufferStream() override = default; + + bool is_readable() const override; + bool is_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; + + const std::string &get_buffer() const; + +private: + std::string buffer; + size_t position = 0; +}; + +class compressor { +public: + virtual ~compressor() = default; + + typedef std::function Callback; + virtual bool compress(const char *data, size_t data_length, bool last, + Callback callback) = 0; +}; + +class decompressor { +public: + virtual ~decompressor() = default; + + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char *data, size_t data_length, + Callback callback) = 0; +}; + +class nocompressor : public compressor { +public: + virtual ~nocompressor() = default; + + bool compress(const char *data, size_t data_length, bool /*last*/, + Callback callback) override; +}; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +class gzip_compressor : public compressor { +public: + gzip_compressor(); + ~gzip_compressor(); + + 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 : public decompressor { +public: + gzip_decompressor(); + ~gzip_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +class brotli_compressor : 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 : public decompressor { +public: + brotli_decompressor(); + ~brotli_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; +}; +#endif + +// 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 glowable_buffer_; +}; + +} // namespace detail + +// ---------------------------------------------------------------------------- + +/* + * Implementation that will be part of the .cc file if split into .h + .cc. + */ + +namespace detail { + +inline bool is_hex(char c, int &v) { + if (0x20 <= c && isdigit(c)) { + v = c - '0'; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } + return false; +} + +inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, + int &val) { + if (i >= s.size()) { return false; } + + val = 0; + for (; cnt; i++, cnt--) { + if (!s[i]) { return false; } + int v = 0; + if (is_hex(s[i], v)) { + val = val * 16 + v; + } else { + return false; + } + } + return true; +} + +inline std::string from_i_to_hex(size_t n) { + const char *charset = "0123456789abcdef"; + std::string ret; + do { + ret = charset[n & 15] + ret; + n >>= 4; + } while (n > 0); + return ret; +} + +inline size_t to_utf8(int code, char *buff) { + if (code < 0x0080) { + buff[0] = (code & 0x7F); + return 1; + } else if (code < 0x0800) { + buff[0] = static_cast(0xC0 | ((code >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (code & 0x3F)); + return 2; + } else if (code < 0xD800) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0xE000) { // D800 - DFFF is invalid... + return 0; + } else if (code < 0x10000) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0x110000) { + buff[0] = static_cast(0xF0 | ((code >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((code >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (code & 0x3F)); + return 4; + } + + // NOTREACHED + return 0; +} + +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c +inline std::string base64_encode(const std::string &in) { + static const auto lookup = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string out; + out.reserve(in.size()); + + int val = 0; + int valb = -6; + + for (auto c : in) { + val = (val << 8) + static_cast(c); + valb += 8; + while (valb >= 0) { + out.push_back(lookup[(val >> valb) & 0x3F]); + valb -= 6; + } + } + + if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); } + + while (out.size() % 4) { + out.push_back('='); + } + + return out; +} + +inline bool is_file(const std::string &path) { +#ifdef _WIN32 + return _access_s(path.c_str(), 0) == 0; +#else + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); +#endif +} + +inline bool is_dir(const std::string &path) { + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode); +} + +inline bool is_valid_path(const std::string &path) { + size_t level = 0; + size_t i = 0; + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + + while (i < path.size()) { + // Read component + auto beg = i; + while (i < path.size() && path[i] != '/') { + i++; + } + + auto len = i - beg; + assert(len > 0); + + if (!path.compare(beg, len, ".")) { + ; + } else if (!path.compare(beg, len, "..")) { + if (level == 0) { return false; } + level--; + } else { + level++; + } + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + } + + return true; +} + +inline std::string encode_query_param(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string encode_url(const std::string &s) { + std::string result; + result.reserve(s.size()); + + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case ' ': result += "%20"; break; + case '+': result += "%2B"; break; + case '\r': result += "%0D"; break; + case '\n': result += "%0A"; break; + case '\'': result += "%27"; break; + case ',': result += "%2C"; break; + // case ':': result += "%3A"; break; // ok? probably... + case ';': result += "%3B"; break; + default: + auto c = static_cast(s[i]); + if (c >= 0x80) { + result += '%'; + char hex[4]; + auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); + assert(len == 2); + result.append(hex, static_cast(len)); + } else { + result += s[i]; + } + break; + } + } + + return result; +} + +inline std::string decode_url(const std::string &s, + bool convert_plus_to_space) { + std::string result; + + for (size_t i = 0; i < s.size(); i++) { + if (s[i] == '%' && i + 1 < s.size()) { + if (s[i + 1] == 'u') { + int val = 0; + if (from_hex_to_i(s, i + 2, 4, val)) { + // 4 digits Unicode codes + char buff[4]; + size_t len = to_utf8(val, buff); + if (len > 0) { result.append(buff, len); } + i += 5; // 'u0000' + } else { + result += s[i]; + } + } else { + int val = 0; + if (from_hex_to_i(s, i + 1, 2, val)) { + // 2 digits hex codes + result += static_cast(val); + i += 2; // '00' + } else { + result += s[i]; + } + } + } else if (convert_plus_to_space && s[i] == '+') { + result += ' '; + } else { + result += s[i]; + } + } + + return result; +} + +inline void read_file(const std::string &path, std::string &out) { + std::ifstream fs(path, std::ios_base::binary); + fs.seekg(0, std::ios_base::end); + auto size = fs.tellg(); + fs.seekg(0); + out.resize(static_cast(size)); + fs.read(&out[0], static_cast(size)); +} + +inline std::string file_extension(const std::string &path) { + std::smatch m; + static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); + if (std::regex_search(path, m, re)) { return m[1].str(); } + return std::string(); +} + +inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } + +inline std::pair trim(const char *b, const char *e, size_t left, + size_t right) { + while (b + left < e && is_space_or_tab(b[left])) { + left++; + } + while (right > 0 && is_space_or_tab(b[right - 1])) { + right--; + } + return std::make_pair(left, right); +} + +inline std::string trim_copy(const std::string &s) { + auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); + return s.substr(r.first, r.second - r.first); +} + +inline void split(const char *b, const char *e, char d, + std::function fn) { + size_t i = 0; + size_t beg = 0; + + while (e ? (b + i < e) : (b[i] != '\0')) { + if (b[i] == d) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + beg = i + 1; + } + i++; + } + + if (i) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + } +} + +inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size) + : strm_(strm), fixed_buffer_(fixed_buffer), + fixed_buffer_size_(fixed_buffer_size) {} + +inline const char *stream_line_reader::ptr() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_; + } else { + return glowable_buffer_.data(); + } +} + +inline size_t stream_line_reader::size() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_used_size_; + } else { + return glowable_buffer_.size(); + } +} + +inline bool stream_line_reader::end_with_crlf() const { + auto end = ptr() + size(); + return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; +} + +inline bool stream_line_reader::getline() { + fixed_buffer_used_size_ = 0; + glowable_buffer_.clear(); + + for (size_t i = 0;; i++) { + char byte; + auto n = strm_.read(&byte, 1); + + if (n < 0) { + return false; + } else if (n == 0) { + if (i == 0) { + return false; + } else { + break; + } + } + + append(byte); + + if (byte == '\n') { break; } + } + + return true; +} + +inline void stream_line_reader::append(char c) { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { + fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } else { + if (glowable_buffer_.empty()) { + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + } + glowable_buffer_ += c; + } +} + +inline int close_socket(socket_t sock) { +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif +} + +template inline ssize_t handle_EINTR(T fn) { + ssize_t res = false; + while (true) { + res = fn(); + if (res < 0 && errno == EINTR) { continue; } + break; + } + return res; +} + +inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) { + return handle_EINTR([&]() { + return recv(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, + int flags) { + return handle_EINTR([&]() { + return send(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); + }); +#endif +} + +inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return 1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); + }); +#endif +} + +inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, + time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN | POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + + if (poll_res == 0) { return Error::ConnectionTimeout; } + + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { + int error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + + return Error::Connection; +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return Error::Connection; } +#endif + + fd_set fdsr; + FD_ZERO(&fdsr); + FD_SET(sock, &fdsr); + + auto fdsw = fdsr; + auto fdse = fdsr; + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + auto ret = handle_EINTR([&]() { + return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); + }); + + if (ret == 0) { return Error::ConnectionTimeout; } + + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { + int error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + return Error::Connection; +#endif +} + +inline bool is_socket_alive(socket_t sock) { + const auto val = detail::select_read(sock, 0, 0); + if (val == 0) { + return true; + } else if (val < 0 && errno == EBADF) { + return false; + } + char buf[1]; + return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0; +} + +class SocketStream : public Stream { +public: + SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec); + ~SocketStream() override; + + bool is_readable() const override; + bool is_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; + +private: + socket_t sock_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; + + std::vector read_buff_; + size_t read_buff_off_ = 0; + size_t read_buff_content_size_ = 0; + + static const size_t read_buff_size_ = 1024 * 4; +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLSocketStream : public Stream { +public: + SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec); + ~SSLSocketStream() override; + + bool is_readable() const override; + bool is_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; + +private: + socket_t sock_; + SSL *ssl_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; +}; +#endif + +inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) { + using namespace std::chrono; + auto start = steady_clock::now(); + while (true) { + auto val = select_read(sock, 0, 10000); + if (val < 0) { + return false; + } else if (val == 0) { + auto current = steady_clock::now(); + auto duration = duration_cast(current - start); + auto timeout = keep_alive_timeout_sec * 1000; + if (duration.count() > timeout) { return false; } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } else { + return true; + } + } +} + +template +inline bool +process_server_socket_core(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, T callback) { + assert(keep_alive_max_count > 0); + auto ret = false; + auto count = keep_alive_max_count; + while (svr_sock != INVALID_SOCKET && count > 0 && + keep_alive(sock, keep_alive_timeout_sec)) { + auto close_connection = count == 1; + auto connection_closed = false; + ret = callback(close_connection, connection_closed); + if (!ret || connection_closed) { break; } + count--; + } + return ret; +} + +template +inline bool +process_server_socket(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +inline 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, + std::function callback) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +inline int shutdown_socket(socket_t sock) { +#ifdef _WIN32 + return shutdown(sock, SD_BOTH); +#else + return shutdown(sock, SHUT_RDWR); +#endif +} + +template +socket_t create_socket(const std::string &host, const std::string &ip, int port, + int address_family, int socket_flags, bool tcp_nodelay, + SocketOptions socket_options, + BindOrConnect bind_or_connect) { + // Get address info + const char *node = nullptr; + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (!ip.empty()) { + node = ip.c_str(); + // Ask getaddrinfo to convert IP in c-string to address + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + } else { + if (!host.empty()) { node = host.c_str(); } + hints.ai_family = address_family; + hints.ai_flags = socket_flags; + } + +#ifndef _WIN32 + if (hints.ai_family == AF_UNIX) { + const auto addrlen = host.length(); + if (addrlen > sizeof(sockaddr_un::sun_path)) return INVALID_SOCKET; + + auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); + if (sock != INVALID_SOCKET) { + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + std::copy(host.begin(), host.end(), addr.sun_path); + + hints.ai_addr = reinterpret_cast(&addr); + hints.ai_addrlen = static_cast( + sizeof(addr) - sizeof(addr.sun_path) + addrlen); + + fcntl(sock, F_SETFD, FD_CLOEXEC); + if (socket_options) { socket_options(sock); } + + if (!bind_or_connect(sock, hints)) { + close_socket(sock); + sock = INVALID_SOCKET; + } + } + return sock; + } +#endif + + auto service = std::to_string(port); + + if (getaddrinfo(node, service.c_str(), &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return INVALID_SOCKET; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + // Create a socket +#ifdef _WIN32 + auto sock = + WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, + WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); + /** + * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 + * and above the socket creation fails on older Windows Systems. + * + * Let's try to create a socket the old way in this case. + * + * Reference: + * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa + * + * WSA_FLAG_NO_HANDLE_INHERIT: + * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with + * SP1, and later + * + */ + if (sock == INVALID_SOCKET) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + } +#else + auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +#endif + if (sock == INVALID_SOCKET) { continue; } + +#ifndef _WIN32 + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { + close_socket(sock); + continue; + } +#endif + + if (tcp_nodelay) { + int yes = 1; + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&yes), + sizeof(yes)); + } + + if (socket_options) { socket_options(sock); } + + if (rp->ai_family == AF_INET6) { + int no = 0; + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&no), + sizeof(no)); + } + + // bind or connect + if (bind_or_connect(sock, *rp)) { + freeaddrinfo(result); + return sock; + } + + close_socket(sock); + } + + freeaddrinfo(result); + return INVALID_SOCKET; +} + +inline void set_nonblocking(socket_t sock, bool nonblocking) { +#ifdef _WIN32 + auto flags = nonblocking ? 1UL : 0UL; + ioctlsocket(sock, FIONBIO, &flags); +#else + auto flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, + nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); +#endif +} + +inline bool is_connection_error() { +#ifdef _WIN32 + return WSAGetLastError() != WSAEWOULDBLOCK; +#else + return errno != EINPROGRESS; +#endif +} + +inline bool bind_ip_address(socket_t sock, const std::string &host) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; } + + auto ret = false; + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &ai = *rp; + if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + ret = true; + break; + } + } + + freeaddrinfo(result); + return ret; +} + +#if !defined _WIN32 && !defined ANDROID && !defined _AIX +#define USE_IF2IP +#endif + +#ifdef USE_IF2IP +inline std::string if2ip(int address_family, const std::string &ifn) { + struct ifaddrs *ifap; + getifaddrs(&ifap); + std::string addr_candidate; + for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifn == ifa->ifa_name && + (AF_UNSPEC == address_family || + ifa->ifa_addr->sa_family == address_family)) { + if (ifa->ifa_addr->sa_family == AF_INET) { + auto sa = reinterpret_cast(ifa->ifa_addr); + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { + freeifaddrs(ifap); + return std::string(buf, INET_ADDRSTRLEN); + } + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + auto sa = reinterpret_cast(ifa->ifa_addr); + if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) { + char buf[INET6_ADDRSTRLEN] = {}; + if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) { + // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL + auto s6_addr_head = sa->sin6_addr.s6_addr[0]; + if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) { + addr_candidate = std::string(buf, INET6_ADDRSTRLEN); + } else { + freeifaddrs(ifap); + return std::string(buf, INET6_ADDRSTRLEN); + } + } + } + } + } + } + freeifaddrs(ifap); + return addr_candidate; +} +#endif + +inline socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error) { + auto sock = create_socket( + host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options), + [&](socket_t sock2, struct addrinfo &ai) -> bool { + if (!intf.empty()) { +#ifdef USE_IF2IP + auto ip_from_if = if2ip(address_family, intf); + if (ip_from_if.empty()) { ip_from_if = intf; } + if (!bind_ip_address(sock2, ip_from_if.c_str())) { + error = Error::BindIPAddress; + return false; + } +#endif + } + + set_nonblocking(sock2, true); + + auto ret = + ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); + + if (ret < 0) { + if (is_connection_error()) { + error = Error::Connection; + return false; + } + error = wait_until_socket_is_ready(sock2, connection_timeout_sec, + connection_timeout_usec); + if (error != Error::Success) { return false; } + } + + set_nonblocking(sock2, false); + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec * 1000 + + read_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec); + tv.tv_usec = static_cast(read_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec * 1000 + + write_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec); + tv.tv_usec = static_cast(write_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + + error = Error::Success; + return true; + }); + + if (sock != INVALID_SOCKET) { + error = Error::Success; + } else { + if (error == Error::Success) { error = Error::Connection; } + } + + return sock; +} + +inline bool get_ip_and_port(const struct sockaddr_storage &addr, + socklen_t addr_len, std::string &ip, int &port) { + if (addr.ss_family == AF_INET) { + port = ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + port = + ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return false; + } + + std::array ipstr{}; + if (getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) { + return false; + } + + ip = ipstr.data(); + return true; +} + +inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (!getsockname(sock, reinterpret_cast(&addr), + &addr_len)) { + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + if (!getpeername(sock, reinterpret_cast(&addr), + &addr_len)) { +#ifndef _WIN32 + if (addr.ss_family == AF_UNIX) { +#if defined(__linux__) + struct ucred ucred; + socklen_t len = sizeof(ucred); + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) { + port = ucred.pid; + } +#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__ + pid_t pid; + socklen_t len = sizeof(pid); + if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) { + port = pid; + } +#endif + return; + } +#endif + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline constexpr unsigned int str2tag_core(const char *s, size_t l, + unsigned int h) { + return (l == 0) + ? h + : str2tag_core( + s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(*s)); +} + +inline unsigned int str2tag(const std::string &s) { + return str2tag_core(s.data(), s.size(), 0); +} + +namespace udl { + +inline constexpr unsigned int operator"" _t(const char *s, size_t l) { + return str2tag_core(s, l, 0); +} + +} // namespace udl + +inline const char * +find_content_type(const std::string &path, + const std::map &user_data) { + auto ext = file_extension(path); + + auto it = user_data.find(ext); + if (it != user_data.end()) { return it->second.c_str(); } + + using udl::operator""_t; + + switch (str2tag(ext)) { + default: return nullptr; + case "css"_t: return "text/css"; + case "csv"_t: return "text/csv"; + case "htm"_t: + case "html"_t: return "text/html"; + case "js"_t: + case "mjs"_t: return "text/javascript"; + case "txt"_t: return "text/plain"; + case "vtt"_t: return "text/vtt"; + + case "apng"_t: return "image/apng"; + case "avif"_t: return "image/avif"; + case "bmp"_t: return "image/bmp"; + case "gif"_t: return "image/gif"; + case "png"_t: return "image/png"; + case "svg"_t: return "image/svg+xml"; + case "webp"_t: return "image/webp"; + case "ico"_t: return "image/x-icon"; + case "tif"_t: return "image/tiff"; + case "tiff"_t: return "image/tiff"; + case "jpg"_t: + case "jpeg"_t: return "image/jpeg"; + + case "mp4"_t: return "video/mp4"; + case "mpeg"_t: return "video/mpeg"; + case "webm"_t: return "video/webm"; + + case "mp3"_t: return "audio/mp3"; + case "mpga"_t: return "audio/mpeg"; + case "weba"_t: return "audio/webm"; + case "wav"_t: return "audio/wave"; + + case "otf"_t: return "font/otf"; + case "ttf"_t: return "font/ttf"; + case "woff"_t: return "font/woff"; + case "woff2"_t: return "font/woff2"; + + case "7z"_t: return "application/x-7z-compressed"; + case "atom"_t: return "application/atom+xml"; + case "pdf"_t: return "application/pdf"; + case "json"_t: return "application/json"; + case "rss"_t: return "application/rss+xml"; + case "tar"_t: return "application/x-tar"; + case "xht"_t: + case "xhtml"_t: return "application/xhtml+xml"; + case "xslt"_t: return "application/xslt+xml"; + case "xml"_t: return "application/xml"; + case "gz"_t: return "application/gzip"; + case "zip"_t: return "application/zip"; + case "wasm"_t: return "application/wasm"; + } +} + +inline const char *status_message(int status) { + switch (status) { + case 100: return "Continue"; + case 101: return "Switching Protocol"; + case 102: return "Processing"; + case 103: return "Early Hints"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 208: return "Already Reported"; + case 226: return "IM Used"; + case 300: return "Multiple Choice"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 306: return "unused"; + case 307: return "Temporary Redirect"; + case 308: return "Permanent Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Payload Too Large"; + case 414: return "URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 418: return "I'm a teapot"; + case 421: return "Misdirected Request"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 425: return "Too Early"; + case 426: return "Upgrade Required"; + case 428: return "Precondition Required"; + case 429: return "Too Many Requests"; + case 431: return "Request Header Fields Too Large"; + case 451: return "Unavailable For Legal Reasons"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 506: return "Variant Also Negotiates"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + + default: + case 500: return "Internal Server Error"; + } +} + +inline bool can_compress_content_type(const std::string &content_type) { + using udl::operator""_t; + + auto tag = str2tag(content_type); + + switch (tag) { + case "image/svg+xml"_t: + case "application/javascript"_t: + case "application/json"_t: + case "application/xml"_t: + case "application/protobuf"_t: + case "application/xhtml+xml"_t: return true; + + default: + return !content_type.rfind("text/", 0) && tag != "text/event-stream"_t; + } +} + +inline EncodingType encoding_type(const Request &req, const Response &res) { + auto ret = + detail::can_compress_content_type(res.get_header_value("Content-Type")); + if (!ret) { return EncodingType::None; } + + const auto &s = req.get_header_value("Accept-Encoding"); + (void)(s); + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + // TODO: 'Accept-Encoding' has br, not br;q=0 + ret = s.find("br") != std::string::npos; + if (ret) { return EncodingType::Brotli; } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 + ret = s.find("gzip") != std::string::npos; + if (ret) { return EncodingType::Gzip; } +#endif + + return EncodingType::None; +} + +inline bool nocompressor::compress(const char *data, size_t data_length, + bool /*last*/, Callback callback) { + if (!data_length) { return true; } + return callback(data, data_length); +} + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +inline gzip_compressor::gzip_compressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; +} + +inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); } + +inline bool gzip_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + assert(is_valid_); + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH; + int ret = Z_OK; + + std::array buff{}; + do { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = deflate(&strm_, flush); + if (ret == Z_STREAM_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } while (strm_.avail_out == 0); + + assert((flush == Z_FINISH && ret == Z_STREAM_END) || + (flush == Z_NO_FLUSH && ret == Z_OK)); + assert(strm_.avail_in == 0); + } while (data_length > 0); + + return true; +} + +inline gzip_decompressor::gzip_decompressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; +} + +inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); } + +inline bool gzip_decompressor::is_valid() const { return is_valid_; } + +inline bool gzip_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { + assert(is_valid_); + + int ret = Z_OK; + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + std::array buff{}; + while (strm_.avail_in > 0) { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + auto prev_avail_in = strm_.avail_in; + + ret = inflate(&strm_, Z_NO_FLUSH); + + if (prev_avail_in - strm_.avail_in == 0) { return false; } + + assert(ret != Z_STREAM_ERROR); + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: inflateEnd(&strm_); return false; + } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } + + if (ret != Z_OK && ret != Z_STREAM_END) return false; + + } while (data_length > 0); + + return true; +} +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +inline brotli_compressor::brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); +} + +inline brotli_compressor::~brotli_compressor() { + BrotliEncoderDestroyInstance(state_); +} + +inline bool brotli_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + std::array buff{}; + + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); + + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { break; } + } else { + if (!available_in) { break; } + } + + auto available_out = buff.size(); + auto next_out = buff.data(); + + if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in, + &available_out, &next_out, nullptr)) { + return false; + } + + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); + } + } + + return true; +} + +inline brotli_decompressor::brotli_decompressor() { + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; +} + +inline brotli_decompressor::~brotli_decompressor() { + if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } +} + +inline bool brotli_decompressor::is_valid() const { return decoder_s; } + +inline bool brotli_decompressor::decompress(const char *data, + size_t data_length, + Callback callback) { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + const uint8_t *next_in = (const uint8_t *)data; + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; +} +#endif + +inline bool has_header(const Headers &headers, const std::string &key) { + return headers.find(key) != headers.end(); +} + +inline const char *get_header_value(const Headers &headers, + const std::string &key, size_t id, + const char *def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second.c_str(); } + return def; +} + +inline bool compare_case_ignore(const std::string &a, const std::string &b) { + if (a.size() != b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; +} + +template +inline bool parse_header(const char *beg, const char *end, T fn) { + // Skip trailing spaces and tabs. + while (beg < end && is_space_or_tab(end[-1])) { + end--; + } + + auto p = beg; + while (p < end && *p != ':') { + p++; + } + + if (p == end) { return false; } + + auto key_end = p; + + if (*p++ != ':') { return false; } + + while (p < end && is_space_or_tab(*p)) { + p++; + } + + if (p < end) { + auto key = std::string(beg, key_end); + auto val = compare_case_ignore(key, "Location") + ? std::string(p, end) + : decode_url(std::string(p, end), false); + fn(std::move(key), std::move(val)); + return true; + } + + return false; +} + +inline bool read_headers(Stream &strm, Headers &headers) { + const auto bufsiz = 2048; + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + + for (;;) { + if (!line_reader.getline()) { return false; } + + // Check if the line ends with CRLF. + auto line_terminator_len = 2; + if (line_reader.end_with_crlf()) { + // Blank line indicates end of headers. + if (line_reader.size() == 2) { break; } +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + } else { + // Blank line indicates end of headers. + if (line_reader.size() == 1) { break; } + line_terminator_len = 1; + } +#else + } else { + continue; // Skip invalid line. + } +#endif + + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + headers.emplace(std::move(key), std::move(val)); + }); + } + + return true; +} + +inline bool read_content_with_length(Stream &strm, uint64_t len, + Progress progress, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return false; } + + if (!out(buf, static_cast(n), r, len)) { return false; } + r += static_cast(n); + + if (progress) { + if (!progress(r, len)) { return false; } + } + } + + return true; +} + +inline void skip_content_with_length(Stream &strm, uint64_t len) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return; } + r += static_cast(n); + } +} + +inline bool read_content_without_length(Stream &strm, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + for (;;) { + auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); + if (n < 0) { + return false; + } else if (n == 0) { + return true; + } + + if (!out(buf, static_cast(n), r, 0)) { return false; } + r += static_cast(n); + } + + return true; +} + +template +inline bool read_content_chunked(Stream &strm, T &x, + ContentReceiverWithProgress out) { + const auto bufsiz = 16; + char buf[bufsiz]; + + stream_line_reader line_reader(strm, buf, bufsiz); + + if (!line_reader.getline()) { return false; } + + unsigned long chunk_len; + while (true) { + char *end_ptr; + + chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); + + if (end_ptr == line_reader.ptr()) { return false; } + if (chunk_len == ULONG_MAX) { return false; } + + if (chunk_len == 0) { break; } + + if (!read_content_with_length(strm, chunk_len, nullptr, out)) { + return false; + } + + if (!line_reader.getline()) { return false; } + + if (strcmp(line_reader.ptr(), "\r\n")) { return false; } + + if (!line_reader.getline()) { return false; } + } + + assert(chunk_len == 0); + + // Trailer + if (!line_reader.getline()) { return false; } + + while (strcmp(line_reader.ptr(), "\r\n")) { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + constexpr auto line_terminator_len = 2; + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + x.headers.emplace(std::move(key), std::move(val)); + }); + + if (!line_reader.getline()) { return false; } + } + + return true; +} + +inline bool is_chunked_transfer_encoding(const Headers &headers) { + return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""), + "chunked"); +} + +template +bool prepare_content_receiver(T &x, int &status, + ContentReceiverWithProgress receiver, + bool decompress, U callback) { + if (decompress) { + std::string encoding = x.get_header_value("Content-Encoding"); + std::unique_ptr decompressor; + + if (encoding == "gzip" || encoding == "deflate") { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } else if (encoding.find("br") != std::string::npos) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } + + if (decompressor) { + if (decompressor->is_valid()) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, + uint64_t off, uint64_t len) { + return decompressor->decompress(buf, n, + [&](const char *buf2, size_t n2) { + return receiver(buf2, n2, off, len); + }); + }; + return callback(std::move(out)); + } else { + status = 500; + return false; + } + } + } + + ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, + uint64_t len) { + return receiver(buf, n, off, len); + }; + return callback(std::move(out)); +} + +template +bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, + Progress progress, ContentReceiverWithProgress receiver, + bool decompress) { + return prepare_content_receiver( + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress &out) { + auto ret = true; + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) { + ret = read_content_chunked(strm, x, out); + } else if (!has_header(x.headers, "Content-Length")) { + ret = read_content_without_length(strm, out); + } else { + auto len = get_header_value(x.headers, "Content-Length"); + if (len > payload_max_length) { + exceed_payload_max_length = true; + skip_content_with_length(strm, len); + ret = false; + } else if (len > 0) { + ret = read_content_with_length(strm, len, std::move(progress), out); + } + } + + if (!ret) { status = exceed_payload_max_length ? 413 : 400; } + return ret; + }); +} // namespace detail + +inline ssize_t write_headers(Stream &strm, const Headers &headers) { + ssize_t write_len = 0; + for (const auto &x : headers) { + auto len = + strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); + if (len < 0) { return len; } + write_len += len; + } + auto len = strm.write("\r\n"); + if (len < 0) { return len; } + write_len += len; + return write_len; +} + +inline bool write_data(Stream &strm, const char *d, size_t l) { + size_t offset = 0; + while (offset < l) { + auto length = strm.write(d + offset, l - offset); + if (length < 0) { return false; } + offset += static_cast(length); + } + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { + size_t end_offset = offset + length; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + if (strm.is_writable() && write_data(strm, d, l)) { + offset += l; + } else { + ok = false; + } + } + return ok; + }; + + while (offset < end_offset && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, end_offset - offset, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, + const T &is_shutting_down) { + auto error = Error::Success; + return write_content(strm, content_provider, offset, length, is_shutting_down, + error); +} + +template +inline bool +write_content_without_length(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + offset += l; + if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; } + } + return ok; + }; + + data_sink.done = [&](void) { data_available = false; }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + return false; + } else if (!content_provider(offset, 0, data_sink)) { + return false; + } else if (!ok) { + return false; + } + } + return true; +} + +template +inline bool +write_content_chunked(Stream &strm, const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor, Error &error) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + data_available = l > 0; + offset += l; + + std::string payload; + if (compressor.compress(d, l, false, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = + from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + } + } + } else { + ok = false; + } + } + return ok; + }; + + auto done_with_trailer = [&](const Headers *trailer) { + if (!ok) { return; } + + data_available = false; + + std::string payload; + if (!compressor.compress(nullptr, 0, true, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + ok = false; + return; + } + + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + return; + } + } + + static const std::string done_marker("0\r\n"); + if (!write_data(strm, done_marker.data(), done_marker.size())) { + ok = false; + } + + // Trailer + if (trailer) { + for (const auto &kv : *trailer) { + std::string field_line = kv.first + ": " + kv.second + "\r\n"; + if (!write_data(strm, field_line.data(), field_line.size())) { + ok = false; + } + } + } + + static const std::string crlf("\r\n"); + if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; } + }; + + data_sink.done = [&](void) { done_with_trailer(nullptr); }; + + data_sink.done_with_trailer = [&](const Headers &trailer) { + done_with_trailer(&trailer); + }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, 0, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content_chunked(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor) { + auto error = Error::Success; + return write_content_chunked(strm, content_provider, is_shutting_down, + compressor, error); +} + +template +inline bool redirect(T &cli, Request &req, Response &res, + const std::string &path, const std::string &location, + Error &error) { + Request new_req = req; + new_req.path = path; + new_req.redirect_count_ -= 1; + + if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) { + new_req.method = "GET"; + new_req.body.clear(); + new_req.headers.clear(); + } + + Response new_res; + + auto ret = cli.send(new_req, new_res, error); + if (ret) { + req = new_req; + res = new_res; + res.location = location; + } + return ret; +} + +inline std::string params_to_query_str(const Params ¶ms) { + std::string query; + + for (auto it = params.begin(); it != params.end(); ++it) { + if (it != params.begin()) { query += "&"; } + query += it->first; + query += "="; + query += encode_query_param(it->second); + } + return query; +} + +inline void parse_query_text(const std::string &s, Params ¶ms) { + std::set cache; + split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(kv); + + std::string key; + std::string val; + split(b, e, '=', [&](const char *b2, const char *e2) { + if (key.empty()) { + key.assign(b2, e2); + } else { + val.assign(b2, e2); + } + }); + + if (!key.empty()) { + params.emplace(decode_url(key, true), decode_url(val, true)); + } + }); +} + +inline bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary) { + auto boundary_keyword = "boundary="; + auto pos = content_type.find(boundary_keyword); + if (pos == std::string::npos) { return false; } + auto end = content_type.find(';', pos); + auto beg = pos + strlen(boundary_keyword); + boundary = content_type.substr(beg, end - beg); + if (boundary.length() >= 2 && boundary.front() == '"' && + boundary.back() == '"') { + boundary = boundary.substr(1, boundary.size() - 2); + } + return !boundary.empty(); +} + +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +inline bool parse_range_header(const std::string &s, Ranges &ranges) { +#else +inline bool parse_range_header(const std::string &s, Ranges &ranges) try { +#endif + static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))"); + std::smatch m; + if (std::regex_match(s, m, re_first_range)) { + auto pos = static_cast(m.position(1)); + auto len = static_cast(m.length(1)); + bool all_valid_ranges = true; + split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) { + if (!all_valid_ranges) return; + static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))"); + std::cmatch cm; + if (std::regex_match(b, e, cm, re_another_range)) { + ssize_t first = -1; + if (!cm.str(1).empty()) { + first = static_cast(std::stoll(cm.str(1))); + } + + ssize_t last = -1; + if (!cm.str(2).empty()) { + last = static_cast(std::stoll(cm.str(2))); + } + + if (first != -1 && last != -1 && first > last) { + all_valid_ranges = false; + return; + } + ranges.emplace_back(std::make_pair(first, last)); + } + }); + return all_valid_ranges; + } + return false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +} +#else +} catch (...) { return false; } +#endif + +class MultipartFormDataParser { +public: + MultipartFormDataParser() = default; + + void set_boundary(std::string &&boundary) { + boundary_ = boundary; + dash_boundary_crlf_ = dash_ + boundary_ + crlf_; + crlf_dash_boundary_ = crlf_ + dash_ + boundary_; + } + + bool is_valid() const { return is_valid_; } + + bool parse(const char *buf, size_t n, const ContentReceiver &content_callback, + const MultipartContentHeader &header_callback) { + + // TODO: support 'filename*' + static const std::regex re_content_disposition( + R"~(^Content-Disposition:\s*form-data;\s*name="(.*?)"(?:;\s*filename="(.*?)")?(?:;\s*filename\*=\S+)?\s*$)~", + std::regex_constants::icase); + + buf_append(buf, n); + + while (buf_size() > 0) { + switch (state_) { + case 0: { // Initial boundary + buf_erase(buf_find(dash_boundary_crlf_)); + if (dash_boundary_crlf_.size() > buf_size()) { return true; } + if (!buf_start_with(dash_boundary_crlf_)) { return false; } + buf_erase(dash_boundary_crlf_.size()); + state_ = 1; + break; + } + case 1: { // New entry + clear_file_info(); + state_ = 2; + break; + } + case 2: { // Headers + auto pos = buf_find(crlf_); + if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + while (pos < buf_size()) { + // Empty line + if (pos == 0) { + if (!header_callback(file_)) { + is_valid_ = false; + return false; + } + buf_erase(crlf_.size()); + state_ = 3; + break; + } + + static const std::string header_name = "content-type:"; + const auto header = buf_head(pos); + if (start_with_case_ignore(header, header_name)) { + file_.content_type = trim_copy(header.substr(header_name.size())); + } else { + std::smatch m; + if (std::regex_match(header, m, re_content_disposition)) { + file_.name = m[1]; + file_.filename = m[2]; + } else { + is_valid_ = false; + return false; + } + } + buf_erase(pos + crlf_.size()); + pos = buf_find(crlf_); + } + if (state_ != 3) { return true; } + break; + } + case 3: { // Body + if (crlf_dash_boundary_.size() > buf_size()) { return true; } + auto pos = buf_find(crlf_dash_boundary_); + if (pos < buf_size()) { + if (!content_callback(buf_data(), pos)) { + is_valid_ = false; + return false; + } + buf_erase(pos + crlf_dash_boundary_.size()); + state_ = 4; + } else { + auto len = buf_size() - crlf_dash_boundary_.size(); + if (len > 0) { + if (!content_callback(buf_data(), len)) { + is_valid_ = false; + return false; + } + buf_erase(len); + } + return true; + } + break; + } + case 4: { // Boundary + if (crlf_.size() > buf_size()) { return true; } + if (buf_start_with(crlf_)) { + buf_erase(crlf_.size()); + state_ = 1; + } else { + if (dash_crlf_.size() > buf_size()) { return true; } + if (buf_start_with(dash_crlf_)) { + buf_erase(dash_crlf_.size()); + is_valid_ = true; + buf_erase(buf_size()); // Remove epilogue + } else { + return true; + } + } + break; + } + } + } + + return true; + } + +private: + void clear_file_info() { + file_.name.clear(); + file_.filename.clear(); + file_.content_type.clear(); + } + + bool start_with_case_ignore(const std::string &a, + const std::string &b) const { + if (a.size() < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; + } + + const std::string dash_ = "--"; + const std::string crlf_ = "\r\n"; + const std::string dash_crlf_ = "--\r\n"; + std::string boundary_; + std::string dash_boundary_crlf_; + std::string crlf_dash_boundary_; + + size_t state_ = 0; + bool is_valid_ = false; + MultipartFormData file_; + + // Buffer + bool start_with(const std::string &a, size_t spos, size_t epos, + const std::string &b) const { + if (epos - spos < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (a[i + spos] != b[i]) { return false; } + } + return true; + } + + size_t buf_size() const { return buf_epos_ - buf_spos_; } + + const char *buf_data() const { return &buf_[buf_spos_]; } + + std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); } + + bool buf_start_with(const std::string &s) const { + return start_with(buf_, buf_spos_, buf_epos_, s); + } + + size_t buf_find(const std::string &s) const { + auto c = s.front(); + + size_t off = buf_spos_; + while (off < buf_epos_) { + auto pos = off; + while (true) { + if (pos == buf_epos_) { return buf_size(); } + if (buf_[pos] == c) { break; } + pos++; + } + + auto remaining_size = buf_epos_ - pos; + if (s.size() > remaining_size) { return buf_size(); } + + if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; } + + off = pos + 1; + } + + return buf_size(); + } + + void buf_append(const char *data, size_t n) { + auto remaining_size = buf_size(); + if (remaining_size > 0 && buf_spos_ > 0) { + for (size_t i = 0; i < remaining_size; i++) { + buf_[i] = buf_[buf_spos_ + i]; + } + } + buf_spos_ = 0; + buf_epos_ = remaining_size; + + if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); } + + for (size_t i = 0; i < n; i++) { + buf_[buf_epos_ + i] = data[i]; + } + buf_epos_ += n; + } + + void buf_erase(size_t size) { buf_spos_ += size; } + + std::string buf_; + size_t buf_spos_ = 0; + size_t buf_epos_ = 0; +}; + +inline std::string to_lower(const char *beg, const char *end) { + std::string out; + auto it = beg; + while (it != end) { + out += static_cast(::tolower(*it)); + it++; + } + return out; +} + +inline std::string make_multipart_data_boundary() { + static const char data[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. + std::random_device seed_gen; + + // Request 128 bits of entropy for initialization + std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()}; + std::mt19937 engine(seed_sequence); + + std::string result = "--cpp-httplib-multipart-data-"; + + for (auto i = 0; i < 16; i++) { + result += data[engine() % (sizeof(data) - 1)]; + } + + return result; +} + +inline bool is_multipart_boundary_chars_valid(const std::string &boundary) { + auto valid = true; + for (size_t i = 0; i < boundary.size(); i++) { + auto c = boundary[i]; + if (!std::isalnum(c) && c != '-' && c != '_') { + valid = false; + break; + } + } + return valid; +} + +template +inline std::string +serialize_multipart_formdata_item_begin(const T &item, + const std::string &boundary) { + std::string body = "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + item.name + "\""; + if (!item.filename.empty()) { + body += "; filename=\"" + item.filename + "\""; + } + body += "\r\n"; + if (!item.content_type.empty()) { + body += "Content-Type: " + item.content_type + "\r\n"; + } + body += "\r\n"; + + return body; +} + +inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; } + +inline std::string +serialize_multipart_formdata_finish(const std::string &boundary) { + return "--" + boundary + "--\r\n"; +} + +inline std::string +serialize_multipart_formdata_get_content_type(const std::string &boundary) { + return "multipart/form-data; boundary=" + boundary; +} + +inline std::string +serialize_multipart_formdata(const MultipartFormDataItems &items, + const std::string &boundary, bool finish = true) { + std::string body; + + for (const auto &item : items) { + body += serialize_multipart_formdata_item_begin(item, boundary); + body += item.content + serialize_multipart_formdata_item_end(); + } + + if (finish) body += serialize_multipart_formdata_finish(boundary); + + return body; +} + +inline std::pair +get_range_offset_and_length(const Request &req, size_t content_length, + size_t index) { + auto r = req.ranges[index]; + + if (r.first == -1 && r.second == -1) { + return std::make_pair(0, content_length); + } + + auto slen = static_cast(content_length); + + if (r.first == -1) { + r.first = (std::max)(static_cast(0), slen - r.second); + r.second = slen - 1; + } + + if (r.second == -1) { r.second = slen - 1; } + return std::make_pair(r.first, static_cast(r.second - r.first) + 1); +} + +inline std::string make_content_range_header_field(size_t offset, size_t length, + size_t content_length) { + std::string field = "bytes "; + field += std::to_string(offset); + field += "-"; + field += std::to_string(offset + length - 1); + field += "/"; + field += std::to_string(content_length); + return field; +} + +template +bool process_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + SToken stoken, CToken ctoken, + Content content) { + for (size_t i = 0; i < req.ranges.size(); i++) { + ctoken("--"); + stoken(boundary); + ctoken("\r\n"); + if (!content_type.empty()) { + ctoken("Content-Type: "); + stoken(content_type); + ctoken("\r\n"); + } + + auto offsets = get_range_offset_and_length(req, res.body.size(), i); + auto offset = offsets.first; + auto length = offsets.second; + + ctoken("Content-Range: "); + stoken(make_content_range_header_field(offset, length, res.body.size())); + ctoken("\r\n"); + ctoken("\r\n"); + if (!content(offset, length)) { return false; } + ctoken("\r\n"); + } + + ctoken("--"); + stoken(boundary); + ctoken("--\r\n"); + + return true; +} + +inline bool make_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + std::string &data) { + return process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { data += token; }, + [&](const std::string &token) { data += token; }, + [&](size_t offset, size_t length) { + if (offset < res.body.size()) { + data += res.body.substr(offset, length); + return true; + } + return false; + }); +} + +inline size_t +get_multipart_ranges_data_length(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type) { + size_t data_length = 0; + + process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { data_length += token.size(); }, + [&](const std::string &token) { data_length += token.size(); }, + [&](size_t /*offset*/, size_t length) { + data_length += length; + return true; + }); + + return data_length; +} + +template +inline bool write_multipart_ranges_data(Stream &strm, const Request &req, + Response &res, + const std::string &boundary, + const std::string &content_type, + const T &is_shutting_down) { + return process_multipart_ranges_data( + req, res, boundary, content_type, + [&](const std::string &token) { strm.write(token); }, + [&](const std::string &token) { strm.write(token); }, + [&](size_t offset, size_t length) { + return write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + }); +} + +inline std::pair +get_range_offset_and_length(const Request &req, const Response &res, + size_t index) { + auto r = req.ranges[index]; + + if (r.second == -1) { + r.second = static_cast(res.content_length_) - 1; + } + + return std::make_pair(r.first, r.second - r.first + 1); +} + +inline bool expect_content(const Request &req) { + if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || + req.method == "PRI" || req.method == "DELETE") { + return true; + } + // TODO: check if Content-Length is set + return false; +} + +inline bool has_crlf(const std::string &s) { + auto p = s.c_str(); + while (*p) { + if (*p == '\r' || *p == '\n') { return true; } + p++; + } + return false; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::string message_digest(const std::string &s, const EVP_MD *algo) { + auto context = std::unique_ptr( + EVP_MD_CTX_new(), EVP_MD_CTX_free); + + unsigned int hash_length = 0; + unsigned char hash[EVP_MAX_MD_SIZE]; + + EVP_DigestInit_ex(context.get(), algo, nullptr); + EVP_DigestUpdate(context.get(), s.c_str(), s.size()); + EVP_DigestFinal_ex(context.get(), hash, &hash_length); + + std::stringstream ss; + for (auto i = 0u; i < hash_length; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') + << (unsigned int)hash[i]; + } + + return ss.str(); +} + +inline std::string MD5(const std::string &s) { + return message_digest(s, EVP_md5()); +} + +inline std::string SHA_256(const std::string &s) { + return message_digest(s, EVP_sha256()); +} + +inline std::string SHA_512(const std::string &s) { + return message_digest(s, EVP_sha512()); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store +inline bool load_system_certs_on_windows(X509_STORE *store) { + auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); + if (!hStore) { return false; } + + auto result = false; + PCCERT_CONTEXT pContext = NULL; + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != + nullptr) { + auto encoded_cert = + static_cast(pContext->pbCertEncoded); + + auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + return result; +} +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX +template +using CFObjectPtr = + std::unique_ptr::type, void (*)(CFTypeRef)>; + +inline void cf_object_ptr_deleter(CFTypeRef obj) { + if (obj) { CFRelease(obj); } +} + +inline bool retrieve_certs_from_keychain(CFObjectPtr &certs) { + CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef}; + CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, + kCFBooleanTrue}; + + CFObjectPtr query( + CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), + cf_object_ptr_deleter); + + if (!query) { return false; } + + CFTypeRef security_items = nullptr; + if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess || + CFArrayGetTypeID() != CFGetTypeID(security_items)) { + return false; + } + + certs.reset(reinterpret_cast(security_items)); + return true; +} + +inline bool retrieve_root_certs_from_keychain(CFObjectPtr &certs) { + CFArrayRef root_security_items = nullptr; + if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) { + return false; + } + + certs.reset(root_security_items); + return true; +} + +inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { + auto result = false; + for (int i = 0; i < CFArrayGetCount(certs); ++i) { + const auto cert = reinterpret_cast( + CFArrayGetValueAtIndex(certs, i)); + + if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; } + + CFDataRef cert_data = nullptr; + if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != + errSecSuccess) { + continue; + } + + CFObjectPtr cert_data_ptr(cert_data, cf_object_ptr_deleter); + + auto encoded_cert = static_cast( + CFDataGetBytePtr(cert_data_ptr.get())); + + auto x509 = + d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); + + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + return result; +} + +inline bool load_system_certs_on_macos(X509_STORE *store) { + auto result = false; + CFObjectPtr certs(nullptr, cf_object_ptr_deleter); + if (retrieve_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store); + } + + if (retrieve_root_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store) || result; + } + + return result; +} +#endif // TARGET_OS_OSX +#endif // _WIN32 +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + +#ifdef _WIN32 +class WSInit { +public: + WSInit() { + WSADATA wsaData; + if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true; + } + + ~WSInit() { + if (is_valid_) WSACleanup(); + } + + bool is_valid_ = false; +}; + +static WSInit wsinit_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::pair make_digest_authentication_header( + const Request &req, const std::map &auth, + size_t cnonce_count, const std::string &cnonce, const std::string &username, + const std::string &password, bool is_proxy = false) { + std::string nc; + { + std::stringstream ss; + ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; + nc = ss.str(); + } + + std::string qop; + if (auth.find("qop") != auth.end()) { + qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } else if (qop.find("auth") != std::string::npos) { + qop = "auth"; + } else { + qop.clear(); + } + } + + std::string algo = "MD5"; + if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } + + std::string response; + { + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; + + auto A1 = username + ":" + auth.at("realm") + ":" + password; + + auto A2 = req.method + ":" + req.path; + if (qop == "auth-int") { A2 += ":" + H(req.body); } + + if (qop.empty()) { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); + } else { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } + } + + auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; + + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + (qop.empty() ? ", response=\"" + : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + + cnonce + "\", response=\"") + + response + "\"" + + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); + + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, field); +} +#endif + +inline bool parse_www_authenticate(const Response &res, + std::map &auth, + bool is_proxy) { + auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; + if (res.has_header(auth_key)) { + static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); + auto s = res.get_header_value(auth_key); + auto pos = s.find(' '); + if (pos != std::string::npos) { + auto type = s.substr(0, pos); + if (type == "Basic") { + return false; + } else if (type == "Digest") { + s = s.substr(pos + 1); + auto beg = std::sregex_iterator(s.begin(), s.end(), re); + for (auto i = beg; i != std::sregex_iterator(); ++i) { + auto m = *i; + auto key = s.substr(static_cast(m.position(1)), + static_cast(m.length(1))); + auto val = m.length(2) > 0 + ? s.substr(static_cast(m.position(2)), + static_cast(m.length(2))) + : s.substr(static_cast(m.position(3)), + static_cast(m.length(3))); + auth[key] = val; + } + return true; + } + } + } + return false; +} + +// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240 +inline std::string random_string(size_t length) { + auto randchar = []() -> char { + const char charset[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + const size_t max_index = (sizeof(charset) - 1); + return charset[static_cast(std::rand()) % max_index]; + }; + std::string str(length, 0); + std::generate_n(str.begin(), length, randchar); + return str; +} + +class ContentProviderAdapter { +public: + explicit ContentProviderAdapter( + ContentProviderWithoutLength &&content_provider) + : content_provider_(content_provider) {} + + bool operator()(size_t offset, size_t, DataSink &sink) { + return content_provider_(offset, sink); + } + +private: + ContentProviderWithoutLength content_provider_; +}; + +} // namespace detail + +inline std::string hosted_at(const std::string &hostname) { + std::vector addrs; + hosted_at(hostname, addrs); + if (addrs.empty()) { return std::string(); } + return addrs[0]; +} + +inline void hosted_at(const std::string &hostname, + std::vector &addrs) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &addr = + *reinterpret_cast(rp->ai_addr); + std::string ip; + int dummy = -1; + if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, + dummy)) { + addrs.push_back(ip); + } + } + + freeaddrinfo(result); +} + +inline std::string append_query_params(const std::string &path, + const Params ¶ms) { + std::string path_with_query = path; + const static std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + detail::params_to_query_str(params); + return path_with_query; +} + +// Header utilities +inline std::pair make_range_header(Ranges ranges) { + std::string field = "bytes="; + auto i = 0; + for (auto r : ranges) { + if (i != 0) { field += ", "; } + if (r.first != -1) { field += std::to_string(r.first); } + field += '-'; + if (r.second != -1) { field += std::to_string(r.second); } + i++; + } + return std::make_pair("Range", std::move(field)); +} + +inline std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, bool is_proxy) { + auto field = "Basic " + detail::base64_encode(username + ":" + password); + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +inline std::pair +make_bearer_token_authentication_header(const std::string &token, + bool is_proxy = false) { + auto field = "Bearer " + token; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +// Request implementation +inline bool Request::has_header(const std::string &key) const { + return detail::has_header(headers, key); +} + +inline std::string Request::get_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(headers, key, id, ""); +} + +inline size_t Request::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Request::set_header(const std::string &key, + const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline bool Request::has_param(const std::string &key) const { + return params.find(key) != params.end(); +} + +inline std::string Request::get_param_value(const std::string &key, + size_t id) const { + auto rng = params.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Request::get_param_value_count(const std::string &key) const { + auto r = params.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline bool Request::is_multipart_form_data() const { + const auto &content_type = get_header_value("Content-Type"); + return !content_type.rfind("multipart/form-data", 0); +} + +inline bool Request::has_file(const std::string &key) const { + return files.find(key) != files.end(); +} + +inline MultipartFormData Request::get_file_value(const std::string &key) const { + auto it = files.find(key); + if (it != files.end()) { return it->second; } + return MultipartFormData(); +} + +inline std::vector +Request::get_file_values(const std::string &key) const { + std::vector values; + auto rng = files.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second); + } + return values; +} + +// Response implementation +inline bool Response::has_header(const std::string &key) const { + return headers.find(key) != headers.end(); +} + +inline std::string Response::get_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(headers, key, id, ""); +} + +inline size_t Response::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Response::set_header(const std::string &key, + const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline void Response::set_redirect(const std::string &url, int stat) { + if (!detail::has_crlf(url)) { + set_header("Location", url); + if (300 <= stat && stat < 400) { + this->status = stat; + } else { + this->status = 302; + } + } +} + +inline void Response::set_content(const char *s, size_t n, + const std::string &content_type) { + body.assign(s, n); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content(const std::string &s, + const std::string &content_type) { + set_content(s.data(), s.size(), content_type); +} + +inline void Response::set_content_provider( + size_t in_length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = in_length; + if (in_length > 0) { content_provider_ = std::move(provider); } + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = false; +} + +inline void Response::set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = false; +} + +inline void Response::set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider_ = true; +} + +// Result implementation +inline bool Result::has_request_header(const std::string &key) const { + return request_headers_.find(key) != request_headers_.end(); +} + +inline std::string Result::get_request_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, ""); +} + +inline size_t +Result::get_request_header_value_count(const std::string &key) const { + auto r = request_headers_.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +// Stream implementation +inline ssize_t Stream::write(const char *ptr) { + return write(ptr, strlen(ptr)); +} + +inline ssize_t Stream::write(const std::string &s) { + return write(s.data(), s.size()); +} + +namespace detail { + +// Socket stream implementation +inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {} + +inline SocketStream::~SocketStream() {} + +inline bool SocketStream::is_readable() const { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SocketStream::read(char *ptr, size_t size) { +#ifdef _WIN32 + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#else + size = (std::min)(size, + static_cast((std::numeric_limits::max)())); +#endif + + if (read_buff_off_ < read_buff_content_size_) { + auto remaining_size = read_buff_content_size_ - read_buff_off_; + if (size <= remaining_size) { + memcpy(ptr, read_buff_.data() + read_buff_off_, size); + read_buff_off_ += size; + return static_cast(size); + } else { + memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size); + read_buff_off_ += remaining_size; + return static_cast(remaining_size); + } + } + + if (!is_readable()) { return -1; } + + read_buff_off_ = 0; + read_buff_content_size_ = 0; + + if (size < read_buff_size_) { + auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, + CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) { + return n; + } else if (n <= static_cast(size)) { + memcpy(ptr, read_buff_.data(), static_cast(n)); + return n; + } else { + memcpy(ptr, read_buff_.data(), size); + read_buff_off_ = size; + read_buff_content_size_ = static_cast(n); + return static_cast(size); + } + } else { + return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); + } +} + +inline ssize_t SocketStream::write(const char *ptr, size_t size) { + if (!is_writable()) { return -1; } + +#if defined(_WIN32) && !defined(_WIN64) + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#endif + + return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); +} + +inline void SocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + return detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + return detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SocketStream::socket() const { return sock_; } + +// Buffer stream implementation +inline bool BufferStream::is_readable() const { return true; } + +inline bool BufferStream::is_writable() const { return true; } + +inline ssize_t BufferStream::read(char *ptr, size_t size) { +#if defined(_MSC_VER) && _MSC_VER < 1910 + auto len_read = buffer._Copy_s(ptr, size, size, position); +#else + auto len_read = buffer.copy(ptr, size, position); +#endif + position += static_cast(len_read); + return static_cast(len_read); +} + +inline ssize_t BufferStream::write(const char *ptr, size_t size) { + buffer.append(ptr, size); + return static_cast(size); +} + +inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline socket_t BufferStream::socket() const { return 0; } + +inline const std::string &BufferStream::get_buffer() const { return buffer; } + +} // namespace detail + +// HTTP server implementation +inline Server::Server() + : new_task_queue( + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) { +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif +} + +inline Server::~Server() {} + +inline Server &Server::Get(const std::string &pattern, Handler handler) { + get_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, Handler handler) { + post_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, + HandlerWithContentReader handler) { + post_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, Handler handler) { + put_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, + HandlerWithContentReader handler) { + put_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, Handler handler) { + patch_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, + HandlerWithContentReader handler) { + patch_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, Handler handler) { + delete_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, + HandlerWithContentReader handler) { + delete_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Options(const std::string &pattern, Handler handler) { + options_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline bool Server::set_base_dir(const std::string &dir, + const std::string &mount_point) { + return set_mount_point(mount_point, dir); +} + +inline bool Server::set_mount_point(const std::string &mount_point, + const std::string &dir, Headers headers) { + if (detail::is_dir(dir)) { + std::string mnt = !mount_point.empty() ? mount_point : "/"; + if (!mnt.empty() && mnt[0] == '/') { + base_dirs_.push_back({mnt, dir, std::move(headers)}); + return true; + } + } + return false; +} + +inline bool Server::remove_mount_point(const std::string &mount_point) { + for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { + if (it->mount_point == mount_point) { + base_dirs_.erase(it); + return true; + } + } + return false; +} + +inline Server & +Server::set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime) { + file_extension_and_mimetype_map_[ext] = mime; + return *this; +} + +inline Server &Server::set_file_request_handler(Handler handler) { + file_request_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler(HandlerWithResponse handler) { + error_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler(Handler handler) { + error_handler_ = [handler](const Request &req, Response &res) { + handler(req, res); + return HandlerResponse::Handled; + }; + return *this; +} + +inline Server &Server::set_exception_handler(ExceptionHandler handler) { + exception_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) { + pre_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_post_routing_handler(Handler handler) { + post_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_logger(Logger logger) { + logger_ = std::move(logger); + return *this; +} + +inline Server & +Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { + expect_100_continue_handler_ = std::move(handler); + + return *this; +} + +inline Server &Server::set_address_family(int family) { + address_family_ = family; + return *this; +} + +inline Server &Server::set_tcp_nodelay(bool on) { + tcp_nodelay_ = on; + return *this; +} + +inline Server &Server::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); + return *this; +} + +inline Server &Server::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); + return *this; +} + +inline Server &Server::set_keep_alive_max_count(size_t count) { + keep_alive_max_count_ = count; + return *this; +} + +inline Server &Server::set_keep_alive_timeout(time_t sec) { + keep_alive_timeout_sec_ = sec; + return *this; +} + +inline Server &Server::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_idle_interval(time_t sec, time_t usec) { + idle_interval_sec_ = sec; + idle_interval_usec_ = usec; + return *this; +} + +inline Server &Server::set_payload_max_length(size_t length) { + payload_max_length_ = length; + return *this; +} + +inline bool Server::bind_to_port(const std::string &host, int port, + int socket_flags) { + if (bind_internal(host, port, socket_flags) < 0) return false; + return true; +} +inline int Server::bind_to_any_port(const std::string &host, int socket_flags) { + return bind_internal(host, 0, socket_flags); +} + +inline bool Server::listen_after_bind() { + auto se = detail::scope_exit([&]() { done_ = true; }); + return listen_internal(); +} + +inline bool Server::listen(const std::string &host, int port, + int socket_flags) { + auto se = detail::scope_exit([&]() { done_ = true; }); + return bind_to_port(host, port, socket_flags) && listen_internal(); +} + +inline bool Server::is_running() const { return is_running_; } + +inline void Server::wait_until_ready() const { + while (!is_running() && !done_) { + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } +} + +inline void Server::stop() { + if (is_running_) { + assert(svr_sock_ != INVALID_SOCKET); + std::atomic sock(svr_sock_.exchange(INVALID_SOCKET)); + detail::shutdown_socket(sock); + detail::close_socket(sock); + } +} + +inline bool Server::parse_request_line(const char *s, Request &req) { + auto len = strlen(s); + if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; } + len -= 2; + + { + size_t count = 0; + + detail::split(s, s + len, ' ', [&](const char *b, const char *e) { + switch (count) { + case 0: req.method = std::string(b, e); break; + case 1: req.target = std::string(b, e); break; + case 2: req.version = std::string(b, e); break; + default: break; + } + count++; + }); + + if (count != 3) { return false; } + } + + static const std::set methods{ + "GET", "HEAD", "POST", "PUT", "DELETE", + "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; + + if (methods.find(req.method) == methods.end()) { return false; } + + if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; } + + { + // Skip URL fragment + for (size_t i = 0; i < req.target.size(); i++) { + if (req.target[i] == '#') { + req.target.erase(i); + break; + } + } + + size_t count = 0; + + detail::split(req.target.data(), req.target.data() + req.target.size(), '?', + [&](const char *b, const char *e) { + switch (count) { + case 0: + req.path = detail::decode_url(std::string(b, e), false); + break; + case 1: { + if (e - b > 0) { + detail::parse_query_text(std::string(b, e), req.params); + } + break; + } + default: break; + } + count++; + }); + + if (count > 2) { return false; } + } + + return true; +} + +inline bool Server::write_response(Stream &strm, bool close_connection, + const Request &req, Response &res) { + return write_response_core(strm, close_connection, req, res, false); +} + +inline bool Server::write_response_with_content(Stream &strm, + bool close_connection, + const Request &req, + Response &res) { + return write_response_core(strm, close_connection, req, res, true); +} + +inline bool Server::write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges) { + assert(res.status != -1); + + if (400 <= res.status && error_handler_ && + error_handler_(req, res) == HandlerResponse::Handled) { + need_apply_ranges = true; + } + + std::string content_type; + std::string boundary; + if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } + + // Prepare additional headers + if (close_connection || req.get_header_value("Connection") == "close") { + res.set_header("Connection", "close"); + } else { + std::stringstream ss; + ss << "timeout=" << keep_alive_timeout_sec_ + << ", max=" << keep_alive_max_count_; + res.set_header("Keep-Alive", ss.str()); + } + + if (!res.has_header("Content-Type") && + (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) { + res.set_header("Content-Type", "text/plain"); + } + + if (!res.has_header("Content-Length") && res.body.empty() && + !res.content_length_ && !res.content_provider_) { + res.set_header("Content-Length", "0"); + } + + if (!res.has_header("Accept-Ranges") && req.method == "HEAD") { + res.set_header("Accept-Ranges", "bytes"); + } + + if (post_routing_handler_) { post_routing_handler_(req, res); } + + // Response line and headers + { + detail::BufferStream bstrm; + + if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, + detail::status_message(res.status))) { + return false; + } + + if (!detail::write_headers(bstrm, res.headers)) { return false; } + + // Flush buffer + auto &data = bstrm.get_buffer(); + detail::write_data(strm, data.data(), data.size()); + } + + // Body + auto ret = true; + if (req.method != "HEAD") { + if (!res.body.empty()) { + if (!detail::write_data(strm, res.body.data(), res.body.size())) { + ret = false; + } + } else if (res.content_provider_) { + if (write_content_with_provider(strm, req, res, boundary, content_type)) { + res.content_provider_success_ = true; + } else { + res.content_provider_success_ = false; + ret = false; + } + } + } + + // Log + if (logger_) { logger_(req, res); } + + return ret; +} + +inline bool +Server::write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type) { + auto is_shutting_down = [this]() { + return this->svr_sock_ == INVALID_SOCKET; + }; + + if (res.content_length_ > 0) { + if (req.ranges.empty()) { + return detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down); + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + auto length = offsets.second; + return detail::write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + } else { + return detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, is_shutting_down); + } + } else { + if (res.is_chunked_content_provider_) { + auto type = detail::encoding_type(req, res); + + std::unique_ptr compressor; + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); +#endif + } else { + compressor = detail::make_unique(); + } + assert(compressor != nullptr); + + return detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor); + } else { + return detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down); + } + } +} + +inline bool Server::read_content(Stream &strm, Request &req, Response &res) { + MultipartFormDataMap::iterator cur; + auto file_count = 0; + if (read_content_core( + strm, req, res, + // Regular + [&](const char *buf, size_t n) { + if (req.body.size() + n > req.body.max_size()) { return false; } + req.body.append(buf, n); + return true; + }, + // Multipart + [&](const MultipartFormData &file) { + if (file_count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { + return false; + } + cur = req.files.emplace(file.name, file); + return true; + }, + [&](const char *buf, size_t n) { + auto &content = cur->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + return true; + })) { + const auto &content_type = req.get_header_value("Content-Type"); + if (!content_type.find("application/x-www-form-urlencoded")) { + if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) { + res.status = 413; // NOTE: should be 414? + return false; + } + detail::parse_query_text(req.body, req.params); + } + return true; + } + return false; +} + +inline bool Server::read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), + std::move(multipart_receiver)); +} + +inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + detail::MultipartFormDataParser multipart_form_data_parser; + ContentReceiverWithProgress out; + + if (req.is_multipart_form_data()) { + const auto &content_type = req.get_header_value("Content-Type"); + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary)) { + res.status = 400; + return false; + } + + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { + /* For debug + size_t pos = 0; + while (pos < n) { + auto read_size = (std::min)(1, n - pos); + auto ret = multipart_form_data_parser.parse( + buf + pos, read_size, multipart_receiver, multipart_header); + if (!ret) { return false; } + pos += read_size; + } + return true; + */ + return multipart_form_data_parser.parse(buf, n, multipart_receiver, + multipart_header); + }; + } else { + out = [receiver](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { return receiver(buf, n); }; + } + + if (req.method == "DELETE" && !req.has_header("Content-Length")) { + return true; + } + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) { + return false; + } + + if (req.is_multipart_form_data()) { + if (!multipart_form_data_parser.is_valid()) { + res.status = 400; + return false; + } + } + + return true; +} + +inline bool Server::handle_file_request(const Request &req, Response &res, + bool head) { + for (const auto &entry : base_dirs_) { + // Prefix match + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); + if (detail::is_valid_path(sub_path)) { + auto path = entry.base_dir + sub_path; + if (path.back() == '/') { path += "index.html"; } + + if (detail::is_file(path)) { + detail::read_file(path, res.body); + auto type = + detail::find_content_type(path, file_extension_and_mimetype_map_); + if (type) { res.set_header("Content-Type", type); } + for (const auto &kv : entry.headers) { + res.set_header(kv.first.c_str(), kv.second); + } + res.status = req.has_header("Range") ? 206 : 200; + if (!head && file_request_handler_) { + file_request_handler_(req, res); + } + return true; + } + } + } + } + return false; +} + +inline socket_t +Server::create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const { + return detail::create_socket( + host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, + std::move(socket_options), + [](socket_t sock, struct addrinfo &ai) -> bool { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + return false; + } + if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; } + return true; + }); +} + +inline int Server::bind_internal(const std::string &host, int port, + int socket_flags) { + if (!is_valid()) { return -1; } + + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); + if (svr_sock_ == INVALID_SOCKET) { return -1; } + + if (port == 0) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) { + return -1; + } + if (addr.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return -1; + } + } else { + return port; + } +} + +inline bool Server::listen_internal() { + auto ret = true; + is_running_ = true; + auto se = detail::scope_exit([&]() { is_running_ = false; }); + + { + std::unique_ptr task_queue(new_task_queue()); + + while (svr_sock_ != INVALID_SOCKET) { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); + if (val == 0) { // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 + } +#endif + socket_t sock = accept(svr_sock_, nullptr, nullptr); + + if (sock == INVALID_SOCKET) { + if (errno == EMFILE) { + // The per-process limit of open file descriptors has been reached. + // Try to accept new connections after a short sleep. + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } else if (errno == EINTR || errno == EAGAIN) { + continue; + } + if (svr_sock_ != INVALID_SOCKET) { + detail::close_socket(svr_sock_); + ret = false; + } else { + ; // The server socket was closed by user. + } + break; + } + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec_ * 1000 + + read_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec_); + tv.tv_usec = static_cast(read_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec_ * 1000 + + write_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, + sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec_); + tv.tv_usec = static_cast(write_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); +#endif + } + + task_queue->enqueue([this, sock]() { process_and_close_socket(sock); }); + } + + task_queue->shutdown(); + } + + return ret; +} + +inline bool Server::routing(Request &req, Response &res, Stream &strm) { + if (pre_routing_handler_ && + pre_routing_handler_(req, res) == HandlerResponse::Handled) { + return true; + } + + // File handler + bool is_head_request = req.method == "HEAD"; + if ((req.method == "GET" || is_head_request) && + handle_file_request(req, res, is_head_request)) { + return true; + } + + if (detail::expect_content(req)) { + // Content reader handler + { + ContentReader reader( + [&](ContentReceiver receiver) { + return read_content_with_content_receiver( + strm, req, res, std::move(receiver), nullptr, nullptr); + }, + [&](MultipartContentHeader header, ContentReceiver receiver) { + return read_content_with_content_receiver(strm, req, res, nullptr, + std::move(header), + std::move(receiver)); + }); + + if (req.method == "POST") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + post_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PUT") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + put_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PATCH") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "DELETE") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { + return true; + } + } + } + + // Read content into `req.body` + if (!read_content(strm, req, res)) { return false; } + } + + // Regular handler + if (req.method == "GET" || req.method == "HEAD") { + return dispatch_request(req, res, get_handlers_); + } else if (req.method == "POST") { + return dispatch_request(req, res, post_handlers_); + } else if (req.method == "PUT") { + return dispatch_request(req, res, put_handlers_); + } else if (req.method == "DELETE") { + return dispatch_request(req, res, delete_handlers_); + } else if (req.method == "OPTIONS") { + return dispatch_request(req, res, options_handlers_); + } else if (req.method == "PATCH") { + return dispatch_request(req, res, patch_handlers_); + } + + res.status = 400; + return false; +} + +inline bool Server::dispatch_request(Request &req, Response &res, + const Handlers &handlers) { + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; + + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res); + return true; + } + } + return false; +} + +inline void Server::apply_ranges(const Request &req, Response &res, + std::string &content_type, + std::string &boundary) { + if (req.ranges.size() > 1) { + boundary = detail::make_multipart_data_boundary(); + + auto it = res.headers.find("Content-Type"); + if (it != res.headers.end()) { + content_type = it->second; + res.headers.erase(it); + } + + res.headers.emplace("Content-Type", + "multipart/byteranges; boundary=" + boundary); + } + + auto type = detail::encoding_type(req, res); + + if (res.body.empty()) { + if (res.content_length_ > 0) { + size_t length = 0; + if (req.ranges.empty()) { + length = res.content_length_; + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offset = offsets.first; + length = offsets.second; + auto content_range = detail::make_content_range_header_field( + offset, length, res.content_length_); + res.set_header("Content-Range", content_range); + } else { + length = detail::get_multipart_ranges_data_length(req, res, boundary, + content_type); + } + res.set_header("Content-Length", std::to_string(length)); + } else { + if (res.content_provider_) { + if (res.is_chunked_content_provider_) { + res.set_header("Transfer-Encoding", "chunked"); + if (type == detail::EncodingType::Gzip) { + res.set_header("Content-Encoding", "gzip"); + } else if (type == detail::EncodingType::Brotli) { + res.set_header("Content-Encoding", "br"); + } + } + } + } + } else { + if (req.ranges.empty()) { + ; + } else if (req.ranges.size() == 1) { + auto offsets = + detail::get_range_offset_and_length(req, res.body.size(), 0); + auto offset = offsets.first; + auto length = offsets.second; + auto content_range = detail::make_content_range_header_field( + offset, length, res.body.size()); + res.set_header("Content-Range", content_range); + if (offset < res.body.size()) { + res.body = res.body.substr(offset, length); + } else { + res.body.clear(); + res.status = 416; + } + } else { + std::string data; + if (detail::make_multipart_ranges_data(req, res, boundary, content_type, + data)) { + res.body.swap(data); + } else { + res.body.clear(); + res.status = 416; + } + } + + if (type != detail::EncodingType::None) { + std::unique_ptr compressor; + std::string content_encoding; + + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); + content_encoding = "gzip"; +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); + content_encoding = "br"; +#endif + } + + if (compressor) { + std::string compressed; + if (compressor->compress(res.body.data(), res.body.size(), true, + [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { + res.body.swap(compressed); + res.set_header("Content-Encoding", content_encoding); + } + } + } + + auto length = std::to_string(res.body.size()); + res.set_header("Content-Length", length); + } +} + +inline bool Server::dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) { + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; + + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res, content_reader); + return true; + } + } + return false; +} + +inline bool +Server::process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + // Connection has been closed on client + if (!line_reader.getline()) { return false; } + + Request req; + Response res; + + res.version = "HTTP/1.1"; + + for (const auto &header : default_headers_) { + if (res.headers.find(header.first) == res.headers.end()) { + res.headers.insert(header); + } + } + +#ifdef _WIN32 + // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL). +#else +#ifndef CPPHTTPLIB_USE_POLL + // Socket file descriptor exceeded FD_SETSIZE... + if (strm.socket() >= FD_SETSIZE) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 500; + return write_response(strm, close_connection, req, res); + } +#endif +#endif + + // Check if the request URI doesn't exceed the limit + if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = 414; + return write_response(strm, close_connection, req, res); + } + + // Request line and headers + if (!parse_request_line(line_reader.ptr(), req) || + !detail::read_headers(strm, req.headers)) { + res.status = 400; + return write_response(strm, close_connection, req, res); + } + + if (req.get_header_value("Connection") == "close") { + connection_closed = true; + } + + if (req.version == "HTTP/1.0" && + req.get_header_value("Connection") != "Keep-Alive") { + connection_closed = true; + } + + strm.get_remote_ip_and_port(req.remote_addr, req.remote_port); + req.set_header("REMOTE_ADDR", req.remote_addr); + req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); + + strm.get_local_ip_and_port(req.local_addr, req.local_port); + req.set_header("LOCAL_ADDR", req.local_addr); + req.set_header("LOCAL_PORT", std::to_string(req.local_port)); + + if (req.has_header("Range")) { + const auto &range_header_value = req.get_header_value("Range"); + if (!detail::parse_range_header(range_header_value, req.ranges)) { + res.status = 416; + return write_response(strm, close_connection, req, res); + } + } + + if (setup_request) { setup_request(req); } + + if (req.get_header_value("Expect") == "100-continue") { + auto status = 100; + if (expect_100_continue_handler_) { + status = expect_100_continue_handler_(req, res); + } + switch (status) { + case 100: + case 417: + strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status, + detail::status_message(status)); + break; + default: return write_response(strm, close_connection, req, res); + } + } + + // Rounting + bool routed = false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS + routed = routing(req, res, strm); +#else + try { + routed = routing(req, res, strm); + } catch (std::exception &e) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = 500; + std::string val; + auto s = e.what(); + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case '\r': val += "\\r"; break; + case '\n': val += "\\n"; break; + default: val += s[i]; break; + } + } + res.set_header("EXCEPTION_WHAT", val); + } + } catch (...) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = 500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); + } + } +#endif + + if (routed) { + if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; } + return write_response_with_content(strm, close_connection, req, res); + } else { + if (res.status == -1) { res.status = 404; } + return write_response(strm, close_connection, req, res); + } +} + +inline bool Server::is_valid() const { return true; } + +inline bool Server::process_and_close_socket(socket_t sock) { + auto ret = detail::process_server_socket( + svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + nullptr); + }); + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// HTTP client implementation +inline ClientImpl::ClientImpl(const std::string &host) + : ClientImpl(host, 80, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port) + : ClientImpl(host, port, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : host_(host), port_(port), + host_and_port_(adjust_host_string(host) + ":" + std::to_string(port)), + client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} + +inline ClientImpl::~ClientImpl() { + std::lock_guard guard(socket_mutex_); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline bool ClientImpl::is_valid() const { return true; } + +inline void ClientImpl::copy_settings(const ClientImpl &rhs) { + client_cert_path_ = rhs.client_cert_path_; + client_key_path_ = rhs.client_key_path_; + connection_timeout_sec_ = rhs.connection_timeout_sec_; + read_timeout_sec_ = rhs.read_timeout_sec_; + read_timeout_usec_ = rhs.read_timeout_usec_; + write_timeout_sec_ = rhs.write_timeout_sec_; + write_timeout_usec_ = rhs.write_timeout_usec_; + basic_auth_username_ = rhs.basic_auth_username_; + basic_auth_password_ = rhs.basic_auth_password_; + bearer_token_auth_token_ = rhs.bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + digest_auth_username_ = rhs.digest_auth_username_; + digest_auth_password_ = rhs.digest_auth_password_; +#endif + keep_alive_ = rhs.keep_alive_; + follow_location_ = rhs.follow_location_; + url_encode_ = rhs.url_encode_; + address_family_ = rhs.address_family_; + tcp_nodelay_ = rhs.tcp_nodelay_; + socket_options_ = rhs.socket_options_; + compress_ = rhs.compress_; + decompress_ = rhs.decompress_; + interface_ = rhs.interface_; + proxy_host_ = rhs.proxy_host_; + proxy_port_ = rhs.proxy_port_; + proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; + proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; + proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; + proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + ca_cert_file_path_ = rhs.ca_cert_file_path_; + ca_cert_dir_path_ = rhs.ca_cert_dir_path_; + ca_cert_store_ = rhs.ca_cert_store_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + server_certificate_verification_ = rhs.server_certificate_verification_; +#endif + logger_ = rhs.logger_; +} + +inline socket_t ClientImpl::create_client_socket(Error &error) const { + if (!proxy_host_.empty() && proxy_port_ != -1) { + return detail::create_client_socket( + proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, interface_, error); + } + + // Check is custom IP specified for host_ + std::string ip; + auto it = addr_map_.find(host_); + if (it != addr_map_.end()) ip = it->second; + + return detail::create_client_socket( + host_, ip, port_, address_family_, tcp_nodelay_, socket_options_, + connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_, + read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_, + error); +} + +inline bool ClientImpl::create_and_connect_socket(Socket &socket, + Error &error) { + auto sock = create_client_socket(error); + if (sock == INVALID_SOCKET) { return false; } + socket.sock = sock; + return true; +} + +inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, + bool /*shutdown_gracefully*/) { + // If there are any requests in flight from threads other than us, then it's + // a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); +} + +inline void ClientImpl::shutdown_socket(Socket &socket) { + if (socket.sock == INVALID_SOCKET) { return; } + detail::shutdown_socket(socket.sock); +} + +inline void ClientImpl::close_socket(Socket &socket) { + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + assert(socket.ssl == nullptr); +#endif + if (socket.sock == INVALID_SOCKET) { return; } + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; +} + +inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, + Response &res) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + if (!line_reader.getline()) { return false; } + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); +#else + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); +#endif + + std::cmatch m; + if (!std::regex_match(line_reader.ptr(), m, re)) { + return req.method == "CONNECT"; + } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + + // Ignore '100 Continue' + while (res.status == 100) { + if (!line_reader.getline()) { return false; } // CRLF + if (!line_reader.getline()) { return false; } // next response line + + if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + } + + return true; +} + +inline bool ClientImpl::send(Request &req, Response &res, Error &error) { + std::lock_guard request_mutex_guard(request_mutex_); + auto ret = send_(req, res, error); + if (error == Error::SSLPeerCouldBeClosed_) { + assert(!ret); + ret = send_(req, res, error); + } + return ret; +} + +inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { + { + std::lock_guard guard(socket_mutex_); + + // Set this to false immediately - if it ever gets set to true by the end of + // the request, we know another thread instructed us to close the socket. + socket_should_be_closed_when_request_is_done_ = false; + + auto is_alive = false; + if (socket_.is_open()) { + is_alive = detail::is_socket_alive(socket_.sock); + if (!is_alive) { + // Attempt to avoid sigpipe by shutting down nongracefully if it seems + // like the other side has already closed the connection Also, there + // cannot be any requests in flight from other threads since we locked + // request_mutex_, so safe to close everything immediately + const bool shutdown_gracefully = false; + shutdown_ssl(socket_, shutdown_gracefully); + shutdown_socket(socket_); + close_socket(socket_); + } + } + + if (!is_alive) { + if (!create_and_connect_socket(socket_, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // TODO: refactoring + if (is_ssl()) { + auto &scli = static_cast(*this); + if (!proxy_host_.empty() && proxy_port_ != -1) { + auto success = false; + if (!scli.connect_with_proxy(socket_, res, success, error)) { + return success; + } + } + + if (!scli.initialize_ssl(socket_, error)) { return false; } + } +#endif + } + + // Mark the current socket as being in use so that it cannot be closed by + // anyone else while this request is ongoing, even though we will be + // releasing the mutex. + if (socket_requests_in_flight_ > 1) { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); + } + + for (const auto &header : default_headers_) { + if (req.headers.find(header.first) == req.headers.end()) { + req.headers.insert(header); + } + } + + auto ret = false; + auto close_connection = !keep_alive_; + + auto se = detail::scope_exit([&]() { + // Briefly lock mutex in order to mark that a request is no longer ongoing + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || close_connection || + !ret) { + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + }); + + ret = process_socket(socket_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection, error); + }); + + if (!ret) { + if (error == Error::Success) { error = Error::Unknown; } + } + + return ret; +} + +inline Result ClientImpl::send(const Request &req) { + auto req2 = req; + return send_(std::move(req2)); +} + +inline Result ClientImpl::send_(Request &&req) { + auto res = detail::make_unique(); + auto error = Error::Success; + auto ret = send(req, *res, error); + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; +} + +inline bool ClientImpl::handle_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + if (req.path.empty()) { + error = Error::Connection; + return false; + } + + auto req_save = req; + + bool ret; + + if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) { + auto req2 = req; + req2.path = "http://" + host_and_port_ + req.path; + ret = process_request(strm, req2, res, close_connection, error); + req = req2; + req.path = req_save.path; + } else { + ret = process_request(strm, req, res, close_connection, error); + } + + if (!ret) { return false; } + + if (300 < res.status && res.status < 400 && follow_location_) { + req = req_save; + ret = redirect(req, res, error); + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if ((res.status == 401 || res.status == 407) && + req.authorization_count_ < 5) { + auto is_proxy = res.status == 407; + const auto &username = + is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; + const auto &password = + is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; + + if (!username.empty() && !password.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res, auth, is_proxy)) { + Request new_req = req; + new_req.authorization_count_ += 1; + new_req.headers.erase(is_proxy ? "Proxy-Authorization" + : "Authorization"); + new_req.headers.insert(detail::make_digest_authentication_header( + req, auth, new_req.authorization_count_, detail::random_string(10), + username, password, is_proxy)); + + Response new_res; + + ret = send(new_req, new_res, error); + if (ret) { res = new_res; } + } + } + } +#endif + + return ret; +} + +inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { + if (req.redirect_count_ == 0) { + error = Error::ExceedRedirectCount; + return false; + } + + auto location = res.get_header_value("location"); + if (location.empty()) { return false; } + + const static std::regex re( + R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)"); + + std::smatch m; + if (!std::regex_match(location, m, re)) { return false; } + + auto scheme = is_ssl() ? "https" : "http"; + + auto next_scheme = m[1].str(); + auto next_host = m[2].str(); + if (next_host.empty()) { next_host = m[3].str(); } + auto port_str = m[4].str(); + auto next_path = m[5].str(); + auto next_query = m[6].str(); + + auto next_port = port_; + if (!port_str.empty()) { + next_port = std::stoi(port_str); + } else if (!next_scheme.empty()) { + next_port = next_scheme == "https" ? 443 : 80; + } + + if (next_scheme.empty()) { next_scheme = scheme; } + if (next_host.empty()) { next_host = host_; } + if (next_path.empty()) { next_path = "/"; } + + auto path = detail::decode_url(next_path, true) + next_query; + + if (next_scheme == scheme && next_host == host_ && next_port == port_) { + return detail::redirect(*this, req, res, path, location, error); + } else { + if (next_scheme == "https") { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSLClient cli(next_host.c_str(), next_port); + cli.copy_settings(*this); + if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); } + return detail::redirect(cli, req, res, path, location, error); +#else + return false; +#endif + } else { + ClientImpl cli(next_host.c_str(), next_port); + cli.copy_settings(*this); + return detail::redirect(cli, req, res, path, location, error); + } + } +} + +inline bool ClientImpl::write_content_with_provider(Stream &strm, + const Request &req, + Error &error) { + auto is_shutting_down = []() { return false; }; + + if (req.is_chunked_content_provider_) { + // TODO: Brotli support + std::unique_ptr compressor; +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { + compressor = detail::make_unique(); + } else +#endif + { + compressor = detail::make_unique(); + } + + return detail::write_content_chunked(strm, req.content_provider_, + is_shutting_down, *compressor, error); + } else { + return detail::write_content(strm, req.content_provider_, 0, + req.content_length_, is_shutting_down, error); + } +} + +inline bool ClientImpl::write_request(Stream &strm, Request &req, + bool close_connection, Error &error) { + // Prepare additional headers + if (close_connection) { + if (!req.has_header("Connection")) { + req.headers.emplace("Connection", "close"); + } + } + + if (!req.has_header("Host")) { + if (is_ssl()) { + if (port_ == 443) { + req.headers.emplace("Host", host_); + } else { + req.headers.emplace("Host", host_and_port_); + } + } else { + if (port_ == 80) { + req.headers.emplace("Host", host_); + } else { + req.headers.emplace("Host", host_and_port_); + } + } + } + + if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); } + +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT + if (!req.has_header("User-Agent")) { + auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; + req.headers.emplace("User-Agent", agent); + } +#endif + + if (req.body.empty()) { + if (req.content_provider_) { + if (!req.is_chunked_content_provider_) { + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.content_length_); + req.headers.emplace("Content-Length", length); + } + } + } else { + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") { + req.headers.emplace("Content-Length", "0"); + } + } + } else { + if (!req.has_header("Content-Type")) { + req.headers.emplace("Content-Type", "text/plain"); + } + + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.body.size()); + req.headers.emplace("Content-Length", length); + } + } + + if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_basic_authentication_header( + basic_auth_username_, basic_auth_password_, false)); + } + } + + if (!proxy_basic_auth_username_.empty() && + !proxy_basic_auth_password_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_basic_authentication_header( + proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + } + } + + if (!bearer_token_auth_token_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); + } + } + + if (!proxy_bearer_token_auth_token_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); + } + } + + // Request line and headers + { + detail::BufferStream bstrm; + + const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path; + bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); + + detail::write_headers(bstrm, req.headers); + + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { + error = Error::Write; + return false; + } + } + + // Body + if (req.body.empty()) { + return write_content_with_provider(strm, req, error); + } + + if (!detail::write_data(strm, req.body.data(), req.body.size())) { + error = Error::Write; + return false; + } + + return true; +} + +inline std::unique_ptr ClientImpl::send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error) { + if (!content_type.empty()) { + req.headers.emplace("Content-Type", content_type); + } + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { req.headers.emplace("Content-Encoding", "gzip"); } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_ && !content_provider_without_length) { + // TODO: Brotli support + detail::gzip_compressor compressor; + + if (content_provider) { + auto ok = true; + size_t offset = 0; + DataSink data_sink; + + data_sink.write = [&](const char *data, size_t data_len) -> bool { + if (ok) { + auto last = offset + data_len == content_length; + + auto ret = compressor.compress( + data, data_len, last, + [&](const char *compressed_data, size_t compressed_data_len) { + req.body.append(compressed_data, compressed_data_len); + return true; + }); + + if (ret) { + offset += data_len; + } else { + ok = false; + } + } + return ok; + }; + + while (ok && offset < content_length) { + if (!content_provider(offset, content_length - offset, data_sink)) { + error = Error::Canceled; + return nullptr; + } + } + } else { + if (!compressor.compress(body, content_length, true, + [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + })) { + error = Error::Compression; + return nullptr; + } + } + } else +#endif + { + if (content_provider) { + req.content_length_ = content_length; + req.content_provider_ = std::move(content_provider); + req.is_chunked_content_provider_ = false; + } else if (content_provider_without_length) { + req.content_length_ = 0; + req.content_provider_ = detail::ContentProviderAdapter( + std::move(content_provider_without_length)); + req.is_chunked_content_provider_ = true; + req.headers.emplace("Transfer-Encoding", "chunked"); + } else { + req.body.assign(body, content_length); + ; + } + } + + auto res = detail::make_unique(); + return send(req, *res, error) ? std::move(res) : nullptr; +} + +inline Result ClientImpl::send_with_content_provider( + 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) { + Request req; + req.method = method; + req.headers = headers; + req.path = path; + + auto error = Error::Success; + + auto res = send_with_content_provider( + req, body, content_length, std::move(content_provider), + std::move(content_provider_without_length), content_type, error); + + return Result{std::move(res), error, std::move(req.headers)}; +} + +inline std::string +ClientImpl::adjust_host_string(const std::string &host) const { + if (host.find(':') != std::string::npos) { return "[" + host + "]"; } + return host; +} + +inline bool ClientImpl::process_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + // Send request + if (!write_request(strm, req, close_connection, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_ssl()) { + auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1; + if (!is_proxy_enabled) { + char buf[1]; + if (SSL_peek(socket_.ssl, buf, 1) == 0 && + SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) { + error = Error::SSLPeerCouldBeClosed_; + return false; + } + } + } +#endif + + // Receive response and headers + if (!read_response_line(strm, req, res) || + !detail::read_headers(strm, res.headers)) { + error = Error::Read; + return false; + } + + // Body + if ((res.status != 204) && req.method != "HEAD" && req.method != "CONNECT") { + auto redirect = 300 < res.status && res.status < 400 && follow_location_; + + if (req.response_handler && !redirect) { + if (!req.response_handler(res)) { + error = Error::Canceled; + return false; + } + } + + auto out = + req.content_receiver + ? static_cast( + [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + if (redirect) { return true; } + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { error = Error::Canceled; } + return ret; + }) + : static_cast( + [&](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { + if (res.body.size() + n > res.body.max_size()) { + return false; + } + res.body.append(buf, n); + return true; + }); + + auto progress = [&](uint64_t current, uint64_t total) { + if (!req.progress || redirect) { return true; } + auto ret = req.progress(current, total); + if (!ret) { error = Error::Canceled; } + return ret; + }; + + int dummy_status; + if (!detail::read_content(strm, res, (std::numeric_limits::max)(), + dummy_status, std::move(progress), std::move(out), + decompress_)) { + if (error != Error::Canceled) { error = Error::Read; } + return false; + } + } + + if (res.get_header_value("Connection") == "close" || + (res.version == "HTTP/1.0" && res.reason != "Connection established")) { + // TODO this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. Maybe a code refactor (such as moving this out to + // the send function and getting rid of the recursiveness of the mutex) + // could make this more obvious. + + // This is safe to call because process_request is only called by + // handle_request which is only called by send, which locks the request + // mutex during the process. It would be a bug to call it from a different + // thread since it's a thread-safety issue to do these things to the socket + // if another thread is using the socket. + std::lock_guard guard(socket_mutex_); + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + + // Log + if (logger_) { logger_(req, res); } + + return true; +} + +inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + size_t cur_item = 0, cur_start = 0; + // cur_item and cur_start are copied to within the std::function and maintain + // state between successive calls + return [&, cur_item, cur_start](size_t offset, + DataSink &sink) mutable -> bool { + if (!offset && items.size()) { + sink.os << detail::serialize_multipart_formdata(items, boundary, false); + return true; + } else if (cur_item < provider_items.size()) { + if (!cur_start) { + const auto &begin = detail::serialize_multipart_formdata_item_begin( + provider_items[cur_item], boundary); + offset += begin.size(); + cur_start = offset; + sink.os << begin; + } + + DataSink cur_sink; + bool has_data = true; + cur_sink.write = sink.write; + cur_sink.done = [&]() { has_data = false; }; + + if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) + return false; + + if (!has_data) { + sink.os << detail::serialize_multipart_formdata_item_end(); + cur_item++; + cur_start = 0; + } + return true; + } else { + sink.os << detail::serialize_multipart_formdata_finish(boundary); + sink.done(); + return true; + } + }; +} + +inline bool +ClientImpl::process_socket(const Socket &socket, + std::function callback) { + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, std::move(callback)); +} + +inline bool ClientImpl::is_ssl() const { return false; } + +inline Result ClientImpl::Get(const std::string &path) { + return Get(path, Headers(), Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, Progress progress) { + return Get(path, Headers(), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers) { + return Get(path, headers, Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver) { + return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return Get(path, headers, nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, headers, nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.response_handler = std::move(response_handler); + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + uint64_t /*offset*/, uint64_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + if (params.empty()) { return Get(path, headers); } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query.c_str(), headers, progress); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, params, headers, nullptr, content_receiver, progress); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + if (params.empty()) { + return Get(path, headers, response_handler, content_receiver, progress); + } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query.c_str(), headers, response_handler, + content_receiver, progress); +} + +inline Result ClientImpl::Head(const std::string &path) { + return Head(path, Headers()); +} + +inline Result ClientImpl::Head(const std::string &path, + const Headers &headers) { + Request req; + req.method = "HEAD"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline Result ClientImpl::Post(const std::string &path) { + return Post(path, std::string(), std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers) { + return Post(path, headers, nullptr, 0, std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Post(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return Post(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) { + return Post(path, Headers(), params); +} + +inline Result ClientImpl::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Post(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Post(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Post(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Post(const std::string &path, + const MultipartFormDataItems &items) { + return Post(path, Headers(), items); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type.c_str()); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type.c_str()); +} + +inline Result +ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "POST", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path) { + return Put(path, std::string(), std::string()); +} + +inline Result ClientImpl::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Put(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return Put(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Put(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { + return Put(path, Headers(), params); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Put(const std::string &path, + const MultipartFormDataItems &items) { + return Put(path, Headers(), items); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result +ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PUT", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} +inline Result ClientImpl::Patch(const std::string &path) { + return Patch(path, std::string(), std::string()); +} + +inline Result ClientImpl::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Patch(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body, + content_length, nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Patch(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Patch(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path) { + return Delete(path, Headers(), std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers) { + return Delete(path, headers, std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Delete(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const char *body, + size_t content_length, + const std::string &content_type) { + Request req; + req.method = "DELETE"; + req.headers = headers; + req.path = path; + + if (!content_type.empty()) { + req.headers.emplace("Content-Type", content_type); + } + req.body.assign(body, content_length); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Delete(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Delete(path, Headers(), body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type) { + return Delete(path, headers, body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Options(const std::string &path) { + return Options(path, Headers()); +} + +inline Result ClientImpl::Options(const std::string &path, + const Headers &headers) { + Request req; + req.method = "OPTIONS"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline size_t ClientImpl::is_socket_open() const { + std::lock_guard guard(socket_mutex_); + return socket_.is_open(); +} + +inline socket_t ClientImpl::socket() const { return socket_.sock; } + +inline void ClientImpl::stop() { + std::lock_guard guard(socket_mutex_); + + // If there is anything ongoing right now, the ONLY thread-safe thing we can + // do is to shutdown_socket, so that threads using this socket suddenly + // discover they can't read/write any more and error out. Everything else + // (closing the socket, shutting ssl down) is unsafe because these actions are + // not thread-safe. + if (socket_requests_in_flight_ > 0) { + shutdown_socket(socket_); + + // Aside from that, we set a flag for the socket to be closed when we're + // done. + socket_should_be_closed_when_request_is_done_ = true; + return; + } + + // Otherwise, still holding the mutex, we can shut everything down ourselves + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; +} + +inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; +} + +inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; +} + +inline void ClientImpl::set_basic_auth(const std::string &username, + const std::string &password) { + basic_auth_username_ = username; + basic_auth_password_ = password; +} + +inline void ClientImpl::set_bearer_token_auth(const std::string &token) { + bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_digest_auth(const std::string &username, + const std::string &password) { + digest_auth_username_ = username; + digest_auth_password_ = password; +} +#endif + +inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } + +inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } + +inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; } + +inline void +ClientImpl::set_hostname_addr_map(std::map addr_map) { + addr_map_ = std::move(addr_map); +} + +inline void ClientImpl::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); +} + +inline void ClientImpl::set_address_family(int family) { + address_family_ = family; +} + +inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } + +inline void ClientImpl::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); +} + +inline void ClientImpl::set_compress(bool on) { compress_ = on; } + +inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } + +inline void ClientImpl::set_interface(const std::string &intf) { + interface_ = intf; +} + +inline void ClientImpl::set_proxy(const std::string &host, int port) { + proxy_host_ = host; + proxy_port_ = port; +} + +inline void ClientImpl::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + proxy_basic_auth_username_ = username; + proxy_basic_auth_password_ = password; +} + +inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) { + proxy_bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + proxy_digest_auth_username_ = username; + proxy_digest_auth_password_ = password; +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + ca_cert_file_path_ = ca_cert_file_path; + ca_cert_dir_path_ = ca_cert_dir_path; +} + +inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store && ca_cert_store != ca_cert_store_) { + ca_cert_store_ = ca_cert_store; + } +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::enable_server_certificate_verification(bool enabled) { + server_certificate_verification_ = enabled; +} +#endif + +inline void ClientImpl::set_logger(Logger logger) { + logger_ = std::move(logger); +} + +/* + * SSL Implementation + */ +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +namespace detail { + +template +inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, + U SSL_connect_or_accept, V setup) { + SSL *ssl = nullptr; + { + std::lock_guard guard(ctx_mutex); + ssl = SSL_new(ctx); + } + + if (ssl) { + set_nonblocking(sock, true); + auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + BIO_set_nbio(bio, 1); + SSL_set_bio(ssl, bio, bio); + + if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { + SSL_shutdown(ssl); + { + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); + } + set_nonblocking(sock, false); + return nullptr; + } + BIO_set_nbio(bio, 0); + set_nonblocking(sock, false); + } + + return ssl; +} + +inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, + bool shutdown_gracefully) { + // sometimes we may want to skip this to try to avoid SIGPIPE if we know + // the remote has closed the network connection + // Note that it is not always possible to avoid SIGPIPE, this is merely a + // best-efforts. + if (shutdown_gracefully) { SSL_shutdown(ssl); } + + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); +} + +template +bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, + U ssl_connect_or_accept, + time_t timeout_sec, + time_t timeout_usec) { + int res = 0; + while ((res = ssl_connect_or_accept(ssl)) != 1) { + auto err = SSL_get_error(ssl, res); + switch (err) { + case SSL_ERROR_WANT_READ: + if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + case SSL_ERROR_WANT_WRITE: + if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + default: break; + } + return false; + } + return true; +} + +template +inline bool process_server_socket_ssl( + const std::atomic &svr_sock, SSL *ssl, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +template +inline bool +process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +class SSLInit { +public: + SSLInit() { + OPENSSL_init_ssl( + OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); + } +}; + +// SSL socket stream implementation +inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec) { + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); +} + +inline SSLSocketStream::~SSLSocketStream() {} + +inline bool SSLSocketStream::is_readable() const { + return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SSLSocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + auto ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + int n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_READ || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_READ) { +#endif + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { + if (is_writable()) { + auto handle_size = static_cast( + std::min(size, (std::numeric_limits::max)())); + + auto ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + int n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) { +#endif + if (is_writable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SSLSocketStream::socket() const { return sock_; } + +static SSLInit sslinit_; + +} // namespace detail + +// SSL HTTP server implementation +inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path, + const char *client_ca_cert_dir_path, + const char *private_key_password) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + + // add default password callback before opening encrypted private key + if (private_key_password != nullptr && (private_key_password[0] != '\0')) { + SSL_CTX_set_default_passwd_cb_userdata(ctx_, + (char *)private_key_password); + } + + if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != + 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { + SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, + client_ca_cert_dir_path); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_store) { + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer( + const std::function &setup_ssl_ctx_callback) { + ctx_ = SSL_CTX_new(TLS_method()); + if (ctx_) { + if (!setup_ssl_ctx_callback(*ctx_)) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLServer::~SSLServer() { + if (ctx_) { SSL_CTX_free(ctx_); } +} + +inline bool SSLServer::is_valid() const { return ctx_; } + +inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; } + +inline bool SSLServer::process_and_close_socket(socket_t sock) { + auto ssl = detail::ssl_new( + sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + return detail::ssl_connect_or_accept_nonblocking( + sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_); + }, + [](SSL * /*ssl2*/) { return true; }); + + auto ret = false; + if (ssl) { + ret = detail::process_server_socket_ssl( + svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this, ssl](Stream &strm, bool close_connection, + bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + [&](Request &req) { req.ssl = ssl; }); + }); + + // Shutdown gracefully if the result seemed successful, non-gracefully if + // the connection appeared to be closed. + const bool shutdown_gracefully = ret; + detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully); + } + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// SSL HTTP client implementation +inline SSLClient::SSLClient(const std::string &host) + : SSLClient(host, 443, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port) + : SSLClient(host, port, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : ClientImpl(host, port, client_cert_path, client_key_path) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + + if (!client_cert_path.empty() && !client_key_path.empty()) { + if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), + SSL_FILETYPE_PEM) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), + SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::SSLClient(const std::string &host, int port, + X509 *client_cert, EVP_PKEY *client_key) + : ClientImpl(host, port) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + + if (client_cert != nullptr && client_key != nullptr) { + if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::~SSLClient() { + if (ctx_) { SSL_CTX_free(ctx_); } + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + shutdown_ssl_impl(socket_, true); +} + +inline bool SSLClient::is_valid() const { return ctx_; } + +inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store) { + if (ctx_) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx_, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } +} + +inline long SSLClient::get_openssl_verify_result() const { + return verify_result_; +} + +inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } + +inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { + return is_valid() && ClientImpl::create_and_connect_socket(socket, error); +} + +// Assumes that socket_mutex_ is locked and that there are no requests in flight +inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, + bool &success, Error &error) { + success = true; + Response res2; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req2; + req2.method = "CONNECT"; + req2.path = host_and_port_; + return process_request(strm, req2, res2, false, error); + })) { + // Thread-safe to close everything because we are assuming there are no + // requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + + if (res2.status == 407) { + if (!proxy_digest_auth_username_.empty() && + !proxy_digest_auth_password_.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res2, auth, true)) { + Response res3; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req3; + req3.method = "CONNECT"; + req3.path = host_and_port_; + req3.headers.insert(detail::make_digest_authentication_header( + req3, auth, 1, detail::random_string(10), + proxy_digest_auth_username_, proxy_digest_auth_password_, + true)); + return process_request(strm, req3, res3, false, error); + })) { + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + } + } else { + res = res2; + return false; + } + } + + return true; +} + +inline bool SSLClient::load_certs() { + bool ret = true; + + std::call_once(initialize_cert_, [&]() { + std::lock_guard guard(ctx_mutex_); + if (!ca_cert_file_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), + nullptr)) { + ret = false; + } + } else if (!ca_cert_dir_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, nullptr, + ca_cert_dir_path_.c_str())) { + ret = false; + } + } else { + auto loaded = false; +#ifdef _WIN32 + loaded = + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX + loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); +#endif // TARGET_OS_OSX +#endif // _WIN32 + if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } + } + }); + + return ret; +} + +inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { + auto ssl = detail::ssl_new( + socket.sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + if (server_certificate_verification_) { + if (!load_certs()) { + error = Error::SSLLoadingCerts; + return false; + } + SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr); + } + + if (!detail::ssl_connect_or_accept_nonblocking( + socket.sock, ssl2, SSL_connect, connection_timeout_sec_, + connection_timeout_usec_)) { + error = Error::SSLConnection; + return false; + } + + if (server_certificate_verification_) { + verify_result_ = SSL_get_verify_result(ssl2); + + if (verify_result_ != X509_V_OK) { + error = Error::SSLServerVerification; + return false; + } + + auto server_cert = SSL_get1_peer_certificate(ssl2); + + if (server_cert == nullptr) { + error = Error::SSLServerVerification; + return false; + } + + if (!verify_host(server_cert)) { + X509_free(server_cert); + error = Error::SSLServerVerification; + return false; + } + X509_free(server_cert); + } + + return true; + }, + [&](SSL *ssl2) { + SSL_set_tlsext_host_name(ssl2, host_.c_str()); + return true; + }); + + if (ssl) { + socket.ssl = ssl; + return true; + } + + shutdown_socket(socket); + close_socket(socket); + return false; +} + +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + shutdown_ssl_impl(socket, shutdown_gracefully); +} + +inline void SSLClient::shutdown_ssl_impl(Socket &socket, + bool shutdown_gracefully) { + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; + } + if (socket.ssl) { + detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully); + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); +} + +inline bool +SSLClient::process_socket(const Socket &socket, + std::function callback) { + assert(socket.ssl); + return detail::process_client_socket_ssl( + socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, std::move(callback)); +} + +inline bool SSLClient::is_ssl() const { return true; } + +inline bool SSLClient::verify_host(X509 *server_cert) const { + /* Quote from RFC2818 section 3.1 "Server Identity" + + If a subjectAltName extension of type dNSName is present, that MUST + be used as the identity. Otherwise, the (most specific) Common Name + field in the Subject field of the certificate MUST be used. Although + the use of the Common Name is existing practice, it is deprecated and + Certification Authorities are encouraged to use the dNSName instead. + + Matching is performed using the matching rules specified by + [RFC2459]. If more than one identity of a given type is present in + the certificate (e.g., more than one dNSName name, a match in any one + of the set is considered acceptable.) Names may contain the wildcard + character * which is considered to match any single domain name + component or component fragment. E.g., *.a.com matches foo.a.com but + not bar.foo.a.com. f*.com matches foo.com but not bar.com. + + In some cases, the URI is specified as an IP address rather than a + hostname. In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI. + + */ + return verify_host_with_subject_alt_name(server_cert) || + verify_host_with_common_name(server_cert); +} + +inline bool +SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { + auto ret = false; + + auto type = GEN_DNS; + + struct in6_addr addr6; + struct in_addr addr; + size_t addr_len = 0; + +#ifndef __MINGW32__ + if (inet_pton(AF_INET6, host_.c_str(), &addr6)) { + type = GEN_IPADD; + addr_len = sizeof(struct in6_addr); + } else if (inet_pton(AF_INET, host_.c_str(), &addr)) { + type = GEN_IPADD; + addr_len = sizeof(struct in_addr); + } +#endif + + auto alt_names = static_cast( + X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr)); + + if (alt_names) { + auto dsn_matched = false; + auto ip_matched = false; + + auto count = sk_GENERAL_NAME_num(alt_names); + + for (decltype(count) i = 0; i < count && !dsn_matched; i++) { + auto val = sk_GENERAL_NAME_value(alt_names, i); + if (val->type == type) { + auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5); + auto name_len = (size_t)ASN1_STRING_length(val->d.ia5); + + switch (type) { + case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; + + case GEN_IPADD: + if (!memcmp(&addr6, name, addr_len) || + !memcmp(&addr, name, addr_len)) { + ip_matched = true; + } + break; + } + } + } + + if (dsn_matched || ip_matched) { ret = true; } + } + + GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names); + return ret; +} + +inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { + const auto subject_name = X509_get_subject_name(server_cert); + + if (subject_name != nullptr) { + char name[BUFSIZ]; + auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, + name, sizeof(name)); + + if (name_len != -1) { + return check_host_name(name, static_cast(name_len)); + } + } + + return false; +} + +inline bool SSLClient::check_host_name(const char *pattern, + size_t pattern_len) const { + if (host_.size() == pattern_len && host_ == pattern) { return true; } + + // Wildcard match + // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 + std::vector pattern_components; + detail::split(&pattern[0], &pattern[pattern_len], '.', + [&](const char *b, const char *e) { + pattern_components.emplace_back(std::string(b, e)); + }); + + if (host_components_.size() != pattern_components.size()) { return false; } + + auto itr = pattern_components.begin(); + for (const auto &h : host_components_) { + auto &p = *itr; + if (p != h && p != "*") { + auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' && + !p.compare(0, p.size() - 1, h)); + if (!partial_match) { return false; } + } + ++itr; + } + + return true; +} +#endif + +// Universal client implementation +inline Client::Client(const std::string &scheme_host_port) + : Client(scheme_host_port, std::string(), std::string()) {} + +inline Client::Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path) { + const static std::regex re( + R"((?:([a-z]+):\/\/)?(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)"); + + std::smatch m; + if (std::regex_match(scheme_host_port, m, re)) { + auto scheme = m[1].str(); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!scheme.empty() && (scheme != "http" && scheme != "https")) { +#else + if (!scheme.empty() && scheme != "http") { +#endif +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); +#endif + return; + } + + auto is_ssl = scheme == "https"; + + auto host = m[2].str(); + if (host.empty()) { host = m[3].str(); } + + auto port_str = m[4].str(); + auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); + + if (is_ssl) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + is_ssl_ = is_ssl; +#endif + } else { + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + } + } else { + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); + } +} + +inline Client::Client(const std::string &host, int port) + : cli_(detail::make_unique(host, port)) {} + +inline Client::Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) {} + +inline Client::~Client() {} + +inline bool Client::is_valid() const { + return cli_ != nullptr && cli_->is_valid(); +} + +inline Result Client::Get(const std::string &path) { return cli_->Get(path); } +inline Result Client::Get(const std::string &path, const Headers &headers) { + return cli_->Get(path, headers); +} +inline Result Client::Get(const std::string &path, Progress progress) { + return cli_->Get(path, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + Progress progress) { + return cli_->Get(path, headers, std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(content_receiver), + std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + return cli_->Get(path, params, headers, progress); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, content_receiver, progress); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, response_handler, content_receiver, + progress); +} + +inline Result Client::Head(const std::string &path) { return cli_->Head(path); } +inline Result Client::Head(const std::string &path, const Headers &headers) { + return cli_->Head(path, headers); +} + +inline Result Client::Post(const std::string &path) { return cli_->Post(path); } +inline Result Client::Post(const std::string &path, const Headers &headers) { + return cli_->Post(path, headers); +} +inline Result Client::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Post(path, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Post(path, body, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_type); +} +inline Result Client::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Params ¶ms) { + return cli_->Post(path, params); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Post(path, headers, params); +} +inline Result Client::Post(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Post(path, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Post(path, headers, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Post(path, headers, items, boundary); +} +inline Result +Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Post(path, headers, items, provider_items); +} +inline Result Client::Put(const std::string &path) { return cli_->Put(path); } +inline Result Client::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Put(path, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Put(path, body, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_type); +} +inline Result Client::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Params ¶ms) { + return cli_->Put(path, params); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Put(path, headers, params); +} +inline Result Client::Put(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Put(path, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Put(path, headers, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Put(path, headers, items, boundary); +} +inline Result +Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Put(path, headers, items, provider_items); +} +inline Result Client::Patch(const std::string &path) { + return cli_->Patch(path); +} +inline Result Client::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, body, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_type); +} +inline Result Client::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, std::move(content_provider), content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Delete(const std::string &path) { + return cli_->Delete(path); +} +inline Result Client::Delete(const std::string &path, const Headers &headers) { + return cli_->Delete(path, headers); +} +inline Result Client::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, body, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_type); +} +inline Result Client::Options(const std::string &path) { + return cli_->Options(path); +} +inline Result Client::Options(const std::string &path, const Headers &headers) { + return cli_->Options(path, headers); +} + +inline bool Client::send(Request &req, Response &res, Error &error) { + return cli_->send(req, res, error); +} + +inline Result Client::send(const Request &req) { return cli_->send(req); } + +inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } + +inline socket_t Client::socket() const { return cli_->socket(); } + +inline void Client::stop() { cli_->stop(); } + +inline void +Client::set_hostname_addr_map(std::map addr_map) { + cli_->set_hostname_addr_map(std::move(addr_map)); +} + +inline void Client::set_default_headers(Headers headers) { + cli_->set_default_headers(std::move(headers)); +} + +inline void Client::set_address_family(int family) { + cli_->set_address_family(family); +} + +inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } + +inline void Client::set_socket_options(SocketOptions socket_options) { + cli_->set_socket_options(std::move(socket_options)); +} + +inline void Client::set_connection_timeout(time_t sec, time_t usec) { + cli_->set_connection_timeout(sec, usec); +} + +inline void Client::set_read_timeout(time_t sec, time_t usec) { + cli_->set_read_timeout(sec, usec); +} + +inline void Client::set_write_timeout(time_t sec, time_t usec) { + cli_->set_write_timeout(sec, usec); +} + +inline void Client::set_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_basic_auth(username, password); +} +inline void Client::set_bearer_token_auth(const std::string &token) { + cli_->set_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_digest_auth(username, password); +} +#endif + +inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); } +inline void Client::set_follow_location(bool on) { + cli_->set_follow_location(on); +} + +inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); } + +inline void Client::set_compress(bool on) { cli_->set_compress(on); } + +inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } + +inline void Client::set_interface(const std::string &intf) { + cli_->set_interface(intf); +} + +inline void Client::set_proxy(const std::string &host, int port) { + cli_->set_proxy(host, port); +} +inline void Client::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_basic_auth(username, password); +} +inline void Client::set_proxy_bearer_token_auth(const std::string &token) { + cli_->set_proxy_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_digest_auth(username, password); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::enable_server_certificate_verification(bool enabled) { + cli_->enable_server_certificate_verification(enabled); +} +#endif + +inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path); +} + +inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } else { + cli_->set_ca_cert_store(ca_cert_store); + } +} + +inline long Client::get_openssl_verify_result() const { + if (is_ssl_) { + return static_cast(*cli_).get_openssl_verify_result(); + } + return -1; // NOTE: -1 doesn't match any of X509_V_ERR_??? +} + +inline SSL_CTX *Client::ssl_context() const { + if (is_ssl_) { return static_cast(*cli_).ssl_context(); } + return nullptr; +} +#endif + +// ---------------------------------------------------------------------------- + +} // namespace httplib + +#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL) +#undef poll +#endif + +#endif // CPPHTTPLIB_HTTPLIB_H diff --git a/ApCreate/NetraLib/src/NetRequest.cpp b/ApCreate/NetraLib/src/NetRequest.cpp new file mode 100644 index 0000000..f3aa541 --- /dev/null +++ b/ApCreate/NetraLib/src/NetRequest.cpp @@ -0,0 +1,635 @@ +#include "NetRequest.hpp" + +#include +#include +#include +#include +#include +#include + +namespace ntq +{ + namespace + { + static std::string joinPath(const std::string &base, const std::string &path) + { + if (base.empty()) return path.empty() || path[0] == '/' ? path : std::string("/") + path; + if (path.empty()) return base[0] == '/' ? base : std::string("/") + base; + bool base_has = base.front() == '/'; + bool base_end = base.back() == '/'; + bool path_has = path.front() == '/'; + std::string b = base_has ? base : std::string("/") + base; + if (base_end && path_has) return b + path.substr(1); + if (!base_end && !path_has) return b + "/" + path; + return b + path; + } + + static std::string paramsToQuery(const httplib::Params ¶ms) + { + if (params.empty()) return {}; + std::string s; + bool first = true; + for (auto &kv : params) + { + if (!first) s += '&'; + first = false; + s += kv.first; + s += '='; + s += kv.second; + } + return s; + } + + static httplib::Headers mergeHeaders(const httplib::Headers &a, const httplib::Headers &b) + { + httplib::Headers h = a; + for (auto &kv : b) + { + // 覆盖同名 header:先删再插 + h.erase(kv.first); + h.emplace(kv.first, kv.second); + } + return h; + } + } + + class ConcurrencyGate + { + public: + explicit ConcurrencyGate(size_t limit) : limit_(limit), active_(0) {} + + void set_limit(size_t limit) + { + std::lock_guard lk(mtx_); + limit_ = limit > 0 ? limit : 1; + cv_.notify_all(); + } + + struct Guard + { + ConcurrencyGate &g; + explicit Guard(ConcurrencyGate &gate) : g(gate) { g.enter(); } + ~Guard() { g.leave(); } + }; + + private: + friend struct Guard; + void enter() + { + std::unique_lock lk(mtx_); + cv_.wait(lk, [&]{ return active_ < limit_; }); + ++active_; + } + void leave() + { + std::lock_guard lk(mtx_); + if (active_ > 0) --active_; + cv_.notify_one(); + } + + size_t limit_; + size_t active_; + std::mutex mtx_; + std::condition_variable cv_; + }; + + struct NetRequest::Impl + { + RequestOptions opts; + LogCallback logger; + Stats stats; + + // 并发控制 + ConcurrencyGate gate{4}; + + // 缓存 + struct CacheEntry + { + HttpResponse resp; + std::chrono::steady_clock::time_point expiry; + }; + bool cache_enabled = false; + std::chrono::milliseconds cache_ttl{0}; + std::unordered_map cache; + std::mutex cache_mtx; + + void log(const std::string &msg) + { + if (logger) logger(msg); + } + + template + void apply_client_options(ClientT &cli) + { + const time_t c_sec = static_cast(opts.connect_timeout_ms / 1000); + const time_t c_usec = static_cast((opts.connect_timeout_ms % 1000) * 1000); + const time_t r_sec = static_cast(opts.read_timeout_ms / 1000); + const time_t r_usec = static_cast((opts.read_timeout_ms % 1000) * 1000); + const time_t w_sec = static_cast(opts.write_timeout_ms / 1000); + const time_t w_usec = static_cast((opts.write_timeout_ms % 1000) * 1000); + cli.set_connection_timeout(c_sec, c_usec); + cli.set_read_timeout(r_sec, r_usec); + cli.set_write_timeout(w_sec, w_usec); + cli.set_keep_alive(opts.keep_alive); + } + + std::string build_full_path(const std::string &path) const + { + return joinPath(opts.base_path, path); + } + + std::string cache_key(const std::string &path, const httplib::Params ¶ms, const httplib::Headers &headers) + { + std::ostringstream oss; + oss << opts.scheme << "://" << opts.host << ':' << opts.port << build_full_path(path); + if (!params.empty()) oss << '?' << paramsToQuery(params); + for (auto &kv : headers) oss << '|' << kv.first << '=' << kv.second; + return oss.str(); + } + + void record_latency(double ms) + { + stats.last_latency_ms = ms; + const double alpha = 0.2; + if (stats.avg_latency_ms <= 0.0) stats.avg_latency_ms = ms; + else stats.avg_latency_ms = alpha * ms + (1.0 - alpha) * stats.avg_latency_ms; + } + + static ErrorCode map_error() + { + // 简化:无法区分具体错误码,统一归为 Network + return ErrorCode::Network; + } + }; + + NetRequest::NetRequest(const RequestOptions &options) + : impl_(new Impl) + { + impl_->opts = options; + if (impl_->opts.scheme == "https" && impl_->opts.port == 80) impl_->opts.port = 443; + if (impl_->opts.scheme == "http" && impl_->opts.port == 0) impl_->opts.port = 80; + } + + NetRequest::~NetRequest() + { + delete impl_; + } + + void NetRequest::setLogger(LogCallback logger) + { + impl_->logger = std::move(logger); + } + + void NetRequest::setMaxConcurrentRequests(size_t n) + { + impl_->gate.set_limit(n > 0 ? n : 1); + } + + void NetRequest::enableCache(std::chrono::milliseconds ttl) + { + impl_->cache_enabled = true; + impl_->cache_ttl = ttl.count() > 0 ? ttl : std::chrono::milliseconds(1000); + } + + void NetRequest::disableCache() + { + impl_->cache_enabled = false; + std::lock_guard lk(impl_->cache_mtx); + impl_->cache.clear(); + } + + ntq::optional NetRequest::Get(const std::string &path, + const httplib::Params &query, + const httplib::Headers &headers, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + if (impl_->cache_enabled) + { + std::string key = impl_->cache_key(path, query, mergeHeaders(impl_->opts.default_headers, headers)); + std::lock_guard lk(impl_->cache_mtx); + auto it = impl_->cache.find(key); + if (it != impl_->cache.end() && std::chrono::steady_clock::now() < it->second.expiry) + { + if (err) *err = ErrorCode::None; + auto resp = it->second.resp; + resp.from_cache = true; + return resp; + } + } + + ntq::optional result; + ErrorCode local_err = ErrorCode::None; + + const auto full_path = impl_->build_full_path(path); + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = query.empty() ? cli.Get(full_path.c_str(), merged_headers) + : cli.Get(full_path.c_str(), query, merged_headers); + if (res) + { + HttpResponse r; + r.status = res->status; + r.body = res->body; + r.headers = res->headers; + r.from_cache = false; + result = r; + } + else + { + local_err = Impl::map_error(); + } +#else + impl_->log("HTTPS requested but OpenSSL is not enabled; falling back to error."); + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = query.empty() ? cli.Get(full_path.c_str(), merged_headers) + : cli.Get(full_path.c_str(), query, merged_headers); + if (res) + { + HttpResponse r; + r.status = res->status; + r.body = res->body; + r.headers = res->headers; + r.from_cache = false; + result = r; + } + else + { + local_err = Impl::map_error(); + } + } + + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + + if (!result.has_value()) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return ntq::nullopt; + } + + if (impl_->cache_enabled) + { + std::string key = impl_->cache_key(path, query, merged_headers); + std::lock_guard lk(impl_->cache_mtx); + impl_->cache[key] = Impl::CacheEntry{*result, std::chrono::steady_clock::now() + impl_->cache_ttl}; + } + if (err) *err = ErrorCode::None; + return result; + } + + ntq::optional NetRequest::PostJson(const std::string &path, + const std::string &json, + const httplib::Headers &headers, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + ntq::optional result; + ErrorCode local_err = ErrorCode::None; + + const auto full_path = impl_->build_full_path(path); + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, json, "application/json"); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } +#else + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, json, "application/json"); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } + } + + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + if (!result) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return ntq::nullopt; + } + if (err) *err = ErrorCode::None; + return result; + } + + ntq::optional NetRequest::PostForm(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + ntq::optional result; + ErrorCode local_err = ErrorCode::None; + + const auto full_path = impl_->build_full_path(path); + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, form); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } +#else + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Post(full_path.c_str(), merged_headers, form); + if (res) + { + HttpResponse r{res->status, res->body, res->headers, false}; + result = r; + } + else + { + local_err = Impl::map_error(); + } + } + + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + if (!result) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return ntq::nullopt; + } + if (err) *err = ErrorCode::None; + return result; + } + + std::future> NetRequest::GetAsync(const std::string &path, + const httplib::Params &query, + const httplib::Headers &headers, + ErrorCode *err) + { + return std::async(std::launch::async, [this, path, query, headers, err]() mutable { + ErrorCode local; + auto r = Get(path, query, headers, &local); + if (err) *err = local; + return r; + }); + } + + std::future> NetRequest::PostJsonAsync(const std::string &path, + const std::string &json, + const httplib::Headers &headers, + ErrorCode *err) + { + return std::async(std::launch::async, [this, path, json, headers, err]() mutable { + ErrorCode local; + auto r = PostJson(path, json, headers, &local); + if (err) *err = local; + return r; + }); + } + + std::future> NetRequest::PostFormAsync(const std::string &path, + const httplib::Params &form, + const httplib::Headers &headers, + ErrorCode *err) + { + return std::async(std::launch::async, [this, path, form, headers, err]() mutable { + ErrorCode local; + auto r = PostForm(path, form, headers, &local); + if (err) *err = local; + return r; + }); + } + + bool NetRequest::DownloadToFile(const std::string &path, + const std::string &local_file, + const httplib::Headers &headers, + bool resume, + size_t /*chunk_size*/, + ErrorCode *err) + { + ConcurrencyGate::Guard guard(impl_->gate); + impl_->stats.total_requests++; + auto start = std::chrono::steady_clock::now(); + + std::ios_base::openmode mode = std::ios::binary | std::ios::out; + size_t offset = 0; + if (resume) + { + std::ifstream in(local_file, std::ios::binary | std::ios::ate); + if (in) + { + offset = static_cast(in.tellg()); + } + mode |= std::ios::app; + } + else + { + mode |= std::ios::trunc; + } + + std::ofstream out(local_file, mode); + if (!out) + { + if (err) *err = ErrorCode::IOError; + impl_->stats.total_errors++; + return false; + } + + auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers); + if (resume && offset > 0) + { + merged_headers.emplace("Range", "bytes=" + std::to_string(offset) + "-"); + } + + const auto full_path = impl_->build_full_path(path); + int status_code = 0; + ErrorCode local_err = ErrorCode::None; + + auto content_receiver = [&](const char *data, size_t data_length) { + out.write(data, static_cast(data_length)); + return static_cast(out); + }; + + bool ok = false; + if (impl_->opts.scheme == "https") + { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Get(full_path.c_str(), merged_headers, content_receiver); + if (res) + { + status_code = res->status; + ok = (status_code == 200 || status_code == 206); + } + else + { + local_err = Impl::map_error(); + } +#else + local_err = ErrorCode::SSL; +#endif + } + else + { + httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port); + impl_->apply_client_options(cli); + auto res = cli.Get(full_path.c_str(), merged_headers, content_receiver); + if (res) + { + status_code = res->status; + ok = (status_code == 200 || status_code == 206); + } + else + { + local_err = Impl::map_error(); + } + } + + out.close(); + auto end = std::chrono::steady_clock::now(); + impl_->record_latency(std::chrono::duration(end - start).count()); + + if (!ok) + { + impl_->stats.total_errors++; + if (err) *err = local_err; + return false; + } + if (err) *err = ErrorCode::None; + return true; + } + + NetRequest::Stats NetRequest::getStats() const + { + return impl_->stats; + } + + // ------------------------- Quick helpers ------------------------- + namespace { + struct ParsedURL { + std::string scheme; + std::string host; + int port = 0; + std::string path_and_query; + bool ok = false; + }; + + static ParsedURL parse_url(const std::string &url) + { + ParsedURL p; p.ok = false; + // very small parser: scheme://host[:port]/path[?query] + auto pos_scheme = url.find("://"); + if (pos_scheme == std::string::npos) return p; + p.scheme = url.substr(0, pos_scheme); + size_t pos_host = pos_scheme + 3; + + size_t pos_path = url.find('/', pos_host); + std::string hostport = pos_path == std::string::npos ? url.substr(pos_host) + : url.substr(pos_host, pos_path - pos_host); + auto pos_colon = hostport.find(':'); + if (pos_colon == std::string::npos) { + p.host = hostport; + p.port = (p.scheme == "https") ? 443 : 80; + } else { + p.host = hostport.substr(0, pos_colon); + std::string port_str = hostport.substr(pos_colon + 1); + p.port = port_str.empty() ? ((p.scheme == "https") ? 443 : 80) : std::atoi(port_str.c_str()); + } + p.path_and_query = (pos_path == std::string::npos) ? "/" : url.substr(pos_path); + p.ok = !p.host.empty(); + return p; + } + } + + ntq::optional NetRequest::QuickGet(const std::string &url, + const httplib::Headers &headers, + ErrorCode *err) + { + auto p = parse_url(url); + if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; } + RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port; + NetRequest req(opt); + return req.Get(p.path_and_query, {}, headers, err); + } + + ntq::optional NetRequest::QuickPostJson(const std::string &url, + const std::string &json, + const httplib::Headers &headers, + ErrorCode *err) + { + auto p = parse_url(url); + if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; } + RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port; + NetRequest req(opt); + return req.PostJson(p.path_and_query, json, headers, err); + } + + ntq::optional NetRequest::QuickPostForm(const std::string &url, + const httplib::Params &form, + const httplib::Headers &headers, + ErrorCode *err) + { + auto p = parse_url(url); + if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; } + RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port; + NetRequest req(opt); + return req.PostForm(p.path_and_query, form, headers, err); + } +} diff --git a/ApCreate/NetraLib/src/Netra.cpp b/ApCreate/NetraLib/src/Netra.cpp new file mode 100644 index 0000000..3160759 --- /dev/null +++ b/ApCreate/NetraLib/src/Netra.cpp @@ -0,0 +1,664 @@ +#include "Netra.hpp" + +namespace QCL +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TcpServer::TcpServer(int port) + : port_(port), running_(false), serverSock_(-1) {} + + /** + * @brief 析构函数中调用stop()确保服务器资源被释放 + */ + TcpServer::~TcpServer() + { + stop(); + } + + /** + * @brief 启动服务器: + * 1. 创建监听socket(TCP) + * 2. 绑定端口 + * 3. 监听端口 + * 4. 启动监听线程acceptThread_ + * + * @return 成功返回true,失败返回false + */ + bool TcpServer::start() + { + // 创建socket + serverSock_ = socket(AF_INET, SOCK_STREAM, 0); + if (serverSock_ < 0) + { + std::cerr << "Socket 创建失败\n"; + return false; + } + + // 设置socket地址结构 + sockaddr_in serverAddr; + std::memset(&serverAddr, 0, sizeof(serverAddr)); + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(port_); // 端口转网络字节序 + serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡IP + + // 允许端口重用,防止服务器异常关闭后端口被占用 + int opt = 1; + setsockopt(serverSock_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + // 绑定端口 + if (bind(serverSock_, (sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) + { + std::cerr << "绑定失败\n"; + return false; + } + + // 开始监听,最大等待连接数为5 + if (listen(serverSock_, 5) < 0) + { + std::cerr << "监听失败\n"; + return false; + } + + // 设置运行标志为true + running_ = true; + + // 启动专门接受客户端连接的线程 + acceptThread_ = std::thread(&TcpServer::acceptClients, this); + + std::cout << "服务器启动,监听端口:" << port_ << std::endl; + return true; + } + + /** + * @brief 停止服务器: + * 1. 设置运行标志为false,通知线程退出 + * 2. 关闭监听socket + * 3. 关闭所有客户端socket,清理客户端列表 + * 4. 等待所有线程退出 + */ + void TcpServer::stop() + { + running_ = false; + + if (serverSock_ >= 0) + { + close(serverSock_); + serverSock_ = -1; + } + + { + // 线程安全关闭所有客户端socket + std::lock_guard lock(clientsMutex_); + for (int sock : clientSockets_) + { + close(sock); + } + clientSockets_.clear(); + } + + // 等待监听线程退出 + if (acceptThread_.joinable()) + acceptThread_.join(); + + // 等待所有客户端处理线程退出 + for (auto &t : clientThreads_) + { + if (t.joinable()) + t.join(); + } + + std::cout << "服务器已停止\n"; + } + + /** + * @brief acceptClients函数循环监听客户端连接请求 + * 每当accept成功: + * 1. 打印客户端IP和Socket信息 + * 2. 线程安全地将客户端Socket加入clientSockets_列表 + * 3. 创建新线程调用handleClient处理该客户端收发 + */ + void TcpServer::acceptClients() + { + while (running_) + { + sockaddr_in clientAddr; + socklen_t clientLen = sizeof(clientAddr); + int clientSock = accept(serverSock_, (sockaddr *)&clientAddr, &clientLen); + if (clientSock < 0) + { + if (running_) + std::cerr << "接受连接失败\n"; + continue; + } + + // 将客户端IP转换成字符串格式打印 + char clientIP[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(clientAddr.sin_addr), clientIP, INET_ADDRSTRLEN); + std::cout << "客户端连接,IP: " << clientIP << ", Socket: " << clientSock << std::endl; + + { + // 加锁保护共享的clientSockets_容器 + std::lock_guard lock(clientsMutex_); + clientSockets_.push_back(clientSock); + } + } + } + + /** + * @brief 发送消息给指定客户端 + * @param clientSock 客户端socket + * @param message 发送消息内容 + */ + void TcpServer::sendToClient(int clientSock, const std::string &message) + { + send(clientSock, message.c_str(), message.size(), 0); + } + + /** + * @brief 单次接收指定客户端数据 + * @param clientSock 客户端socket + */ + std::string TcpServer::receiveFromClient(int clientSock, bool flag) + { + char buffer[1024]; + std::memset(buffer, 0, sizeof(buffer)); + + int flags = flag ? 0 : MSG_DONTWAIT; + ssize_t bytesReceived = recv(clientSock, buffer, sizeof(buffer) - 1, flags); + + if (bytesReceived <= 0) + return {}; + + return std::string(buffer, bytesReceived); + } + + /** + * @brief 获取当前所有客户端Socket副本(线程安全) + * @return 包含所有客户端socket的vector副本 + */ + std::vector TcpServer::getClientSockets() + { + std::lock_guard lock(clientsMutex_); + return clientSockets_; + } + + /** + * @brief 获取连接客户端的IP和端口 + * @param clientSock 客户端Socket描述符 + */ + char *TcpServer::getClientIPAndPort(int clientSock) + { + struct sockaddr_in addr; + socklen_t addr_size = sizeof(addr); + + // 获取客户端地址信息 + if (getpeername(clientSock, (struct sockaddr *)&addr, &addr_size) == -1) + { + perror("getpeername failed"); + return NULL; + } + + // 分配内存存储结果(格式: "IP:PORT") + char *result = (char *)malloc(INET_ADDRSTRLEN + 10); + if (!result) + return NULL; + + // 转换IP和端口 + char *ip = inet_ntoa(addr.sin_addr); + unsigned short port = ntohs(addr.sin_port); + + snprintf(result, INET_ADDRSTRLEN + 10, "%s:%d", ip, port); + return result; + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + WriteFile::WriteFile(const std::string &filePath) + : filePath_(filePath) {} + + /** + * @brief 覆盖写文本(线程安全) + */ + bool WriteFile::overwriteText(const std::string &content) + { + std::lock_guard lock(writeMutex_); // 加锁 + return writeToFile(content, std::ios::out | std::ios::trunc); + } + + /** + * @brief 追加写文本(线程安全) + */ + bool WriteFile::appendText(const std::string &content) + { + std::lock_guard lock(writeMutex_); + return writeToFile(content, std::ios::out | std::ios::app); + } + + /** + * @brief 覆盖写二进制(线程安全) + */ + bool WriteFile::overwriteBinary(const std::vector &data) + { + std::lock_guard lock(writeMutex_); + return writeBinary(data, std::ios::out | std::ios::trunc | std::ios::binary); + } + + /** + * @brief 追加写二进制(线程安全) + */ + bool WriteFile::appendBinary(const std::vector &data) + { + std::lock_guard lock(writeMutex_); + return writeBinary(data, std::ios::out | std::ios::app | std::ios::binary); + } + + /** + * @brief 通用文本写入(私有) + */ + bool WriteFile::writeToFile(const std::string &content, std::ios::openmode mode) + { + std::ofstream file(filePath_, mode); + if (!file.is_open()) + return false; + file << content; + file.close(); + return true; + } + + /** + * @brief 通用二进制写入(私有) + */ + bool WriteFile::writeBinary(const std::vector &data, std::ios::openmode mode) + { + std::ofstream file(filePath_, mode); + if (!file.is_open()) + return false; + file.write(data.data(), data.size()); + file.close(); + return true; + } + + size_t WriteFile::countBytesPattern(const std::string &pattern, bool includePattern) + { + std::lock_guard lock(writeMutex_); + + if (pattern.empty()) + return 0; + + std::ifstream file(filePath_, std::ios::binary); + if (!file.is_open()) + return 0; + + const size_t chunkSize = 4096; + std::string buffer; + buffer.reserve(chunkSize * 2); + + size_t totalRead = 0; + char chunk[chunkSize]; + + while (file.read(chunk, chunkSize) || file.gcount() > 0) + { + size_t bytesRead = file.gcount(); + buffer.append(chunk, bytesRead); + + size_t pos = buffer.find(pattern); + if (pos != std::string::npos) + { + size_t absolutePos = totalRead + pos; // 关键:加上 totalRead + return includePattern ? (absolutePos + pattern.size()) : absolutePos; + } + + if (buffer.size() > pattern.size()) + buffer.erase(0, buffer.size() - pattern.size()); + + totalRead += bytesRead; // 读完后再累计 + } + + return 0; + } + + bool WriteFile::writeAfterPatternOrAppend(const std::string &pattern, const std::string &content) + { + std::lock_guard lock(writeMutex_); + + // 读取整个文件 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(in)), {}); + in.close(); + + size_t pos = fileData.find(pattern); + if (pos != std::string::npos) + { + // 模式存在,插入位置在模式结尾 + pos += pattern.size(); + + // 删除模式后所有内容 + if (pos < fileData.size()) + fileData.erase(pos); + + // 插入新内容 + fileData.insert(pos, content); + } + else + { + // 模式不存在,直接追加到文件末尾 + if (!fileData.empty() && fileData.back() != '\n') + fileData += '\n'; // 保证换行 + fileData += content; + } + + // 写回文件 + std::ofstream out(filePath_, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + return false; + + out.write(fileData.data(), fileData.size()); + return true; + } + + bool WriteFile::overwriteAtPos(const std::string &content, size_t pos, size_t length) + { + std::lock_guard lock(writeMutex_); + + // 打开文件读取 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(in)), {}); + in.close(); + + // 边界检查 + if (pos >= fileData.size()) + return false; // pos 超过文件范围,无法覆盖 + + // 生成要覆盖的实际数据块 + std::string overwriteBlock; + if (content.size() >= length) + { + overwriteBlock = content.substr(0, length); + } + else + { + overwriteBlock = content; + overwriteBlock.append(length - content.size(), '\0'); // 补齐 + } + + // 计算实际可写范围 + size_t maxWritable = std::min(length, fileData.size() - pos); + + // 覆盖 + fileData.replace(pos, maxWritable, overwriteBlock.substr(0, maxWritable)); + + // 写回文件 + std::ofstream out(filePath_, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + return false; + + out.write(fileData.data(), fileData.size()); + return true; + } + + bool WriteFile::insertAfterPos(const std::string &content, size_t pos, size_t length) + { + std::lock_guard lock(writeMutex_); + + // 打开文件读取 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(in)), {}); + in.close(); + + // 边界检查 + if (pos > fileData.size()) + pos = fileData.size(); // 如果 pos 超出范围,就视为文件末尾 + + // 生成要插入的实际数据块 + std::string insertBlock; + if (content.size() >= length) + { + insertBlock = content.substr(0, length); // 只取前 length 个字节 + } + else + { + insertBlock = content; // 全部内容 + insertBlock.append(length - content.size(), '\0'); // 补足空字节 + } + + // 插入到 pos 后面 + fileData.insert(pos + 1, insertBlock); + + // 写回文件 + std::ofstream out(filePath_, std::ios::binary | std::ios::trunc); + if (!out.is_open()) + return false; + + out.write(fileData.data(), fileData.size()); + return true; + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ReadFile::ReadFile(const std::string &filename) : filename_(filename) {} + + ReadFile::~ReadFile() + { + std::lock_guard lock(mtx_); + Close(); + } + + bool ReadFile::Open() + { + std::lock_guard lock(mtx_); + if (file_.is_open()) + file_.close(); + file_.open(filename_, std::ios::in | std::ios::binary); + return file_.is_open(); + } + + void ReadFile::Close() + { + if (file_.is_open()) + { + std::lock_guard lock(mtx_); + file_.close(); + } + } + + bool ReadFile::IsOpen() const + { + std::lock_guard lock(mtx_); + return file_.is_open(); + } + + std::string ReadFile::ReadAllText() + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return ""; + + std::ostringstream ss; + ss << file_.rdbuf(); + return ss.str(); + } + + std::vector ReadFile::ReadAllBinary() + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return {}; + + return ReadBytes(GetFileSize()); + } + + std::vector ReadFile::ReadLines() + { + // std::lock_guard lock(mtx_); + // if (!file_.is_open() && !Open()) + // return {}; + + // std::vector lines; + // std::string line; + // while (std::getline(file_, line)) + // { + // lines.push_back(line); + // } + // return lines; + + std::lock_guard lock(mtx_); + if (!file_.is_open()) { + file_.open(filename_, std::ios::in | std::ios::binary); + if (!file_.is_open()) return {}; + } + file_.clear(); + file_.seekg(0, std::ios::beg); + + std::vector lines; + std::string line; + while (std::getline(file_, line)) lines.push_back(line); + return lines; + } + + std::vector ReadFile::ReadBytes(size_t count) + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return {}; + + std::vector buffer(count); + file_.read(buffer.data(), count); + buffer.resize(file_.gcount()); + return buffer; + } + + size_t ReadFile::GetBytesBefore(const std::string &marker, bool includeMarker) + { + std::lock_guard lock(mtx_); + + if (!file_.is_open() && !Open()) + return 0; + + file_.clear(); // 清除EOF和错误状态 + file_.seekg(0, std::ios::beg); // 回到文件开头 + + const size_t chunkSize = 4096; + std::string buffer; + buffer.reserve(chunkSize * 2); + + size_t totalRead = 0; + char chunk[chunkSize]; + + while (file_.read(chunk, chunkSize) || file_.gcount() > 0) + { + buffer.append(chunk, file_.gcount()); + size_t pos = buffer.find(marker); + if (pos != std::string::npos) + { + // 如果 includeMarker 为 true,返回包含 marker 的长度 + if (includeMarker) + return pos + marker.size(); + else + return pos; + } + + // 保留末尾部分,避免 buffer 无限增长 + if (buffer.size() > marker.size()) + buffer.erase(0, buffer.size() - marker.size()); + + totalRead += file_.gcount(); + } + + return 0; + } + + std::vector ReadFile::ReadBytesFrom(size_t pos, size_t count) + { + std::lock_guard lock(mtx_); + + if (!file_.is_open() && !Open()) + return {}; + + size_t filesize = GetFileSize(); + if (pos >= filesize) + return {}; // 起始位置超出文件大小 + + file_.clear(); // 清除 EOF 和错误状态 + file_.seekg(pos, std::ios::beg); + if (!file_) + return {}; + + size_t bytes_to_read = count; + if (count == 0 || pos + count > filesize) + bytes_to_read = filesize - pos; // 读取到文件末尾 + + std::vector buffer(bytes_to_read); + file_.read(buffer.data(), bytes_to_read); + + // 实际读取的字节数可能少于请求的数量 + buffer.resize(file_.gcount()); + + return buffer; + } + + bool ReadFile::FileExists() const + { + return std::filesystem::exists(filename_); + } + + size_t ReadFile::GetFileSize() const + { + if (!FileExists()) + return 0; + return std::filesystem::file_size(filename_); + } + + void ReadFile::Reset() + { + std::lock_guard lock(mtx_); + if (file_.is_open()) + { + file_.clear(); + file_.seekg(0, std::ios::beg); + } + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // 屏蔽所有信号 + void blockAllSignals() + { + // 忽略全部的信号 + for (int ii = 1; ii <= 64; ii++) + signal(ii, SIG_IGN); + } + + std::string Ltrim(const std::string &s) + { + size_t start = 0; + while (start < s.size() && std::isspace(static_cast(s[start]))) + { + ++start; + } + return s.substr(start); + } + + std::string Rtrim(const std::string &s) + { + if (s.empty()) + return s; + + size_t end = s.size(); + while (end > 0 && std::isspace(static_cast(s[end - 1]))) + { + --end; + } + return s.substr(0, end); + } + + std::string LRtrim(const std::string &s) + { + return Ltrim(Rtrim(s)); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +} \ No newline at end of file diff --git a/ApCreate/NetraLib/src/encrypt.cpp b/ApCreate/NetraLib/src/encrypt.cpp new file mode 100644 index 0000000..a8aea93 --- /dev/null +++ b/ApCreate/NetraLib/src/encrypt.cpp @@ -0,0 +1,91 @@ +#include "encrypt.hpp" +#include + +namespace encrypt +{ + string MD5(const string &info) + { + auto leftrotate = [](uint32_t x, uint32_t c) -> uint32_t { return (x << c) | (x >> (32 - c)); }; + + static const uint32_t s[64] = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 + }; + static const uint32_t K[64] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 + }; + + uint32_t a0 = 0x67452301; + uint32_t b0 = 0xefcdab89; + uint32_t c0 = 0x98badcfe; + uint32_t d0 = 0x10325476; + + std::vector msg(info.begin(), info.end()); + uint64_t bit_len = static_cast(msg.size()) * 8ULL; + msg.push_back(0x80); + while ((msg.size() % 64) != 56) msg.push_back(0x00); + for (int i = 0; i < 8; ++i) msg.push_back(static_cast((bit_len >> (8 * i)) & 0xff)); + + for (size_t offset = 0; offset < msg.size(); offset += 64) + { + uint32_t M[16]; + for (int i = 0; i < 16; ++i) + { + size_t j = offset + i * 4; + M[i] = static_cast(msg[j]) | + (static_cast(msg[j + 1]) << 8) | + (static_cast(msg[j + 2]) << 16) | + (static_cast(msg[j + 3]) << 24); + } + + uint32_t A = a0, B = b0, C = c0, D = d0; + for (uint32_t i = 0; i < 64; ++i) + { + uint32_t F, g; + if (i < 16) { F = (B & C) | ((~B) & D); g = i; } + else if (i < 32) { F = (D & B) | ((~D) & C); g = (5 * i + 1) % 16; } + else if (i < 48) { F = B ^ C ^ D; g = (3 * i + 5) % 16; } + else { F = C ^ (B | (~D)); g = (7 * i) % 16; } + + F = F + A + K[i] + M[g]; + A = D; + D = C; + C = B; + B = B + leftrotate(F, s[i]); + } + + a0 += A; b0 += B; c0 += C; d0 += D; + } + + uint8_t digest[16]; + auto u32_to_le = [](uint32_t v, uint8_t out[4]) { + out[0] = static_cast(v & 0xff); + out[1] = static_cast((v >> 8) & 0xff); + out[2] = static_cast((v >> 16) & 0xff); + out[3] = static_cast((v >> 24) & 0xff); + }; + u32_to_le(a0, digest + 0); + u32_to_le(b0, digest + 4); + u32_to_le(c0, digest + 8); + u32_to_le(d0, digest + 12); + + static const char *hex = "0123456789abcdef"; + std::string out; + out.resize(32); + for (int i = 0; i < 16; ++i) + { + out[i * 2] = hex[(digest[i] >> 4) & 0x0f]; + out[i * 2 + 1] = hex[digest[i] & 0x0f]; + } + return out; + } +} \ No newline at end of file diff --git a/ApCreate/RTSPServer/LICENSE b/ApCreate/RTSPServer/LICENSE new file mode 100644 index 0000000..50bbbd8 --- /dev/null +++ b/ApCreate/RTSPServer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 aler9 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ApCreate/RTSPServer/mediamtx b/ApCreate/RTSPServer/mediamtx new file mode 100755 index 0000000..3943bf9 Binary files /dev/null and b/ApCreate/RTSPServer/mediamtx differ diff --git a/ApCreate/RTSPServer/mediamtx.yml b/ApCreate/RTSPServer/mediamtx.yml new file mode 100644 index 0000000..b5b33c6 --- /dev/null +++ b/ApCreate/RTSPServer/mediamtx.yml @@ -0,0 +1,739 @@ +############################################### +# Global settings + +# Settings in this section are applied anywhere. + +############################################### +# Global settings -> General + +# Verbosity of the program; available values are "error", "warn", "info", "debug". +logLevel: info +# Destinations of log messages; available values are "stdout", "file" and "syslog". +logDestinations: [stdout] +# If "file" is in logDestinations, this is the file which will receive the logs. +logFile: mediamtx.log +# If "syslog" is in logDestinations, use prefix for logs. +sysLogPrefix: mediamtx + +# Timeout of read operations. +readTimeout: 10s +# Timeout of write operations. +writeTimeout: 10s +# Size of the queue of outgoing packets. +# A higher value allows to increase throughput, a lower value allows to save RAM. +writeQueueSize: 512 +# Maximum size of outgoing UDP packets. +# This can be decreased to avoid fragmentation on networks with a low UDP MTU. +udpMaxPayloadSize: 1472 + +# Command to run when a client connects to the server. +# This is terminated with SIGINT when a client disconnects from the server. +# The following environment variables are available: +# * MTX_CONN_TYPE: connection type +# * MTX_CONN_ID: connection ID +# * RTSP_PORT: RTSP server port +runOnConnect: +# Restart the command if it exits. +runOnConnectRestart: no +# Command to run when a client disconnects from the server. +# Environment variables are the same of runOnConnect. +runOnDisconnect: + +############################################### +# Global settings -> Authentication + +# Authentication method. Available values are: +# * internal: users are stored in the configuration file +# * http: an external HTTP URL is contacted to perform authentication +# * jwt: an external identity server provides authentication through JWTs +authMethod: internal + +# Internal authentication. +# list of users. +authInternalUsers: + # Default unprivileged user. + # Username. 'any' means any user, including anonymous ones. +- user: any + # Password. Not used in case of 'any' user. + pass: + # IPs or networks allowed to use this user. An empty list means any IP. + ips: [] + # List of permissions. + permissions: + # Available actions are: publish, read, playback, api, metrics, pprof. + - action: publish + # Paths can be set to further restrict access to a specific path. + # An empty path means any path. + # Regular expressions can be used by using a tilde as prefix. + path: + - action: read + path: + - action: playback + path: + + # Default administrator. + # This allows to use API, metrics and PPROF without authentication, + # if the IP is localhost. +- user: any + pass: + ips: ['127.0.0.1', '::1'] + permissions: + - action: api + - action: metrics + - action: pprof + +# HTTP-based authentication. +# URL called to perform authentication. Every time a user wants +# to authenticate, the server calls this URL with the POST method +# and a body containing: +# { +# "user": "user", +# "password": "password", +# "token": "token", +# "ip": "ip", +# "action": "publish|read|playback|api|metrics|pprof", +# "path": "path", +# "protocol": "rtsp|rtmp|hls|webrtc|srt", +# "id": "id", +# "query": "query" +# } +# If the response code is 20x, authentication is accepted, otherwise +# it is discarded. +authHTTPAddress: +# Actions to exclude from HTTP-based authentication. +# Format is the same as the one of user permissions. +authHTTPExclude: +- action: api +- action: metrics +- action: pprof + +# JWT-based authentication. +# Users have to login through an external identity server and obtain a JWT. +# This JWT must contain the claim "mediamtx_permissions" with permissions, +# for instance: +# { +# "mediamtx_permissions": [ +# { +# "action": "publish", +# "path": "somepath" +# } +# ] +# } +# Users are expected to pass the JWT in the Authorization header, password or query parameter. +# This is the JWKS URL that will be used to pull (once) the public key that allows +# to validate JWTs. +authJWTJWKS: +# If the JWKS URL has a self-signed or invalid certificate, +# you can provide the fingerprint of the certificate in order to +# validate it anyway. It can be obtained by running: +# openssl s_client -connect jwt_jwks_domain:443 /dev/null | sed -n '/BEGIN/,/END/p' > server.crt +# openssl x509 -in server.crt -noout -fingerprint -sha256 | cut -d "=" -f2 | tr -d ':' +authJWTJWKSFingerprint: +# name of the claim that contains permissions. +authJWTClaimKey: mediamtx_permissions +# Actions to exclude from JWT-based authentication. +# Format is the same as the one of user permissions. +authJWTExclude: [] +# allow passing the JWT through query parameters of HTTP requests (i.e. ?jwt=JWT). +# This is a security risk. +authJWTInHTTPQuery: true + +############################################### +# Global settings -> Control API + +# Enable controlling the server through the Control API. +api: no +# Address of the Control API listener. +apiAddress: :9997 +# Enable TLS/HTTPS on the Control API server. +apiEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +apiServerKey: server.key +# Path to the server certificate. +apiServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +apiAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HTTP server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +apiTrustedProxies: [] + +############################################### +# Global settings -> Metrics + +# Enable Prometheus-compatible metrics. +metrics: no +# Address of the metrics HTTP listener. +metricsAddress: :9998 +# Enable TLS/HTTPS on the Metrics server. +metricsEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +metricsServerKey: server.key +# Path to the server certificate. +metricsServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +metricsAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HTTP server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +metricsTrustedProxies: [] + +############################################### +# Global settings -> PPROF + +# Enable pprof-compatible endpoint to monitor performances. +pprof: no +# Address of the pprof listener. +pprofAddress: :9999 +# Enable TLS/HTTPS on the pprof server. +pprofEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +pprofServerKey: server.key +# Path to the server certificate. +pprofServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +pprofAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HTTP server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +pprofTrustedProxies: [] + +############################################### +# Global settings -> Playback server + +# Enable downloading recordings from the playback server. +playback: no +# Address of the playback server listener. +playbackAddress: :9996 +# Enable TLS/HTTPS on the playback server. +playbackEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +playbackServerKey: server.key +# Path to the server certificate. +playbackServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +playbackAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HTTP server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +playbackTrustedProxies: [] + +############################################### +# Global settings -> RTSP server + +# Enable publishing and reading streams with the RTSP protocol. +rtsp: yes +# List of enabled RTSP transport protocols. +# UDP is the most performant, but doesn't work when there's a NAT/firewall between +# server and clients. +# UDP-multicast allows to save bandwidth when clients are all in the same LAN. +# TCP is the most versatile. +# The handshake is always performed with TCP. +rtspTransports: [udp, multicast, tcp] +# Use secure protocol variants (RTSPS, TLS, SRTP). +# Available values are "no", "strict", "optional". +rtspEncryption: "no" +# Address of the TCP/RTSP listener. This is needed only when encryption is "no" or "optional". +rtspAddress: :8554 +# Address of the TCP/TLS/RTSPS listener. This is needed only when encryption is "strict" or "optional". +rtspsAddress: :8322 +# Address of the UDP/RTP listener. This is needed only when "udp" is in rtspTransports. +rtpAddress: :8000 +# Address of the UDP/RTCP listener. This is needed only when "udp" is in rtspTransports. +rtcpAddress: :8001 +# IP range of all UDP-multicast listeners. This is needed only when "multicast" is in rtspTransports. +multicastIPRange: 224.1.0.0/16 +# Port of all UDP-multicast/RTP listeners. This is needed only when "multicast" is in rtspTransports. +multicastRTPPort: 8002 +# Port of all UDP-multicast/RTCP listeners. This is needed only when "multicast" is in rtspTransports. +multicastRTCPPort: 8003 +# Address of the UDP/SRTP listener. This is needed only when "udp" is in rtspTransports and encryption is enabled. +srtpAddress: :8004 +# Address of the UDP/SRTCP listener. This is needed only when "udp" is in rtspTransports and encryption is enabled. +srtcpAddress: :8005 +# Port of all UDP-multicast/SRTP listeners. This is needed only when "multicast" is in rtspTransports and encryption is enabled. +multicastSRTPPort: 8006 +# Port of all UDP-multicast/SRTCP listeners. This is needed only when "multicast" is in rtspTransports and encryption is enabled. +multicastSRTCPPort: 8007 +# Path to the server key. This is needed only when encryption is "strict" or "optional". +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +rtspServerKey: server.key +# Path to the server certificate. This is needed only when encryption is "strict" or "optional". +rtspServerCert: server.crt +# Authentication methods. Available are "basic" and "digest". +# "digest" doesn't provide any additional security and is available for compatibility only. +rtspAuthMethods: [basic] + +############################################### +# Global settings -> RTMP server + +# Enable publishing and reading streams with the RTMP protocol. +rtmp: yes +# Address of the RTMP listener. This is needed only when encryption is "no" or "optional". +rtmpAddress: :1935 +# Encrypt connections with TLS (RTMPS). +# Available values are "no", "strict", "optional". +rtmpEncryption: "no" +# Address of the RTMPS listener. This is needed only when encryption is "strict" or "optional". +rtmpsAddress: :1936 +# Path to the server key. This is needed only when encryption is "strict" or "optional". +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +rtmpServerKey: server.key +# Path to the server certificate. This is needed only when encryption is "strict" or "optional". +rtmpServerCert: server.crt + +############################################### +# Global settings -> HLS server + +# Enable reading streams with the HLS protocol. +hls: yes +# Address of the HLS listener. +hlsAddress: :8888 +# Enable TLS/HTTPS on the HLS server. +# This is required for Low-Latency HLS. +hlsEncryption: no +# Path to the server key. This is needed only when encryption is yes. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +hlsServerKey: server.key +# Path to the server certificate. +hlsServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +# This allows to play the HLS stream from an external website. +hlsAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the HLS server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +hlsTrustedProxies: [] +# By default, HLS is generated only when requested by a user. +# This option allows to generate it always, avoiding the delay between request and generation. +hlsAlwaysRemux: no +# Variant of the HLS protocol to use. Available options are: +# * mpegts - uses MPEG-TS segments, for maximum compatibility. +# * fmp4 - uses fragmented MP4 segments, more efficient. +# * lowLatency - uses Low-Latency HLS. +hlsVariant: lowLatency +# Number of HLS segments to keep on the server. +# Segments allow to seek through the stream. +# Their number doesn't influence latency. +hlsSegmentCount: 7 +# Minimum duration of each segment. +# A player usually puts 3 segments in a buffer before reproducing the stream. +# The final segment duration is also influenced by the interval between IDR frames, +# since the server changes the duration in order to include at least one IDR frame +# in each segment. +hlsSegmentDuration: 1s +# Minimum duration of each part. +# A player usually puts 3 parts in a buffer before reproducing the stream. +# Parts are used in Low-Latency HLS in place of segments. +# Part duration is influenced by the distance between video/audio samples +# and is adjusted in order to produce segments with a similar duration. +hlsPartDuration: 200ms +# Maximum size of each segment. +# This prevents RAM exhaustion. +hlsSegmentMaxSize: 50M +# Directory in which to save segments, instead of keeping them in the RAM. +# This decreases performance, since reading from disk is less performant than +# reading from RAM, but allows to save RAM. +hlsDirectory: '' +# The muxer will be closed when there are no +# reader requests and this amount of time has passed. +hlsMuxerCloseAfter: 60s + +############################################### +# Global settings -> WebRTC server + +# Enable publishing and reading streams with the WebRTC protocol. +webrtc: yes +# Address of the WebRTC HTTP listener. +webrtcAddress: :8889 +# Enable TLS/HTTPS on the WebRTC server. +webrtcEncryption: no +# Path to the server key. +# This can be generated with: +# openssl genrsa -out server.key 2048 +# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 +webrtcServerKey: server.key +# Path to the server certificate. +webrtcServerCert: server.crt +# Value of the Access-Control-Allow-Origin header provided in every HTTP response. +# This allows to play the WebRTC stream from an external website. +webrtcAllowOrigin: '*' +# List of IPs or CIDRs of proxies placed before the WebRTC server. +# If the server receives a request from one of these entries, IP in logs +# will be taken from the X-Forwarded-For header. +webrtcTrustedProxies: [] +# Address of a local UDP listener that will receive connections. +# Use a blank string to disable. +webrtcLocalUDPAddress: :8189 +# Address of a local TCP listener that will receive connections. +# This is disabled by default since TCP is less efficient than UDP and +# introduces a progressive delay when network is congested. +webrtcLocalTCPAddress: '' +# WebRTC clients need to know the IP of the server. +# Gather IPs from interfaces and send them to clients. +webrtcIPsFromInterfaces: yes +# List of interfaces whose IPs will be sent to clients. +# An empty value means to use all available interfaces. +webrtcIPsFromInterfacesList: [] +# List of additional hosts or IPs to send to clients. +webrtcAdditionalHosts: [] +# ICE servers. Needed only when local listeners can't be reached by clients. +# STUN servers allows to obtain and share the public IP of the server. +# TURN/TURNS servers forces all traffic through them. +webrtcICEServers2: [] + # - url: stun:stun.l.google.com:19302 + # if user is "AUTH_SECRET", then authentication is secret based. + # the secret must be inserted into the password field. + # username: '' + # password: '' + # clientOnly: false +# Time to wait for the WebRTC handshake to complete. +webrtcHandshakeTimeout: 10s +# Maximum time to gather video tracks. +webrtcTrackGatherTimeout: 2s +# The maximum time to gather STUN candidates. +webrtcSTUNGatherTimeout: 5s + +############################################### +# Global settings -> SRT server + +# Enable publishing and reading streams with the SRT protocol. +srt: yes +# Address of the SRT listener. +srtAddress: :8890 + +############################################### +# Default path settings + +# Settings in "pathDefaults" are applied anywhere, +# unless they are overridden in "paths". +pathDefaults: + + ############################################### + # Default path settings -> General + + # Source of the stream. This can be: + # * publisher -> the stream is provided by a RTSP, RTMP, WebRTC or SRT client + # * rtsp://existing-url -> the stream is pulled from another RTSP server / camera + # * rtsps://existing-url -> the stream is pulled from another RTSP server / camera with RTSPS + # * rtmp://existing-url -> the stream is pulled from another RTMP server / camera + # * rtmps://existing-url -> the stream is pulled from another RTMP server / camera with RTMPS + # * http://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera + # * https://existing-url/stream.m3u8 -> the stream is pulled from another HLS server / camera with HTTPS + # * udp://ip:port -> the stream is pulled with UDP, by listening on the specified IP and port + # * srt://existing-url -> the stream is pulled from another SRT server / camera + # * whep://existing-url -> the stream is pulled from another WebRTC server / camera + # * wheps://existing-url -> the stream is pulled from another WebRTC server / camera with HTTPS + # * redirect -> the stream is provided by another path or server + # * rpiCamera -> the stream is provided by a Raspberry Pi Camera + # The following variables can be used in the source string: + # * $MTX_QUERY: query parameters (passed by first reader) + # * $G1, $G2, ...: regular expression groups, if path name is + # a regular expression. + source: publisher + # If the source is a URL, and the source certificate is self-signed + # or invalid, you can provide the fingerprint of the certificate in order to + # validate it anyway. It can be obtained by running: + # openssl s_client -connect source_ip:source_port /dev/null | sed -n '/BEGIN/,/END/p' > server.crt + # openssl x509 -in server.crt -noout -fingerprint -sha256 | cut -d "=" -f2 | tr -d ':' + sourceFingerprint: + # If the source is a URL, it will be pulled only when at least + # one reader is connected, saving bandwidth. + sourceOnDemand: no + # If sourceOnDemand is "yes", readers will be put on hold until the source is + # ready or until this amount of time has passed. + sourceOnDemandStartTimeout: 10s + # If sourceOnDemand is "yes", the source will be closed when there are no + # readers connected and this amount of time has passed. + sourceOnDemandCloseAfter: 10s + # Maximum number of readers. Zero means no limit. + maxReaders: 0 + # SRT encryption passphrase required to read from this path. + srtReadPassphrase: + # If the stream is not available, redirect readers to this path. + # It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL. + fallback: + # Route original absolute timestamps of RTSP and WebRTC frames, instead of replacing them. + useAbsoluteTimestamp: false + + ############################################### + # Default path settings -> Record + + # Record streams to disk. + record: no + # Path of recording segments. + # Extension is added automatically. + # Available variables are %path (path name), %Y %m %d (year, month, day), + # %H %M %S (hours, minutes, seconds), %f (microseconds), %z (time zone), %s (unix epoch). + recordPath: ./recordings/%path/%Y-%m-%d_%H-%M-%S-%f + # Format of recorded segments. + # Available formats are "fmp4" (fragmented MP4) and "mpegts" (MPEG-TS). + recordFormat: fmp4 + # fMP4 segments are concatenation of small MP4 files (parts), each with this duration. + # MPEG-TS segments are concatenation of 188-bytes packets, flushed to disk with this period. + # When a system failure occurs, the last part gets lost. + # Therefore, the part duration is equal to the RPO (recovery point objective). + recordPartDuration: 1s + # This prevents RAM exhaustion. + recordMaxPartSize: 50M + # Minimum duration of each segment. + recordSegmentDuration: 1h + # Delete segments after this timespan. + # Set to 0s to disable automatic deletion. + recordDeleteAfter: 1d + + ############################################### + # Default path settings -> Publisher source (when source is "publisher") + + # Allow another client to disconnect the current publisher and publish in its place. + overridePublisher: yes + # SRT encryption passphrase required to publish to this path. + srtPublishPassphrase: + + ############################################### + # Default path settings -> RTSP source (when source is a RTSP or a RTSPS URL) + + # Transport protocol used to pull the stream. available values are "automatic", "udp", "multicast", "tcp". + rtspTransport: automatic + # Support sources that don't provide server ports or use random server ports. This is a security issue + # and must be used only when interacting with sources that require it. + rtspAnyPort: no + # Range header to send to the source, in order to start streaming from the specified offset. + # available values: + # * clock: Absolute time + # * npt: Normal Play Time + # * smpte: SMPTE timestamps relative to the start of the recording + rtspRangeType: + # Available values: + # * clock: UTC ISO 8601 combined date and time string, e.g. 20230812T120000Z + # * npt: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h" + # * smpte: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h" + rtspRangeStart: + + ############################################### + # Default path settings -> Redirect source (when source is "redirect") + + # path which clients will be redirected to. + # It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL. + sourceRedirect: + + ############################################### + # Default path settings -> Raspberry Pi Camera source (when source is "rpiCamera") + + # ID of the camera. + rpiCameraCamID: 0 + # Whether this is a secondary stream. + rpiCameraSecondary: false + # Width of frames. + rpiCameraWidth: 1920 + # Height of frames. + rpiCameraHeight: 1080 + # Flip horizontally. + rpiCameraHFlip: false + # Flip vertically. + rpiCameraVFlip: false + # Brightness [-1, 1]. + rpiCameraBrightness: 0 + # Contrast [0, 16]. + rpiCameraContrast: 1 + # Saturation [0, 16]. + rpiCameraSaturation: 1 + # Sharpness [0, 16]. + rpiCameraSharpness: 1 + # Exposure mode. + # values: normal, short, long, custom. + rpiCameraExposure: normal + # Auto-white-balance mode. + # values: auto, incandescent, tungsten, fluorescent, indoor, daylight, cloudy, custom. + rpiCameraAWB: auto + # Auto-white-balance fixed gains. This can be used in place of rpiCameraAWB. + # format: [red,blue]. + rpiCameraAWBGains: [0, 0] + # Denoise operating mode. + # values: off, cdn_off, cdn_fast, cdn_hq. + rpiCameraDenoise: "off" + # Fixed shutter speed, in microseconds. + rpiCameraShutter: 0 + # Metering mode of the AEC/AGC algorithm. + # values: centre, spot, matrix, custom. + rpiCameraMetering: centre + # Fixed gain. + rpiCameraGain: 0 + # EV compensation of the image [-10, 10]. + rpiCameraEV: 0 + # Region of interest, in format x,y,width,height (all normalized between 0 and 1). + rpiCameraROI: + # Whether to enable HDR on Raspberry Camera 3. + rpiCameraHDR: false + # Tuning file. + rpiCameraTuningFile: + # Sensor mode, in format [width]:[height]:[bit-depth]:[packing] + # bit-depth and packing are optional. + rpiCameraMode: + # frames per second. + rpiCameraFPS: 30 + # Autofocus mode. + # values: auto, manual, continuous. + rpiCameraAfMode: continuous + # Autofocus range. + # values: normal, macro, full. + rpiCameraAfRange: normal + # Autofocus speed. + # values: normal, fast. + rpiCameraAfSpeed: normal + # Lens position (for manual autofocus only), will be set to focus to a specific distance + # calculated by the following formula: d = 1 / value + # Examples: 0 moves the lens to infinity. + # 0.5 moves the lens to focus on objects 2m away. + # 2 moves the lens to focus on objects 50cm away. + rpiCameraLensPosition: 0.0 + # Specifies the autofocus window, in the form x,y,width,height where the coordinates + # are given as a proportion of the entire image. + rpiCameraAfWindow: + # Manual flicker correction period, in microseconds. + rpiCameraFlickerPeriod: 0 + # Enables printing text on each frame. + rpiCameraTextOverlayEnable: false + # Text that is printed on each frame. + # format is the one of the strftime() function. + rpiCameraTextOverlay: '%Y-%m-%d %H:%M:%S - MediaMTX' + # Codec. Available values: auto, hardwareH264, softwareH264, mjpeg. + rpiCameraCodec: auto + # Period between H264 IDR frames. + rpiCameraIDRPeriod: 60 + # H264 Bitrate. + rpiCameraBitrate: 5000000 + # H264 profile. + rpiCameraProfile: main + # H264 level. + rpiCameraLevel: '4.1' + # JPEG quality. + rpiCameraJPEGQuality: 60 + + ############################################### + # Default path settings -> Hooks + + # Command to run when this path is initialized. + # This can be used to publish a stream when the server is launched. + # This is terminated with SIGINT when the program closes. + # The following environment variables are available: + # * MTX_PATH: path name + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnInit: + # Restart the command if it exits. + runOnInitRestart: no + + # Command to run when this path is requested by a reader + # and no one is publishing to this path yet. + # This can be used to publish a stream on demand. + # This is terminated with SIGINT when there are no readers anymore. + # The following environment variables are available: + # * MTX_PATH: path name + # * MTX_QUERY: query parameters (passed by first reader) + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnDemand: + # Restart the command if it exits. + runOnDemandRestart: no + # Readers will be put on hold until the runOnDemand command starts publishing + # or until this amount of time has passed. + runOnDemandStartTimeout: 10s + # The command will be closed when there are no + # readers connected and this amount of time has passed. + runOnDemandCloseAfter: 10s + # Command to run when there are no readers anymore. + # Environment variables are the same of runOnDemand. + runOnUnDemand: + + # Command to run when the stream is ready to be read, whenever it is + # published by a client or pulled from a server / camera. + # This is terminated with SIGINT when the stream is not ready anymore. + # The following environment variables are available: + # * MTX_PATH: path name + # * MTX_QUERY: query parameters (passed by publisher) + # * MTX_SOURCE_TYPE: source type + # * MTX_SOURCE_ID: source ID + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnReady: + # Restart the command if it exits. + runOnReadyRestart: no + # Command to run when the stream is not available anymore. + # Environment variables are the same of runOnReady. + runOnNotReady: + + # Command to run when a client starts reading. + # This is terminated with SIGINT when a client stops reading. + # The following environment variables are available: + # * MTX_PATH: path name + # * MTX_QUERY: query parameters (passed by reader) + # * MTX_READER_TYPE: reader type + # * MTX_READER_ID: reader ID + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnRead: + # Restart the command if it exits. + runOnReadRestart: no + # Command to run when a client stops reading. + # Environment variables are the same of runOnRead. + runOnUnread: + + # Command to run when a recording segment is created. + # The following environment variables are available: + # * MTX_PATH: path name + # * MTX_SEGMENT_PATH: segment file path + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnRecordSegmentCreate: + + # Command to run when a recording segment is complete. + # The following environment variables are available: + # * MTX_PATH: path name + # * MTX_SEGMENT_PATH: segment file path + # * MTX_SEGMENT_DURATION: segment duration + # * RTSP_PORT: RTSP server port + # * G1, G2, ...: regular expression groups, if path name is + # a regular expression. + runOnRecordSegmentComplete: + +############################################### +# Path settings + +# Settings in "paths" are applied to specific paths, and the map key +# is the name of the path. +# Any setting in "pathDefaults" can be overridden here. +# It's possible to use regular expressions by using a tilde as prefix, +# for example "~^(test1|test2)$" will match both "test1" and "test2", +# for example "~^prefix" will match all paths that start with "prefix". +paths: + # example: + # my_camera: + # source: rtsp://my_camera + + # Settings under path "all_others" are applied to all paths that + # do not match another entry. + all_others: diff --git a/ApCreate/RTSPServer/mediamtx_v1.13.1_linux_arm64.tar.gz b/ApCreate/RTSPServer/mediamtx_v1.13.1_linux_arm64.tar.gz new file mode 100644 index 0000000..82ef675 Binary files /dev/null and b/ApCreate/RTSPServer/mediamtx_v1.13.1_linux_arm64.tar.gz differ diff --git a/ApCreate/src/main.cpp b/ApCreate/src/main.cpp new file mode 100644 index 0000000..7cc3a00 --- /dev/null +++ b/ApCreate/src/main.cpp @@ -0,0 +1,564 @@ +/* +本程序提供以下功能: +1.开启4G模块,允许上网 +2.进行RTSP推流 +3.初始化验证->启动系统服务 +*/ + +#include +#include +#include +#include + +#include + +#include + +#include "Netra.hpp" +#include "NetRequest.hpp" +#include "encrypt.hpp" + +using namespace std; +using namespace QCL; +using namespace ntq; +using namespace encrypt; +namespace bp = boost::process; + +bp::child rtsp_proc; // 服务器进程 +bp::child video_proc; // 视频推流进程 +bp::child ap_proc; // 创建热点进程 +bp::child net_proc; // 开启网络进程 + +// 文件设置路径 +string filepath = "/home/orangepi/InitAuth/conf/.env"; +string cameraPath = "/opt/rknn-yolov11/.env"; +string passwd = "/home/orangepi/InitAuth/pwd/.env"; +// string cameraPath = "/home/orangepi/InitAuth/.env"; + +string url = "http://116.147.36.110:8095/device/validateDevice"; + +std::atomic isRunning(true); // 全局运行标志 +std::atomic confirm(true); // 发送卡和ID + +// 媒体对象 +struct Media +{ + bool mirror; // 镜像 + bool flip; // 翻转 + bool occlusion; // 遮挡 +} media{}; + +// 输入输出对象 +struct OutSignal +{ + bool outPutMode; // 警报输出电平 true--高电平,false -- 低电平 + bool inPutMode; // 触发输入模式 true--高电平,false -- 低电平 +} algor{}; + +// 确认是否已经进行过认证 +bool ConfirmInit(); + +// 获取cpu序列号和SIM卡号 +string GetCardInfo(); + +// 获取SIM卡号 +string GetSimICCID(const string &tty = "/dev/ttyUSB2"); + +// 发送cpu序列号和SIM卡号 +void SendCardInfo(TcpServer *MyServer); + +// 进行验证 +bool verification(); + +// 解析接收的数据 +template +void CalculateInfo(T &conf, const string &json); + +// 创建服务器 +TcpServer *MyServer; + +// 打开RSTP服务器 +void OpenRTSP(); + +// 视频推流 +void VideoStream(); + +// 接收数据 +void ReceiveData(); + +// 退出信号捕捉 +void Exit(int sig); + +// 开启网络(4G模块) +void StartNet(); + +// 开启服务 +void StartService(); + +/* +parames: +argv[1] - SSID of the WiFi network to connect to +argv[2] - Password for the WiFi network (if applicable) +*/ +int main(int argc, char *argv[]) +{ + // 开启4G模块 + StartNet(); + + this_thread::sleep_for(chrono::seconds(2)); + + // 关闭全部信号 + blockAllSignals(); + signal(SIGINT, Exit); // 捕获Ctrl+C信号 + + // 开启服务器 + MyServer = new TcpServer(8848); + + MyServer->start(); // 不会阻塞 + + // 如果没有进行过初始化,发送物联网卡信息进行认证 + if (ConfirmInit() == false) + { + SendCardInfo(MyServer); + } + else + { + if (verification() == false) + { + cerr << "验证失败" << endl; + return 0; + } + } + + thread(StartService).detach(); + + thread Rtsp(OpenRTSP); + std::this_thread::sleep_for(chrono::seconds(2)); // 等待RTSP服务器启动 + + // 进行RTSP视频推流 + thread video(VideoStream); + + // 作为服务端进行数据的收发 + thread Processing(ReceiveData); + + // 等待热点创建线程结束 + Processing.join(); + video.join(); + Rtsp.join(); + + return 0; +} + +// 开启服务 +void StartService() +{ + string commnd = "echo 'orangepi' | sudo -S ../../StartService/start start"; + system(commnd.c_str()); +} + +void StartNet() +{ + string commd = "../../GetNet/bin/setNet"; + net_proc = bp::child("/bin/bash", bp::args = {"-c", commd}); +} + +// 进行验证 +bool verification() +{ + // 读取文件密文 + ReadFile *rf = new ReadFile(passwd); + bool flag = false; + auto pass = rf->ReadLines(); + // 若为空:改写初始化标志位false,重认证 + if (pass.empty()) + { + WriteFile *wf = new WriteFile(filepath); + wf->overwriteAtPos("no", wf->countBytesPattern("InitOrNot:", true), 3); + return 0; + } + + for (auto &ii : pass) + { + // 不为空,线上发送密文开始认证 + auto pos = format(R"({{"cardNo":"{}"}})", ii); + + auto res = NetRequest::QuickPostJson(url, pos); + + int code = -1; + if (res) + { + try + { + code = nlohmann::json::parse(res->body).value("code", -1); + } + catch (const std::exception &e) + { + std::cerr << "JSON解析失败: " << e.what() << std::endl; + } + } + + if (res && code == 200) + { + cout << "线上:认证成功" << endl; + flag = true; + } + else + { + try + { + // 进行线下认证 + // 获取cpu序列号和SIM卡号 + string CardID = GetCardInfo(); + string SIMID = GetSimICCID(); + string info = CardID + SIMID; + // 进行MD5加密 + string md5 = MD5(info); + cout << md5 << endl; + if (md5.compare(ii) == 0) + { + cout << "线下:认证成功" << endl; + flag = true; + } + } + catch (const std::exception &e) + { + std::cerr << e.what() << '\n'; + } + } + } + cout << "结束" << endl; + return flag; +} + +// 退出信号 +void Exit(int sig) +{ + cout << "Exiting..." << endl; + isRunning = false; + confirm = false; + MyServer->stop(); + + system("sudo killall create_ap"); + // 杀死后台 RTSP 和 ffmpeg 进程 + if (rtsp_proc.running()) + rtsp_proc.terminate(); + if (video_proc.running()) + video_proc.terminate(); + if (ap_proc.running()) + ap_proc.terminate(); + if (net_proc.running()) + net_proc.terminate(); +} + +// 接收数据 +void ReceiveData() +{ + string buffer = ""; + while (isRunning) + { + std::vector client = MyServer->getClientSockets(); + if (client.empty()) + { + this_thread::sleep_for(chrono::milliseconds(100)); + continue; + } + int index = client.size(); + for (int ii = 0; ii < index; ii++) + { + buffer = MyServer->receiveFromClient(client[ii], false); // 非阻塞模式接受数据 + if (buffer.empty() == false) + { + cout << "已收到" << buffer << endl; + // 处理数据 + if (buffer.find("Pass:") != string::npos) + { + cout << "已收到" << buffer << endl; + // 验证秘钥 + WriteFile wf(filepath); + string str = "yse\n"; + wf.overwriteAtPos(str, wf.countBytesPattern("InitOrNot:", true), str.size()); + cout << "已更新" << endl; + // 将秘钥写入配置文件: + WriteFile wf2(passwd); + wf2.overwriteText(buffer.substr(5)); + } + else if (buffer.find("SET_DISTANCES") != string::npos) + { // 接收距离参数 + auto res = nlohmann::json::parse(buffer); + // 读取值并兼容 number/string + auto &danger_json = res["params"]["danger_distance"]; + auto &warning_json = res["params"]["warning_distance"]; + auto &safe_json = res["params"]["safe_distance"]; + + auto toDouble = [](const nlohmann::json &jv) -> double + { + if (jv.is_number()) + return jv.get(); + if (jv.is_string()) + { + try + { + return std::stod(jv.get()); + } + catch (...) + { + return 0.0; + } + } + return 0.0; + }; + + double danger = toDouble(danger_json); + double warn = toDouble(warning_json); + double safe = toDouble(safe_json); + + // 整文件读取,逐行替换,保持注释不变 + ReadFile rf(cameraPath); + if (!rf.Open()) + { + cerr << "文件打开失败: " << cameraPath << "\n"; + } + else + { + auto lines = rf.ReadLines(); + for (auto &line : lines) + { + if (line.rfind("NEAR_THRESHOLD=", 0) == 0) + { + line = format("NEAR_THRESHOLD={}", danger); + } + else if (line.rfind("MID_THRESHOLD=", 0) == 0) + { + line = format("MID_THRESHOLD={}", warn); + } + else if (line.rfind("MAX_DISTANCE=", 0) == 0) + { + line = format("MAX_DISTANCE={}", safe); + } + } + + // 重新拼接并整体写回 + string out; + out.reserve(4096); + for (size_t i = 0; i < lines.size(); ++i) + { + out += lines[i]; + if (i + 1 < lines.size()) + out += "\n"; + } + WriteFile wf(cameraPath); + wf.overwriteText(out); + } + } + else + { + // 写摄像头画面设置 + string conf = ""; + if (buffer.find("media") != string::npos) + { // 写摄像头参数 + cout << buffer << endl; + WriteFile cam(cameraPath); + CalculateInfo(media, buffer); + conf = format("\nMEDIA_MIRROR={}\nMEDIA_FLIP={}\nMEDIA_OCCLUSION={}\n", media.mirror == 1 ? "true" : "false", media.flip == 1 ? "true" : "false", media.occlusion == 1 ? "true" : "false"); + cout << conf; + cam.writeAfterPatternOrAppend("***---***", conf); + } + if (buffer.find("algorithm") != string::npos) + { + // 写输入输出参数 + WriteFile mode(filepath); + int pos = mode.countBytesPattern("***---***", true); + CalculateInfo(algor, buffer); + conf = format("\noutPutMode:{}\ninPutMode:{}", algor.outPutMode == 1 ? "true" : "false", algor.inPutMode == 1 ? "true" : "false"); + mode.writeAfterPatternOrAppend("***---***", conf); + } + } + } + } + this_thread::sleep_for(chrono::milliseconds(100)); // 避免过于频繁的循环 + } +} + +// 打开RSTP服务器 +void OpenRTSP() +{ + string commnd = "../RTSPServer/mediamtx ../RTSPServer/mediamtx.yml"; + rtsp_proc = bp::child("/bin/bash", bp::args = {"-c", commnd}); +} + +// 视频推流 +void VideoStream() +{ + string commnd = "ffmpeg -f v4l2 -i /dev/video0 -c:v h264_rkmpp -rtsp_transport tcp -f rtsp rtsp://192.168.12.1:8554/stream"; + video_proc = bp::child("/bin/bash", bp::args = {"-c", commnd}); +} + +// 确认是否已经进行过认证 +bool ConfirmInit() +{ + ReadFile *rf = new ReadFile(filepath); + if (rf->Open() == false) + { + cerr << "文件打开失败\n"; + return false; + } + + auto vct = rf->ReadLines(); + for (auto &ii : vct) + { + if (ii.find("InitOrNot:no") != string::npos) + return false; + } + return true; +} + +// 获取cpu序列号和SIM卡号 +string GetCardInfo() +{ + ReadFile *rf = new ReadFile("/proc/cpuinfo"); + if (rf->Open() == false) + { + cerr << "文件打开失败\n"; + return ""; + } + + auto lines = rf->ReadLines(); + string res = ""; + for (auto &ii : lines) + { + if (ii.find("Serial") != string::npos) + { + auto pos = ii.find(":"); + if (pos != string::npos) + { + res = ii.substr(pos + 1); + break; + } + } + } + + return LRtrim(res); +} + +// 获取SIM卡号 +// 通过串口发送 AT+CCID 指令,读取并解析返回的ICCID号 +string GetSimICCID(const string &tty) +{ + int retry = 0; // 重试次数 + while (retry < 5) // 最多重试5次 + { + int fd = open(tty.c_str(), O_RDWR | O_NOCTTY | O_NDELAY); // 打开串口 + if (fd < 0) + { + std::cerr << "无法打开串口: " << tty << std::endl; + return ""; + } + + // 配置串口参数 + struct termios options; + tcgetattr(fd, &options); + cfsetispeed(&options, B115200); + cfsetospeed(&options, B115200); + options.c_cflag |= (CLOCAL | CREAD); + tcsetattr(fd, TCSANOW, &options); + + // 发送 AT+CCID 指令 + write(fd, "AT+CCID\r\n", 9); + + std::string result; + char buf[256] = {0}; + // 循环多次读取,拼接内容,防止数据分包 + for (int i = 0; i < 10; ++i) + { + usleep(100000); // 每次等待100ms + int n = read(fd, buf, sizeof(buf) - 1); + if (n > 0) + result.append(buf, n); + } + close(fd); // 关闭串口 + + // 检查是否有ERROR,若有则重试 + if (result.find("ERROR") != std::string::npos) + { + retry++; + usleep(200000); // 等待200ms再重试 + continue; + } + + // 用正则提取ICCID(+CCID: 后面的数字) + std::smatch m; + std::regex reg(R"(\+CCID:\s*([0-9]+))"); + if (std::regex_search(result, m, reg)) + return m[1]; + + // 兜底:直接找19~22位数字(兼容不同长度的ICCID) + std::regex reg2(R"((\d{19,22}))"); + if (std::regex_search(result, m, reg2)) + return m[1]; + + // 没有ERROR但也没读到ICCID,直接退出循环 + break; + } + return ""; // 多次重试失败,返回空 +} + +// 发送cpu序列号和SIM卡号 +void SendCardInfo(TcpServer *MyServer) +{ + string CardID = GetCardInfo(); + string SIMID = GetSimICCID(); + string info = CardID + SIMID; + + while (confirm) + { + std::vector client = MyServer->getClientSockets(); + if (client.empty()) + continue; + cout << "有了客户端连接" << endl; + size_t index = client.size(); + + for (int ii = 0; ii < index; ii++) + { + if (MyServer->receiveFromClient(client[ii]) == "GET_ID") + { + cout << info << endl; + MyServer->sendToClient(client[ii], info + " "); + } + } + break; + } +} + +// 解析接收的数据 +template +void CalculateInfo(T &conf, const string &json) +{ + try + { + /* code */ + auto j = nlohmann::json::parse(json); + if (j.contains("params") && j["params"].contains("media")) + { + auto conf = j["params"]["media"]; + if (conf.contains("mirror")) + media.mirror = conf["mirror"].get(); + if (conf.contains("flip")) + media.flip = conf["mirror"].get(); + if (conf.contains("occlusion")) + media.occlusion = conf["occlusion"].get(); + } + else if (j.contains("params") && j["params"].contains("algorithm")) + { + auto conf = j["params"]["algorithm"]; + if (conf.contains("alarm_output_mode")) + algor.outPutMode = (conf["alarm_output_mode"].get().find("高电平") != string::npos) ? true : false; + + if (conf.contains("trigger_input_mode")) + algor.inPutMode = (conf["trigger_input_mode"].get().find("高电平") != string::npos) ? true : false; + } + } + catch (const std::exception &e) + { + std::cerr << e.what() << '\n'; + } +} diff --git a/ApCreate/src/makefile b/ApCreate/src/makefile new file mode 100644 index 0000000..0437205 --- /dev/null +++ b/ApCreate/src/makefile @@ -0,0 +1,8 @@ +all:wifi + +wifi:main.cpp + g++ -g -o wifi main.cpp /home/orangepi/RKApp/ApCreate/NetraLib/src/Netra.cpp /home/orangepi/RKApp/ApCreate/NetraLib/src/encrypt.cpp /home/orangepi/RKApp/ApCreate/NetraLib/src/NetRequest.cpp -lpthread -I/home/orangepi/RKApp/ApCreate/NetraLib/include + mv ./wifi ../bin/wifi + +clean: + rm -rf wifi \ No newline at end of file diff --git a/FastApi/__pycache__/fastApi.cpython-38.pyc b/FastApi/__pycache__/fastApi.cpython-38.pyc new file mode 100644 index 0000000..d01f59d Binary files /dev/null and b/FastApi/__pycache__/fastApi.cpython-38.pyc differ diff --git a/FastApi/fastApi.py b/FastApi/fastApi.py new file mode 100644 index 0000000..41e124f --- /dev/null +++ b/FastApi/fastApi.py @@ -0,0 +1,116 @@ +#本程序用于启用fastApi,用于与摄像头的数据交互建立连接 + +from fastapi import FastAPI, File, UploadFile, Form, WebSocket, WebSocketDisconnect +from fastapi.responses import JSONResponse +import os, shutil, subprocess, json + +app = FastAPI() + +VIDEO_SAVE_PATH = "/mnt/save/video" +IMAGE_SAVE_PATH = "/mnt/save/warning" +MODBUS_BIN_PATH = "/home/orangepi/RKApp/ModBus/modbus" + +os.makedirs(VIDEO_SAVE_PATH, exist_ok=True) +os.makedirs(IMAGE_SAVE_PATH, exist_ok=True) + + +@app.websocket("/ws/distance") +@app.websocket("/ws/distance/") +async def websocket_distance(websocket: WebSocket): + await websocket.accept() + print("✅ WebSocket 客户端已连接") + try: + while True: + data = await websocket.receive_text() + try: + msg = json.loads(data) + distance = msg.get("distance") + ts = msg.get("ts") + print(f"收到距离: {distance}, 时间戳: {ts}") + + # # 写入日志,单独try,避免异常影响后续流程 + # try: + # now = datetime.now() + # log_line = f"{now.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]} distance={distance} ts={ts}\n" + # with open("/home/orangepi/Opencv/time.txt", "a") as f: + # f.write(log_line) + # except Exception as log_e: + # print(f"日志写入失败: {log_e}") + #调用 Modbus 可执行文件 + result = subprocess.run( + [MODBUS_BIN_PATH, str(int(distance))], + capture_output=True, text=True, timeout=5 + ) + if result.returncode != 0: + # 发送错误信息给客户端 + await websocket.send_json({ + "error": f"modbus 程序执行失败: {result.stderr.strip()}" + }) + continue + + # 正常发送结果 + await websocket.send_json({ + "distance": distance, + "modbus_output": result.stdout.strip() + }) + + except json.JSONDecodeError: + await websocket.send_text("invalid JSON") + except Exception as e: + await websocket.send_text(f"server error: {e}") + + except WebSocketDisconnect: + print("⚠️ WebSocket 客户端断开连接") + except Exception as e: + print("❌ WebSocket 处理出错:", e) + +@app.post("/upload_video/") +async def upload_video( + video: UploadFile = File(..., description="上传的视频(.mp4)") +): + if not video.filename.lower().endswith('.mp4'): + return JSONResponse(status_code=400, content={"error": "视频必须为.mp4格式"}) + video_path = os.path.join(VIDEO_SAVE_PATH, video.filename) + with open(video_path, "wb") as vid_file: + shutil.copyfileobj(video.file, vid_file) + return {"video_saved_to": video_path} + + +@app.post("/upload_image/") +async def upload_image( + image: UploadFile = File(..., description="上传的图片(.jpg)") +): + if not image.filename.lower().endswith('.jpg'): + return JSONResponse(status_code=400, content={"error": "图片必须为.jpg格式"}) + image_path = os.path.join(IMAGE_SAVE_PATH, image.filename) + with open(image_path, "wb") as img_file: + shutil.copyfileobj(image.file, img_file) + return {"image_saved_to": image_path} + + +# @app.post("/upload_distance/") +# async def upload_distance( +# distance: float = Form(..., description="距离") +# ): +# # 调用本地 modbus 程序,将距离作为参数传递 +# try: +# #写入日志精确到毫秒 +# # now = datetime.now() +# # log_line = f"{now.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]} distance={distance} ts={ts}\n" +# # with open("/home/orangepi/Opencv/time.txt", "a") as f: +# # f.write(log_line) +# result = subprocess.run( +# [MODBUS_BIN_PATH, str(int(distance))], +# capture_output=True, +# text=True, +# timeout=5 +# ) +# if result.returncode != 0: +# return JSONResponse(status_code=500, content={"error": f"modbus程序执行失败: {result.stderr}"}) +# return { +# "distance": distance, +# "modbus_output": result.stdout.strip() +# } +# except Exception as e: +# return JSONResponse(status_code=500, content={"error": str(e)}) + diff --git a/GetNet/bin/setNet b/GetNet/bin/setNet new file mode 100755 index 0000000..56e8e7f Binary files /dev/null and b/GetNet/bin/setNet differ diff --git a/GetNet/src/GetNet.cpp b/GetNet/src/GetNet.cpp new file mode 100644 index 0000000..120bf7b --- /dev/null +++ b/GetNet/src/GetNet.cpp @@ -0,0 +1,229 @@ +/* +本程序主要用于初始化网络配置,使4g模块可以正常上网 +*/ + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +// 串口设备 +string Serious = "/dev/ttyUSB2"; + +// 设置网络 +void SetNet(); + +int main(int argc, char *argv[]) +{ + + this_thread::sleep_for(chrono::seconds(3)); + + SetNet(); + return 0; +} + +// 设置网络 +void SetNet() +{ + auto configure_serial = [](int fd) -> bool + { + struct termios options; + if (tcgetattr(fd, &options) != 0) + return false; + + cfsetispeed(&options, B115200); + cfsetospeed(&options, B115200); + + options.c_cflag |= (CLOCAL | CREAD); + options.c_cflag &= ~PARENB; // 无校验 + options.c_cflag &= ~CSTOPB; // 1 位停止位 + options.c_cflag &= ~CSIZE; + options.c_cflag |= CS8; // 8 位数据位 + options.c_cflag &= ~CRTSCTS; // 关闭硬件流控 + + options.c_iflag &= ~(IXON | IXOFF | IXANY); // 关闭软件流控 + options.c_iflag &= ~(INLCR | ICRNL); + + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 原始模式 + options.c_oflag &= ~OPOST; // 原始输出 + + options.c_cc[VMIN] = 0; // 非阻塞读取 + options.c_cc[VTIME] = 10; // 1.0s 读超时 + + if (tcsetattr(fd, TCSANOW, &options) != 0) + return false; + tcflush(fd, TCIOFLUSH); + return true; + }; + + auto write_all = [](int fd, const std::string &data) -> bool + { + const char *p = data.c_str(); + size_t left = data.size(); + while (left > 0) + { + ssize_t n = write(fd, p, left); + if (n < 0) + { + if (errno == EINTR) + continue; + return false; + } + left -= static_cast(n); + p += n; + } + return true; + }; + + auto send_at = [&](int fd, const std::string &cmd, std::string &resp, int timeout_ms = 3000) -> bool + { + std::string line = cmd + "\r\n"; + if (!write_all(fd, line)) + return false; + + resp.clear(); + char buf[256]; + int elapsed = 0; + while (elapsed < timeout_ms) + { + struct pollfd pfd{fd, POLLIN, 0}; + int pr = poll(&pfd, 1, 200); + if (pr > 0 && (pfd.revents & POLLIN)) + { + ssize_t n = read(fd, buf, sizeof(buf)); + if (n > 0) + { + resp.append(buf, buf + n); + if (resp.find("\r\nOK\r\n") != std::string::npos || + resp.find("\nOK\n") != std::string::npos || + resp.find("OK") != std::string::npos) + return true; + if (resp.find("ERROR") != std::string::npos) + return false; + } + } + elapsed += 200; + } + return false; // 超时 + }; + + int fd = open(Serious.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); + if (fd < 0) + { + std::cerr << "无法打开串口: " << Serious << "\n"; + return; + } + // 设为阻塞 + int flags = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); + + if (!configure_serial(fd)) + { + std::cerr << "串口参数配置失败\n"; + close(fd); + return; + } + + std::string resp; + // 0) 连续握手,确保 AT 可用(最多 10 次) + { + bool ok = false; + for (int i = 1; i <= 10; ++i) + { + if (send_at(fd, "AT", resp, 500)) + { + ok = true; + break; + } + usleep(500 * 1000); + } + if (!ok) + { + std::cerr << "AT 握手失败,模块未响应\n"; + close(fd); + return; + } + } + + // 1) 打开功能 + if (!send_at(fd, "AT+CFUN=1", resp)) + { + std::cerr << "AT+CFUN=1 失败: " << resp << "\n"; + } + else + { + std::cout << "AT+CFUN=1 OK\n"; + } + + // 1.1) 等 SIM 就绪(AT+CPIN? 返回 READY,最多 30 秒) + { + bool ready = false; + for (int i = 0; i < 60; ++i) + { + if (send_at(fd, "AT+CPIN?", resp, 500) && resp.find("READY") != std::string::npos) + { + ready = true; + break; + } + usleep(500 * 1000); + } + if (!ready) + std::cerr << "SIM 未就绪,后续可能失败\n"; + } + + // 2) 设置 PDP 上下文:APN = ipn(请按运营商实际 APN 调整) + if (!send_at(fd, "AT+CGDCONT=1,\"IP\",\"ipn\"", resp)) + { + std::cerr << "AT+CGDCONT 设置失败: " << resp << "\n"; + } + else + { + std::cout << "AT+CGDCONT OK\n"; + } + + // 2.1) 等驻网(AT+CEREG? 返回 ,1 或 ,5,最多 60 秒) + { + auto registered = [](const std::string &s) + { return s.find(",1") != std::string::npos || s.find(",5") != std::string::npos; }; + bool ok = false; + for (int i = 0; i < 60; ++i) + { + if (send_at(fd, "AT+CEREG?", resp, 500) && registered(resp)) + { + ok = true; + break; + } + usleep(1000 * 1000); + } + if (!ok) + std::cerr << "未驻网,继续尝试拨号但成功率较低\n"; + } + + // 3) 打开网卡/拨号(Quectel 私有指令示例)- 失败重试 5 次 + { + bool ok = false; + for (int attempt = 1; attempt <= 5; ++attempt) + { + resp.clear(); + if (send_at(fd, "AT+QNETDEVCTL=1,1,1", resp)) + { + std::cout << "AT+QNETDEVCTL OK (attempt " << attempt << ")\n"; + ok = true; + break; + } + std::cerr << "AT+QNETDEVCTL 失败 (attempt " << attempt << "/5): " << resp << "\n"; + usleep(1000 * 1000); // 1s 间隔 + } + if (!ok) + { + std::cerr << "AT+QNETDEVCTL 连续 5 次失败,退出本步骤\n"; + } + } + + close(fd); +} \ No newline at end of file diff --git a/GetNet/src/makefile b/GetNet/src/makefile new file mode 100644 index 0000000..d120f97 --- /dev/null +++ b/GetNet/src/makefile @@ -0,0 +1,8 @@ +all:SetNet + +SetNet:GetNet.cpp + g++ -g -o setNet GetNet.cpp + mv ./setNet ../bin/ + +clean: + rm -rf ../bin/* \ No newline at end of file diff --git a/PyApp/Pub.py b/PyApp/Pub.py new file mode 100755 index 0000000..61b483f --- /dev/null +++ b/PyApp/Pub.py @@ -0,0 +1,263 @@ +import os # 文件系统操作 +import base64 # Base64编码图片 +import paho.mqtt.client as mqtt # MQTT通信 +import pymysql # 数据库操作 +import time # 时间处理 +from datetime import datetime, timedelta # 时间处理 +import serial # 串口通信 +import re # 正则匹配GPS串口数据 +import threading # 多线程上传图片 + +# ======== 配置区 ======== +IMAGE_FOLDER = "/mnt/save/warning" # 图片目录 +MQTT_BROKER = "116.147.36.110" +MQTT_PORT = 1883 +MQTT_TOPIC = "images/topic" + +DB_CONFIG = { + "host": "116.147.36.110", + "port": 13306, + "user": "root", + "password": "Wxit11335577", + "database": "mqtt" +} + +SERIAL_PORT = "/dev/ttyUSB3" +SERIAL_BAUDRATE = 115200 + +MAX_UPLOADED_SIZE = 100000 # 最大上传记录数 +NOW_SIZE = 0 # 当前记录数 + +LOG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "upload.log") + +# ======== 全局变量与线程锁 ======== +last_lon, last_lat = None, None # 上次读取到的GPS +ser = None # 串口对象 + +serial_lock = threading.Lock() +uploaded_lock = threading.Lock() +uploaded = {} # 保存已上传记录的文件及时间 + +# ======== 工具函数 ======== +def write_log(message): + """写入日志文件""" + with open(LOG_FILE, "a") as logf: + logf.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - {message}\n") + +# def extract_gps_data(serial_line): +# """ +# 从串口数据中提取经纬度(只处理$GNGLL格式) +# 示例数据: $GNGLL,3114.72543,N,12130.62735,E,... +# 只做方向转换,不转换度分格式 +# 返回 (lon, lat),都为float,带正负号 +# """ +# match = re.search(r'\$GNGLL,(\d+\.\d+),(N|S),(\d+\.\d+),(E|W)', serial_line) +# if match: +# lat = float(match.group(1)) +# lat_dir = match.group(2) +# lon = float(match.group(3)) +# lon_dir = match.group(4) + +# if lat_dir == 'S': +# lat = -lat +# if lon_dir == 'W': +# lon = -lon +# return lon, lat +# return None, None + + +def extract_gps_data(serial_line): + """ + 从串口数据中提取经纬度,支持$GNGLL、$GNRMC、$GNGGA格式 + 只做方向正负号转换,不转换度分格式 + 返回 (lon, lat),float,带正负号 + """ + # 先匹配$GNGLL(你之前的) + match = re.search(r'\$GNGLL,(\d+\.\d+),(N|S),(\d+\.\d+),(E|W)', serial_line) + if match: + lat = float(match.group(1)) + lat_dir = match.group(2) + lon = float(match.group(3)) + lon_dir = match.group(4) + + if lat_dir == 'S': + lat = -lat + if lon_dir == 'W': + lon = -lon + + return lon, lat + + # 尝试匹配$GNRMC + match = re.search(r'\$GNRMC,[^,]*,[AV],(\d+\.\d+),(N|S),(\d+\.\d+),(E|W)', serial_line) + if match: + lat = float(match.group(1)) + lat_dir = match.group(2) + lon = float(match.group(3)) + lon_dir = match.group(4) + + if lat_dir == 'S': + lat = -lat + if lon_dir == 'W': + lon = -lon + + return lon, lat + + # 尝试匹配$GNGGA + match = re.search(r'\$GNGGA,[^,]*,(\d+\.\d+),(N|S),(\d+\.\d+),(E|W)', serial_line) + if match: + lat = float(match.group(1)) + lat_dir = match.group(2) + lon = float(match.group(3)) + lon_dir = match.group(4) + + if lat_dir == 'S': + lat = -lat + if lon_dir == 'W': + lon = -lon + + return lon, lat + + return None, None + +def read_gps_data(): + global last_lon, last_lat, ser + with serial_lock: + try: + if ser is None or not ser.is_open: + ser = serial.Serial(SERIAL_PORT, SERIAL_BAUDRATE, timeout=1) + + line = ser.readline().decode('utf-8').strip() + if line: + # 只对特定句子类型尝试解析并写日志 + if line.startswith(('$GNGLL', '$GNRMC', '$GNGGA')): + lon, lat = extract_gps_data(line) + if lon is not None and lat is not None: + last_lon, last_lat = lon, lat + return lon, lat + else: + write_log(f"无效的GPS数据: {line}") + else: + # 忽略其他NMEA语句 + pass + except Exception as e: + write_log(f"串口读取失败: {e}") + return 1201, 3129 + + +# def read_gps_data(): +# """ +# 从串口读取一行数据并尝试解析GPS经纬度。 +# 不成功时返回 (None, None) +# """ +# global last_lon, last_lat, ser +# with serial_lock: +# try: +# if ser is None or not ser.is_open: +# ser = serial.Serial(SERIAL_PORT, SERIAL_BAUDRATE, timeout=1) + +# line = ser.readline().decode('utf-8').strip() +# if line: +# lon, lat = extract_gps_data(line) +# if lon is not None and lat is not None: +# last_lon, last_lat = lon, lat # 更新最新值 +# return lon, lat +# else: +# write_log(f"无效的GPS数据: {line}") +# except Exception as e: +# write_log(f"串口读取失败: {e}") +# return 1201, 3129 + +def save_to_db(topic, payload, lon, lat): + """ + 将图片和经纬度信息写入数据库,可接受空值 + """ + try: + db = pymysql.connect(**DB_CONFIG) + cursor = db.cursor() + sql = "INSERT INTO mqtt_messages (topic, payload, lon, lat) VALUES (%s, %s, %s, %s)" + cursor.execute(sql, (topic, payload, lon, lat)) + db.commit() + write_log("数据库插入成功") + except Exception as e: + write_log(f"数据库插入失败: {e}") + finally: + db.close() + +def publish_image(image_path): + """ + 发布图片到MQTT、写入数据库,并附带经纬度信息(如无法获取可为空) + """ + with open(image_path, "rb") as f: + img_bytes = f.read() + img_b64 = base64.b64encode(img_bytes).decode("utf-8") + + # MQTT发布 + client = mqtt.Client() + client.connect(MQTT_BROKER, MQTT_PORT) + client.publish(MQTT_TOPIC, img_b64) + client.disconnect() + + # 获取GPS数据(尝试获取最新,失败用历史,再失败就用None) + lon, lat = read_gps_data() + if lon is None or lat is None: + write_log("GPS读取失败,尝试使用上一次位置") + lon, lat = last_lon, last_lat + + if lon is not None and lat is not None: + write_log(f"使用经纬度上传: lon={lon}, lat={lat}") + else: + write_log("经纬度为空,上传空GPS信息") + + save_to_db(MQTT_TOPIC, img_b64, lon, lat) + +def upload_worker(img_path): + """上传线程任务,处理一张图片上传""" + try: + publish_image(img_path) + write_log("上传线程结束: " + img_path + ":\t成功!") + except Exception as e: + write_log(f"上传图片 {img_path} 时出错: {e}") + +def clean_uploaded(): + """ + 清理7天前的已上传图片记录,避免内存泄漏 + """ + with uploaded_lock: + expiration_time = datetime.now() - timedelta(days=7) + keys_to_remove = [key for key, value in uploaded.items() if value < expiration_time] + for key in keys_to_remove: + del uploaded[key] + if keys_to_remove: + write_log(f"清理已上传图片记录,移除 {len(keys_to_remove)} 条记录") + +# ======== 主循环程序入口 ======== +if __name__ == "__main__": + last_clean_time = time.time() + try: + while True: + now = time.time() + for filename in os.listdir(IMAGE_FOLDER): + if filename.lower().endswith((".jpg", ".jpeg", ".png", ".bmp", ".gif")): + img_path = os.path.join(IMAGE_FOLDER, filename) + mtime = os.path.getmtime(img_path) + # 检查文件是否是最近10秒内新创建,且未上传 + if now - mtime <= 10: + with uploaded_lock: + if img_path in uploaded: + continue + write_log("开始上传图片: " + img_path) + t = threading.Thread(target=upload_worker, args=(img_path,)) + t.start() + with uploaded_lock: + uploaded[img_path] = datetime.now() + # 每小时清理一次上传记录 + if time.time() - last_clean_time > 3600: + clean_uploaded() + last_clean_time = time.time() + + time.sleep(2) # 每2秒轮询一次 + except Exception as e: + write_log(f"主程序异常: {e}") + finally: + if ser is not None and ser.is_open: + ser.close() diff --git a/PyApp/upload.log b/PyApp/upload.log new file mode 100644 index 0000000..ad2e430 --- /dev/null +++ b/PyApp/upload.log @@ -0,0 +1,89 @@ +2025-08-10 13:24:05 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:05 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:08 - 开始上传图片: /mnt/save/warning/warning_20250810_132407.jpg +2025-08-10 13:24:09 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:10 - 开始上传图片: /mnt/save/warning/warning_20250810_132410.jpg +2025-08-10 13:24:11 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:12 - 开始上传图片: /mnt/save/warning/warning_20250810_132411.jpg +2025-08-10 13:24:14 - 使用经纬度上传: lon=12015.97019, lat=3129.92541 +2025-08-10 13:24:14 - 开始上传图片: /mnt/save/warning/warning_20250810_132413.jpg +2025-08-10 13:24:15 - 使用经纬度上传: lon=12015.97019, lat=3129.92541 +2025-08-10 13:24:16 - 开始上传图片: /mnt/save/warning/warning_20250810_132415.jpg +2025-08-10 13:24:18 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:18 - 数据库插入成功 +2025-08-10 13:24:18 - 数据库插入成功 +2025-08-10 13:24:18 - 上传线程结束: /mnt/save/warning/warning_20250810_132354.jpg: 成功! +2025-08-10 13:24:18 - 数据库插入成功 +2025-08-10 13:24:18 - 上传线程结束: /mnt/save/warning/warning_20250810_132356.jpg: 成功! +2025-08-10 13:24:18 - 上传线程结束: /mnt/save/warning/warning_20250810_132353.jpg: 成功! +2025-08-10 13:24:18 - 数据库插入成功 +2025-08-10 13:24:18 - 上传线程结束: /mnt/save/warning/warning_20250810_132350.jpg: 成功! +2025-08-10 13:24:18 - 开始上传图片: /mnt/save/warning/warning_20250810_132416.jpg +2025-08-10 13:24:18 - 开始上传图片: /mnt/save/warning/warning_20250810_132417.jpg +2025-08-10 13:24:18 - 开始上传图片: /mnt/save/warning/warning_20250810_132418.jpg +2025-08-10 13:24:18 - 数据库插入成功 +2025-08-10 13:24:18 - 上传线程结束: /mnt/save/warning/warning_20250810_132357.jpg: 成功! +2025-08-10 13:24:19 - 数据库插入成功 +2025-08-10 13:24:19 - 上传线程结束: /mnt/save/warning/warning_20250810_132401.jpg: 成功! +2025-08-10 13:24:19 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:19 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:19 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:19 - 数据库插入成功 +2025-08-10 13:24:19 - 上传线程结束: /mnt/save/warning/warning_20250810_132400.jpg: 成功! +2025-08-10 13:24:20 - 数据库插入成功 +2025-08-10 13:24:20 - 上传线程结束: /mnt/save/warning/warning_20250810_132403.jpg: 成功! +2025-08-10 13:24:20 - 开始上传图片: /mnt/save/warning/warning_20250810_132420.jpg +2025-08-10 13:24:20 - 开始上传图片: /mnt/save/warning/warning_20250810_132419.jpg +2025-08-10 13:24:20 - 数据库插入成功 +2025-08-10 13:24:20 - 上传线程结束: /mnt/save/warning/warning_20250810_132402.jpg: 成功! +2025-08-10 13:24:21 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:21 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:22 - 开始上传图片: /mnt/save/warning/warning_20250810_132421.jpg +2025-08-10 13:24:22 - 开始上传图片: /mnt/save/warning/warning_20250810_132422.jpg +2025-08-10 13:24:23 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:23 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:24 - 开始上传图片: /mnt/save/warning/warning_20250810_132423.jpg +2025-08-10 13:24:24 - 数据库插入成功 +2025-08-10 13:24:24 - 上传线程结束: /mnt/save/warning/warning_20250810_132407.jpg: 成功! +2025-08-10 13:24:25 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:26 - 开始上传图片: /mnt/save/warning/warning_20250810_132424.jpg +2025-08-10 13:24:26 - 开始上传图片: /mnt/save/warning/warning_20250810_132425.jpg +2025-08-10 13:24:26 - 数据库插入成功 +2025-08-10 13:24:26 - 上传线程结束: /mnt/save/warning/warning_20250810_132410.jpg: 成功! +2025-08-10 13:24:28 - 数据库插入成功 +2025-08-10 13:24:28 - 上传线程结束: /mnt/save/warning/warning_20250810_132411.jpg: 成功! +2025-08-10 13:24:28 - 使用经纬度上传: lon=12015.97039, lat=3129.92553 +2025-08-10 13:24:28 - 使用经纬度上传: lon=12015.97039, lat=3129.92553 +2025-08-10 13:24:28 - 开始上传图片: /mnt/save/warning/warning_20250810_132428.jpg +2025-08-10 13:24:28 - 开始上传图片: /mnt/save/warning/warning_20250810_132427.jpg +2025-08-10 13:24:29 - 数据库插入成功 +2025-08-10 13:24:29 - 上传线程结束: /mnt/save/warning/warning_20250810_132413.jpg: 成功! +2025-08-10 13:24:29 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:29 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:30 - 开始上传图片: /mnt/save/warning/warning_20250810_132429.jpg +2025-08-10 13:24:32 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:32 - 开始上传图片: /mnt/save/warning/warning_20250810_132431.jpg +2025-08-10 13:24:33 - 数据库插入成功 +2025-08-10 13:24:33 - 上传线程结束: /mnt/save/warning/warning_20250810_132415.jpg: 成功! +2025-08-10 13:24:34 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:34 - 开始上传图片: /mnt/save/warning/warning_20250810_132433.jpg +2025-08-10 13:24:35 - 数据库插入成功 +2025-08-10 13:24:35 - 数据库插入成功 +2025-08-10 13:24:35 - 上传线程结束: /mnt/save/warning/warning_20250810_132418.jpg: 成功! +2025-08-10 13:24:35 - 上传线程结束: /mnt/save/warning/warning_20250810_132417.jpg: 成功! +2025-08-10 13:24:35 - 数据库插入成功 +2025-08-10 13:24:35 - 上传线程结束: /mnt/save/warning/warning_20250810_132416.jpg: 成功! +2025-08-10 13:24:35 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 13:24:36 - 数据库插入成功 +2025-08-10 13:24:36 - 上传线程结束: /mnt/save/warning/warning_20250810_132420.jpg: 成功! +2025-08-10 13:24:36 - 数据库插入成功 +2025-08-10 13:24:36 - 上传线程结束: /mnt/save/warning/warning_20250810_132419.jpg: 成功! +2025-08-10 13:24:38 - 数据库插入成功 +2025-08-10 13:24:38 - 上传线程结束: /mnt/save/warning/warning_20250810_132422.jpg: 成功! +2025-08-10 13:24:38 - 数据库插入成功 +2025-08-10 13:24:38 - 上传线程结束: /mnt/save/warning/warning_20250810_132421.jpg: 成功! +2025-08-10 13:24:38 - 开始上传图片: /mnt/save/warning/warning_20250810_132437.jpg +2025-08-10 13:24:39 - 数据库插入成功 +2025-08-10 13:24:39 - 上传线程结束: /mnt/save/warning/warning_20250810_132423.jpg: 成功! +2025-08-10 13:24:39 - 使用经纬度上传: lon=1201, lat=3129 +2025-08-10 16:25:27 - 主程序异常: [Errno 2] No such file or directory: '/mnt/save/warning'