Compare commits

...

10 Commits

Author SHA1 Message Date
2f21d8b948 updata:http 2025-09-08 12:41:08 +08:00
Quella777
71f4b175ed Writefile 2025-08-16 10:35:26 +08:00
Quella777
c8ce637315 Writefile 2025-08-16 10:33:24 +08:00
Quella777
9bfc4c2538 update 2025-08-15 13:04:21 +08:00
Quella777
8ed879cd90 update 2025-08-15 12:41:16 +08:00
Quella777
72a82ccac2 update 2025-08-15 12:40:44 +08:00
Quella777
140decf42e update 2025-08-14 16:18:21 +08:00
Quella777
f3c7d97edf update 2025-08-13 14:25:26 +08:00
Quella777
1683996d43 update 2025-08-13 11:33:31 +08:00
Quella777
7ee0b69aa6 http 2025-08-12 21:50:33 +08:00
8 changed files with 10831 additions and 93 deletions

View File

@@ -11,8 +11,9 @@ c/c++基本开发库
# 写文件操作
允许原文本进行覆盖写,追加写
允许二进制进行覆盖写,追加写
允许在特定位置进行写操作
可以计算特定符号最后一个字节所在位置
允许在特定位置后面进行插入覆盖操作
允许删除特定字段后面所有内容在进行写操作
可以根据需要计算特定符号最后一个字节或者第一个字节所在位置所在位置
所有操作都添加mutex锁机制 ,保障线程安全
@@ -28,3 +29,8 @@ c/c++基本开发库
所有操作都添加mutex锁机制 ,保障线程安全
# 字符串操作
支持左右空格删除
支持格式化输出

224
include/NetRequest.hpp Normal file
View File

@@ -0,0 +1,224 @@
/*
本文件
网络请求类需要实现以下功能:
1. 发送网络请求
2. 接收网络响应
3. 处理网络请求和响应
4. 实现网络请求和响应的回调函数
5. 实现网络请求和响应的错误处理
6. 实现网络请求和响应的日志记录
7. 实现网络请求和响应的性能统计
8. 实现网络请求和响应的并发控制
9. 实现网络请求和响应的缓存管理
10. 实现网络请求和响应的断点续传
11. 实现网络请求和响应的断点续传
12. 实现网络请求和响应的断点续传
13. 实现网络请求和响应的断点续传
14. 实现网络请求和响应的断点续传
*/
#pragma once
#include "httplib.h"
#include <string>
#include <functional>
#include <optional>
#include <future>
#include <chrono>
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
*/
std::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
*/
std::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
*/
std::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<std::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<std::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<std::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;
private:
struct Impl;
Impl *impl_;
};
}

View File

@@ -49,7 +49,7 @@ namespace QCL
* @param clientSock 客户端Socket描述符
* @param flag:false 非阻塞模式,true 阻塞模式
*/
char *receiveFromClient(int clientSock, bool flag = true);
std::string receiveFromClient(int clientSock, bool flag = true);
/**
* @brief 获取连接客户端的IP和端口
@@ -120,19 +120,6 @@ namespace QCL
*/
bool appendText(const std::string &content);
/**
* @brief 按指定位置写入文本(原文写,线程安全)
*
* 不清空文件内容,仅替换指定位置的内容。
* 若文件不存在,则会自动创建空文件。
*
* @param content 要写入的内容
* @param position 写入位置(默认为文件开头)
* @return true 写入成功
* @return false 写入失败
*/
bool writeOriginal(const std::string &content, std::streampos position = 0);
/**
* @brief 覆盖写二进制文件(线程安全)
* @param data 要写入的二进制数据
@@ -150,15 +137,64 @@ namespace QCL
bool appendBinary(const std::vector<char> &data);
/**
* @brief 计算第一个指定字节序列前的字节数(包含该字节序列本身)
*
* 例如:文件内容是 "ABC***--***XYZ",模式是 "***--***"
* 返回值应为 11"ABC"=3字节 + "***--***"=8字节
*
* @param pattern 要查找的字节模式(支持多字节)
* @return long 字节数(包含匹配模式本身),未找到返回 -1
* @brief 计算第一个指定字节序列前的字节数
* @param pattern 要查找的字节序列
* @param includePattern true 表示返回值包含 pattern 自身长度false 表示不包含
* @return size_t 字节数,如果文件没打开或 pattern 为空则返回 0
*/
long countBytesBeforePattern(const std::string &pattern);
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_; ///< 文件路径
@@ -259,9 +295,17 @@ namespace QCL
/**
* @brief 获取指定字节序列前的字节数(包含该字节序列)
* @param marker 要查找的字节序列(可能不止一个字节)
* @return 如果找到,返回前面部分字节数;找不到返回全文字节数
* @return 如果找到,返回前面部分字节数;找不到返回0
*/
size_t GetBytesBefore(const std::string &marker);
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 检查文件是否存在
@@ -287,4 +331,81 @@ namespace QCL
// 屏蔽所有信号
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

@@ -38,6 +38,7 @@
#include <iomanip> // 流格式控制setw/setprecision
#include <regex> // 正则表达式
#include <filesystem> // 文件系统(C++17)
#include<termios.h>
// ==================== 并发编程支持 ====================
#include <thread> // 线程管理std::thread

850
include/README.md Normal file
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!

8794
include/httplib.h Normal file

File diff suppressed because it is too large Load Diff

563
src/NetRequest.cpp Normal file
View File

@@ -0,0 +1,563 @@
#include "NetRequest.hpp"
#include <mutex>
#include <condition_variable>
#include <unordered_map>
#include <fstream>
#include <sstream>
#include <atomic>
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 &params)
{
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<std::mutex> 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<std::mutex> lk(mtx_);
cv_.wait(lk, [&]{ return active_ < limit_; });
++active_;
}
void leave()
{
std::lock_guard<std::mutex> 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<std::string, CacheEntry> cache;
std::mutex cache_mtx;
void log(const std::string &msg)
{
if (logger) logger(msg);
}
template <typename ClientT>
void apply_client_options(ClientT &cli)
{
const time_t c_sec = static_cast<time_t>(opts.connect_timeout_ms / 1000);
const time_t c_usec = static_cast<time_t>((opts.connect_timeout_ms % 1000) * 1000);
const time_t r_sec = static_cast<time_t>(opts.read_timeout_ms / 1000);
const time_t r_usec = static_cast<time_t>((opts.read_timeout_ms % 1000) * 1000);
const time_t w_sec = static_cast<time_t>(opts.write_timeout_ms / 1000);
const time_t w_usec = static_cast<time_t>((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 &params, 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<std::mutex> lk(impl_->cache_mtx);
impl_->cache.clear();
}
std::optional<HttpResponse> 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<std::mutex> 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;
}
}
std::optional<HttpResponse> 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<double, std::milli>(end - start).count());
if (!result.has_value())
{
impl_->stats.total_errors++;
if (err) *err = local_err;
return std::nullopt;
}
if (impl_->cache_enabled)
{
std::string key = impl_->cache_key(path, query, merged_headers);
std::lock_guard<std::mutex> 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;
}
std::optional<HttpResponse> 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();
std::optional<HttpResponse> 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<double, std::milli>(end - start).count());
if (!result)
{
impl_->stats.total_errors++;
if (err) *err = local_err;
return std::nullopt;
}
if (err) *err = ErrorCode::None;
return result;
}
std::optional<HttpResponse> 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();
std::optional<HttpResponse> 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<double, std::milli>(end - start).count());
if (!result)
{
impl_->stats.total_errors++;
if (err) *err = local_err;
return std::nullopt;
}
if (err) *err = ErrorCode::None;
return result;
}
std::future<std::optional<HttpResponse>> 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<std::optional<HttpResponse>> 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<std::optional<HttpResponse>> 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<size_t>(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<std::streamsize>(data_length));
return static_cast<bool>(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<double, std::milli>(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;
}
}

View File

@@ -156,26 +156,19 @@ namespace QCL
/**
* @brief 单次接收指定客户端数据
* @param clientSock 客户端socket
*
* 注意:此函数在当前设计中未在线程中使用,仅演示用。
*/
char *TcpServer::receiveFromClient(int clientSock, bool flag)
std::string TcpServer::receiveFromClient(int clientSock, bool flag)
{
char buffer[1024];
std::memset(buffer, 0, sizeof(buffer));
ssize_t bytesReceived = 0;
if (flag)
bytesReceived = recv(clientSock, buffer, sizeof(buffer) - 1, 0);
else
bytesReceived = recv(clientSock, buffer, sizeof(buffer) - 1, MSG_DONTWAIT);
int flags = flag ? 0 : MSG_DONTWAIT;
ssize_t bytesReceived = recv(clientSock, buffer, sizeof(buffer) - 1, flags);
if (bytesReceived <= 0)
{
return nullptr;
}
return strdup(buffer); // 返回动态分配的字符串副本
return {};
return std::string(buffer, bytesReceived);
}
/**
@@ -238,30 +231,6 @@ namespace QCL
return writeToFile(content, std::ios::out | std::ios::app);
}
/**
* @brief 按位置写入(线程安全)
*/
bool WriteFile::writeOriginal(const std::string &content, std::streampos position)
{
std::lock_guard<std::mutex> lock(writeMutex_);
std::ofstream file(filePath_, std::ios::in | std::ios::out);
if (!file.is_open())
{
// 文件不存在则创建
file.open(filePath_, std::ios::out);
file.close();
file.open(filePath_, std::ios::in | std::ios::out);
}
if (!file.is_open())
return false;
file.seekp(position);
file << content;
file.close();
return true;
}
/**
* @brief 覆盖写二进制(线程安全)
*/
@@ -306,37 +275,168 @@ namespace QCL
return true;
}
/**
* @brief 计算第一个指定字节序列前的字节数(包含该字节序列本身)
*/
long WriteFile::countBytesBeforePattern(const std::string &pattern)
size_t WriteFile::countBytesPattern(const std::string &pattern, bool includePattern)
{
std::lock_guard<std::mutex> lock(writeMutex_);
if (pattern.empty())
return -1;
return 0;
std::ifstream file(filePath_, std::ios::binary); // 二进制模式防止编码干扰
std::ifstream file(filePath_, std::ios::binary);
if (!file.is_open())
return -1;
return 0;
// 将整个文件读入内存
std::vector<char> buffer((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
file.close();
const size_t chunkSize = 4096;
std::string buffer;
buffer.reserve(chunkSize * 2);
// 在 buffer 中查找 pattern
auto it = std::search(buffer.begin(), buffer.end(),
pattern.begin(), pattern.end());
size_t totalRead = 0;
char chunk[chunkSize];
if (it == buffer.end())
while (file.read(chunk, chunkSize) || file.gcount() > 0)
{
return -1; // 没找到
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; // 读完后再累计
}
// 计算从开头到 pattern 结束的字节数
size_t pos = std::distance(buffer.begin(), it);
return static_cast<long>(pos + pattern.size());
return 0;
}
bool WriteFile::writeAfterPatternOrAppend(const std::string &pattern, const std::string &content)
{
std::lock_guard<std::mutex> lock(writeMutex_);
// 读取整个文件
std::ifstream in(filePath_, std::ios::binary);
if (!in.is_open())
return false;
std::string fileData((std::istreambuf_iterator<char>(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<std::mutex> lock(writeMutex_);
// 打开文件读取
std::ifstream in(filePath_, std::ios::binary);
if (!in.is_open())
return false;
std::string fileData((std::istreambuf_iterator<char>(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<std::mutex> lock(writeMutex_);
// 打开文件读取
std::ifstream in(filePath_, std::ios::binary);
if (!in.is_open())
return false;
std::string fileData((std::istreambuf_iterator<char>(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;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -419,32 +519,82 @@ namespace QCL
return buffer;
}
size_t ReadFile::GetBytesBefore(const std::string &marker)
size_t ReadFile::GetBytesBefore(const std::string &marker, bool includeMarker)
{
std::lock_guard<std::mutex> lock(mtx_);
if (!file_.is_open() && !Open())
return 0;
std::ostringstream ss;
ss << file_.rdbuf();
std::string content = ss.str();
file_.clear(); // 清除EOF和错误状态
file_.seekg(0, std::ios::beg); // 回到文件开头
size_t pos = content.find(marker);
if (pos != std::string::npos)
return pos + marker.size();
else
return content.size();
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<char> ReadFile::ReadBytesFrom(size_t pos, size_t count)
{
std::lock_guard<std::mutex> 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<char> buffer(bytes_to_read);
file_.read(buffer.data(), bytes_to_read);
// 实际读取的字节数可能少于请求的数量
buffer.resize(file_.gcount());
return buffer;
}
bool ReadFile::FileExists() const
{
std::lock_guard<std::mutex> lock(mtx_);
return std::filesystem::exists(filename_);
}
size_t ReadFile::GetFileSize() const
{
std::lock_guard<std::mutex> lock(mtx_);
if (!FileExists())
return 0;
return std::filesystem::file_size(filename_);
@@ -468,5 +618,34 @@ namespace QCL
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<unsigned char>(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<unsigned char>(s[end - 1])))
{
--end;
}
return s.substr(0, end);
}
std::string LRtrim(const std::string &s)
{
return Ltrim(Rtrim(s));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}