This commit is contained in:
2025-10-24 16:56:35 +08:00
commit 7462b7aa93
49 changed files with 28483 additions and 0 deletions

View File

@@ -0,0 +1,344 @@
/*
本文件
网络请求类需要实现以下功能:
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>
// 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
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
*/
ntq::optional<HttpResponse> 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<HttpResponse> 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<HttpResponse> PostForm(const std::string &path,
const httplib::Params &form,
const httplib::Headers &headers = {},
ErrorCode *err = nullptr);
/**
* @brief 异步 GET 请求
* @return std::future用于获取响应结果
*/
std::future<ntq::optional<HttpResponse>> GetAsync(const std::string &path,
const httplib::Params &query = {},
const httplib::Headers &headers = {},
ErrorCode *err = nullptr);
/**
* @brief 异步 POST JSON 请求
* @return std::future用于获取响应结果
*/
std::future<ntq::optional<HttpResponse>> PostJsonAsync(const std::string &path,
const std::string &json,
const httplib::Headers &headers = {},
ErrorCode *err = nullptr);
/**
* @brief 异步 POST 表单请求
* @return std::future用于获取响应结果
*/
std::future<ntq::optional<HttpResponse>> 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 或 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;
/**
* @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);
private:
struct Impl;
Impl *impl_;
};
}

View File

@@ -0,0 +1,426 @@
#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<int> getClientSockets();
/**
* @brief 从服务器的客户端列表中移除并关闭一个客户端socket
* @param clientSock 客户端Socket描述符
*/
void removeClient(int clientSock);
/**
* @brief 非阻塞探测客户端是否已断开(不消耗数据)
* @param clientSock 客户端Socket描述符
* @return true 已断开或发生致命错误false 仍然存活或暂无数据
*/
bool isClientDisconnected(int clientSock);
private:
/**
* @brief 监听并接受新的客户端连接(运行在独立线程中)
*/
void acceptClients();
private:
int serverSock_; ///< 服务器监听Socket描述符
int port_; ///< 服务器监听端口
std::atomic<bool> running_; ///< 服务器运行状态标志(线程安全)
std::vector<std::thread> clientThreads_; ///< 用于处理每个客户端的线程集合
std::thread acceptThread_; ///< 负责监听新连接的线程
std::mutex clientsMutex_; ///< 保护clientSockets_的互斥锁
std::vector<int> 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<char> &data);
/**
* @brief 追加写二进制文件(线程安全)
* @param data 要写入的二进制数据
* @return true 写入成功
* @return false 写入失败
*/
bool appendBinary(const std::vector<char> &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);
void close();
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<char> &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<char> ReadAllBinary();
/**
* @brief 按行读取文本
* @return 每行作为一个字符串的 vector
*/
std::vector<std::string> ReadLines();
/**
* @brief 读取指定字节数
* @param count 要读取的字节数
* @return 实际读取到的字节数据
*/
std::vector<char> 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<char> 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 <typename T>
std::string to_string_any(const T &value)
{
std::ostringstream oss;
oss << value;
return oss.str();
}
// 递归获取 tuple 中 index 对应参数
template <std::size_t I = 0, typename Tuple>
std::string get_tuple_arg(const Tuple &tup, std::size_t index)
{
if constexpr (I < std::tuple_size_v<Tuple>)
{
if (I == index)
return to_string_any(std::get<I>(tup));
else
return get_tuple_arg<I + 1>(tup, index);
}
else
{
throw std::runtime_error("Too few arguments for format string");
}
}
// format 函数
template <typename... Args>
std::string format(const std::string &fmt, const Args &...args)
{
std::ostringstream oss;
std::tuple<const Args &...> 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();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}

View File

@@ -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 <iostream> // 标准输入输出流cin/cout/cerr
#include <string> // std::string类及相关操作
#include <cstring> // C风格字符串操作strcpy/strcmp等
#include <cstdlib> // 通用工具函数atoi/rand/malloc等
#include <cstdio> // C风格IOprintf/scanf
#include <cassert> // 断言宏(调试期检查)
#include <cmath> // 数学函数sin/pow等
#include <ctime> // 时间处理time/clock
#include <csignal> // 信号处理signal/kill
#include <memory> // 智能指针
// ==================== STL容器与算法 ====================
#include <vector> // 动态数组(连续内存容器)
#include <list> // 双向链表
#include <deque> // 双端队列
#include <map> // 有序键值对(红黑树实现)
#include <set> // 有序集合
#include <unordered_map> // 哈希表实现的键值对
#include <unordered_set> // 哈希表实现的集合
#include <stack> // 栈适配器LIFO
#include <queue> // 队列适配器FIFO
#include <algorithm> // 通用算法sort/find等
#include <numeric> // 数值算法accumulate等
#include <iterator> // 迭代器相关
// ==================== 字符串与流处理 ====================
#include <sstream> // 字符串流内存IO
#include <fstream> // 文件流文件IO
#include <iomanip> // 流格式控制setw/setprecision
#include <regex> // 正则表达式
#include <filesystem> // 文件系统(C++17)
#include<termios.h>
// ==================== 并发编程支持 ====================
#include <thread> // 线程管理std::thread
#include <mutex> // 互斥锁mutex/lock_guard
#include <atomic> // 原子操作(线程安全变量)
#include <condition_variable> // 条件变量(线程同步)
// ==================== Linux网络编程 ====================
#include <sys/socket.h> // 套接字基础APIsocket/bind
#include <netinet/in.h> // IPV4/IPV6地址结构体
#include <arpa/inet.h> // 地址转换函数inet_pton等
#include <unistd.h> // POSIX APIclose/read/write
#endif // QCL_INCLUDE_HPP

View File

@@ -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 <httplib.h>
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 = "<p>Error Status: <span style='color:red;'>%d</span></p>";
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 = "<h1>Error 500</h1><p>%s</p>";
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<char> 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<void()> 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 <httplib.h>
#include <iostream>
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&note=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 `<regex>` 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 <httplib.h>
#include <Windows.h>
```
```cpp
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <httplib.h>
```
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!

View File

@@ -0,0 +1,14 @@
#pragma once
/*
主要是用于各种加密
*/
#include "QCL_Include.hpp"
using namespace std;
namespace encrypt
{
string MD5(const string &info);
}

File diff suppressed because it is too large Load Diff