/* 本文件 网络请求类需要实现以下功能: 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_; }; }