Files
Netralib/include/NetRequest.hpp

344 lines
15 KiB
C++
Raw Permalink Normal View History

2025-09-08 12:41:08 +08:00
/*
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
*/
#pragma once
#include "httplib.h"
#include <string>
#include <functional>
#include <future>
#include <chrono>
2025-09-08 15:12:25 +08:00
// C++17/14 可选类型回退适配:统一使用 ntq::optional / ntq::nullopt
#if defined(__has_include)
#if __has_include(<optional>)
#include <optional>
// 仅当启用了 C++17 或库声明了 optional 功能时,才使用 std::optional
#if defined(__cpp_lib_optional) || (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
namespace ntq { template <typename T> using optional = std::optional<T>; }
namespace ntq { constexpr auto nullopt = std::nullopt; using nullopt_t = decltype(std::nullopt); }
#elif __has_include(<experimental/optional>)
#include <experimental/optional>
namespace ntq { template <typename T> using optional = std::experimental::optional<T>; }
namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); }
#else
#include <utility>
namespace ntq {
struct nullopt_t { explicit constexpr nullopt_t(int) {} };
static constexpr nullopt_t nullopt{0};
template <typename T>
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(<experimental/optional>)
#include <experimental/optional>
namespace ntq { template <typename T> using optional = std::experimental::optional<T>; }
namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); }
#else
#include <utility>
namespace ntq {
struct nullopt_t { explicit constexpr nullopt_t(int) {} };
static constexpr nullopt_t nullopt{0};
template <typename T>
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 <optional>
namespace ntq { template <typename T> using optional = std::optional<T>; }
namespace ntq { constexpr auto nullopt = std::nullopt; using nullopt_t = decltype(std::nullopt); }
#else
#include <experimental/optional>
namespace ntq { template <typename T> using optional = std::experimental::optional<T>; }
namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); }
#endif
#endif
2025-09-08 12:41:08 +08:00
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 默认为 80https 通常为 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<void(const std::string &)>; ///< 日志回调类型
/**
* @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
*/
2025-09-08 15:12:25 +08:00
ntq::optional<HttpResponse> Get(const std::string &path,
2025-09-08 12:41:08 +08:00
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
*/
2025-09-08 15:12:25 +08:00
ntq::optional<HttpResponse> PostJson(const std::string &path,
2025-09-08 12:41:08 +08:00
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
*/
2025-09-08 15:12:25 +08:00
ntq::optional<HttpResponse> PostForm(const std::string &path,
2025-09-08 12:41:08 +08:00
const httplib::Params &form,
const httplib::Headers &headers = {},
ErrorCode *err = nullptr);
/**
* @brief GET
* @return std::future
*/
2025-09-08 15:12:25 +08:00
std::future<ntq::optional<HttpResponse>> GetAsync(const std::string &path,
2025-09-08 12:41:08 +08:00
const httplib::Params &query = {},
const httplib::Headers &headers = {},
ErrorCode *err = nullptr);
/**
* @brief POST JSON
* @return std::future
*/
2025-09-08 15:12:25 +08:00
std::future<ntq::optional<HttpResponse>> PostJsonAsync(const std::string &path,
2025-09-08 12:41:08 +08:00
const std::string &json,
const httplib::Headers &headers = {},
ErrorCode *err = nullptr);
/**
* @brief POST
* @return std::future
*/
2025-09-08 15:12:25 +08:00
std::future<ntq::optional<HttpResponse>> PostFormAsync(const std::string &path,
2025-09-08 12:41:08 +08:00
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 206false
*/
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;
2025-09-08 15:12:25 +08:00
/**
* @brief 便 URL GET
* @param url http://host:port/path?x=1 或 https://host/path
* @param headers
* @param err
*/
static ntq::optional<HttpResponse> 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<HttpResponse> 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<HttpResponse> QuickPostForm(const std::string &url,
const httplib::Params &form,
const httplib::Headers &headers = {},
ErrorCode *err = nullptr);
2025-09-08 12:41:08 +08:00
private:
struct Impl;
Impl *impl_;
};
}