本文目录 40 个章节
cpp-httplib 是一个轻量级的 C++ HTTP/HTTPS 库,用于构建 HTTP 客户端和服务器。
cpp-httplib使用指南
GET 请求格式
GET 请求通常用于请求数据,它的特点是将请求参数附加在 URL 中,并且请求体通常为空。
请求行:
GET /path/resource?key1=value1&key2=value2 HTTP/1.1
- GET:请求方法,表示请求资源。
- /path/resource?key1=value1&key2=value2:请求的路径及查询参数,查询参数通过
?和&分隔。 - HTTP/1.1:HTTP 协议版本。
请求头:
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
Host:目标服务器的域名。
User-Agent:客户端(如浏览器)的信息。
Accept:客户端可以处理的内容类型,如
text/html。空行:
- 请求头后跟一个空行,表示请求头结束。
请求体:
- GET 请求没有请求体,所有数据通过 URL 查询字符串传递。
GET 响应格式
状态行:
HTTP/1.1 200 OK
- HTTP/1.1:HTTP 协议版本。
- 200:状态码,表示请求成功。
- OK:状态描述。
响应头:
Content-Type: text/html
Content-Length: 138
- Content-Type:响应体的 MIME 类型,如
text/html表示 HTML 页面。 - Content-Length:响应体的长度,以字节为单位。
空行:
- 响应头后跟一个空行,表示响应头结束。
响应体:
<h1>Hello, World!</h1>
- 响应体包含实际的内容,如 HTML 页面、JSON 数据等。
POST 请求格式
POST 请求用于向服务器提交数据,如表单数据或文件上传。与 GET 不同,POST 请求的数据在请求体中传输,而不是通过 URL。
请求行:
POST /path/resource HTTP/1.1
- POST:请求方法,表示提交数据。
- /path/resource:请求的路径,不包括查询参数。
- HTTP/1.1:HTTP 协议版本。
请求头:
Host: www.example.com
User-Agent: Mozilla/5.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
- Content-Type:请求体的数据类型,如
application/x-www-form-urlencoded表示表单数据,multipart/form-data表示文件上传。 - Content-Length:请求体的长度。
空行:
- 请求头后跟一个空行,表示请求头结束。
请求体:
key1=value1&key2=value2
- 请求体包含实际发送的数据,如表单字段或文件内容。
POST 响应格式
与 GET 响应类似,POST 响应包含状态行、响应头、空行和响应体。 状态行:
HTTP/1.1 200 OK
响应头:
Content-Type: application/json
Content-Length: 85
- 空行:
- 响应头后跟一个空行,表示响应头结束。
- 响应体:
{
"status": "success",
"message": "Data received successfully",
"data": {
"key1": "value1",
"key2": "value2"
}
}
- 响应体包含服务器返回的数据,通常是 JSON、HTML、XML 等格式。
常见响应状态码
1xx: 信息响应
- 100 Continue:表示到目前为止一切正常,客户端应继续请求。
- 101 Switching Protocols:服务器已接受客户端的请求,并正在更改协议。 2xx: 成功
- 200 OK:请求已成功,服务器已返回所请求的数据。
- 201 Created:请求已成功并且服务器创建了一个新的资源。
- 202 Accepted:服务器已接受请求,但尚未处理完成。
- 204 No Content:服务器成功处理了请求,但没有返回任何内容。 3xx: 重定向
- 301 Moved Permanently:请求的资源已被永久移动到新的URL。
- 302 Found:请求的资源临时从不同的URL响应请求。
- 304 Not Modified:资源未被修改,客户端可以使用缓存的版本。 4xx: 客户端错误
- 400 Bad Request:服务器无法理解请求,因为请求格式错误。
- 401 Unauthorized:请求需要用户认证。
- 403 Forbidden:服务器理解请求但拒绝执行。
- 404 Not Found:服务器找不到请求的资源。
- 405 Method Not Allowed:请求方法不被允许。 5xx: 服务器错误
- 500 Internal Server Error:服务器在处理请求时遇到错误。
- 501 Not Implemented:服务器不支持请求的方法。
- 502 Bad Gateway:服务器作为网关或代理,从上游服务器收到无效响应。
- 503 Service Unavailable:服务器暂时无法处理请求,通常是由于超载或维护。
- 504 Gateway Timeout:服务器作为网关或代理,没有及时从上游服务器接收到请求。
Client 基本功能
创建 HTTP 客户端
httplib::Client cli("httpbin.org");
- 创建一个连接到
httpbin.org的 HTTP 客户端。
发起 GET 请求
auto res = cli.Get("/hello");
- 发送一个
GET请求到httpbin.org/hello,并返回响应对象res。
cli.Get(const char* path, const Headers& headers, ResponseHandler response_handler, ContentReceiver content_receiver)
最完整的形式,既可以指定请求头,还可以添加两个回调函数:
auto res = cli.Get("/stream", {},
[&](const httplib::Response &response) {
// Handle response headers
return true;
},
[&](const char *data, size_t data_length) {
// Handle data chunks
return true;
});
response_handler:在接收到服务器的响应头时调用。content_receiver:在接收到响应体的每个数据块时调用。
知识补充 --- lambda 表达式的捕获列表: 在 C++ 中,lambda 表达式的方括号部分(也称为捕获列表)用来指定 lambda 表达式可以捕获并使用的外部变量。方括号中的内容决定了 lambda 表达式如何访问它所在的作用域中的变量。
捕获列表 [&, data] 的含义:
&(按引用捕获):- 捕获列表中的
&表示按引用捕获外部作用域中的所有变量。这意味着 lambda 表达式可以通过引用访问其作用域中的所有变量,因此可以修改这些变量的值。 - 例如,如果外部作用域中有一个变量
counter,那么在 lambda 中对counter的修改会直接影响到外部的counter变量。
- 捕获列表中的
data(按值捕获):- 捕获列表中的
data指定了一个特定的变量data,并且是按值捕获。这意味着 lambda 表达式内部有一个data的副本,lambda 内对data的修改不会影响到外部的data变量。 - 在这段代码中,
data是一个指针,按值捕获意味着 lambda 捕获了data指针的副本,而不是指针所指向的对象的副本。换句话说,lambda 内部可以使用data指针访问实际的字符串数据,但不能改变data指针本身指向的地址。
- 捕获列表中的
发起 POST 请求
auto res = cli.Post("/post", "body content", "text/plain");
- 向指定路径发送
POST请求,并附带请求体和Content-Type。
PUT请求
res = cli.Put("/resource/foo", "text", "text/plain"); 这行代码使用 cpp-httplib 库中的 httplib::Client 类向服务器发送了一个 HTTP PUT 请求。
HTTP 的 PUT 方法通常用于更新服务器上的某个资源。它与 POST 不同,PUT 是幂等的,这意味着同样的 PUT 请求被多次发送,其结果应是相同的。如果资源 /resource/foo 已经存在,PUT 请求会更新它;如果资源不存在,则 PUT 请求通常会创建它。
当服务器收到这个 PUT 请求时,它会:
- 检查 URL
/resource/foo对应的资源是否存在。 - 使用请求体中的
"text"作为新的资源内容,替换或创建资源。 - 返回一个响应,通常带有状态码(如 200 OK 表示成功,或 201 Created 表示资源已创建)。
使用场景:
- 更新资源:客户端向服务器发送更新请求,例如更新用户信息、配置文件或其他可修改的资源。
- 创建资源:如果服务器支持在目标资源不存在时创建新资源,
PUT也可以用于创建新资源。
Client 配置选项
启用 Keep-Alive
cli.set_keep_alive(true);
- 启用 HTTP Keep-Alive,保持与服务器的连接,避免每次请求都重新建立连接。
处理重定向
cli.set_follow_location(true);
auto res = cli.Get("/");
- 自动跟随服务器返回的重定向(例如
301、302)。
自定义请求头
auto res = cli.Get("/path", {{"Authorization", "Bearer token"}});
- 在请求中添加自定义头信息。
分块传输响应处理
auto res = cli.Get("/stream", Headers(),
[&](const Response &response) { return true; },
[&](const char *data, size_t data_length) { return true; });
- 使用回调函数逐块处理服务器返回的数据,适合处理大数据或流媒体内容。
Range 请求
auto res = cli.Get("/range/32", {httplib::make_range_header({{1, 10}})});
使用
Range头部,只请求服务器上资源的特定字节范围。cli.Get("/range/32", {...});发起了一个GET请求,请求路径是/range/32,它会返回一个长度为 32 字节的资源。第二个参数
{httplib::make_range_header({{1, 10}})}是一个Range头的设置,表示请求服务器返回文件从字节 1 到字节 10 的内容。
Server基本功能
创建 HTTP 服务器
httplib::Server svr;
svr.Get("/hi", [](const httplib::Request &req, httplib::Response &res) {
res.set_content("Hello World!", "text/plain");
});
svr.listen("localhost", 8080);
- 创建一个 HTTP 服务器并监听
localhost:8080端口,响应/hi路径的 GET 请求。
文件请求处理
svr.set_file_request_handler([](const httplib::Request &req, httplib::Response &res) {
// Custom logic before serving a file
});
- 处理静态文件请求前自定义处理逻辑。
错误处理
svr.set_error_handler([](const httplib::Request &req, httplib::Response &res) {
res.set_content("Error Status: %d", "text/html");
});
- 当服务器返回非 2xx 状态码时,使用自定义错误处理函数生成错误响应。
预处理路由
在处理客户端请求之前,这个处理器会先执行,允许你在实际路由处理器之前对请求进行拦截和处理。
svr.set_pre_routing_handler([](const httplib::Request &req, httplib::Response &res) {
if (req.path == "/hello") {
res.set_content("world", "text/html");
return httplib::Server::HandlerResponse::Handled;
}
return httplib::Server::HandlerResponse::Unhandled;
});
Server 高级功能
Multipart 数据处理
svr.Post("/multipart", [&](const httplib::Request &req, httplib::Response &res) {
if (req.is_multipart_form_data()) {
req.get_file_value("name1");
// Handle file content
}
});
- 处理
multipart/form-data类型的 POST 请求,适用于文件上传等场景。
Stream 数据传输
const size_t DATA_CHUNK_SIZE = 4;
svr.Get("/stream", [&](const httplib::Request &req, httplib::Response &res) {
auto data = new std::string("abcdefg");
res.set_content_provider(
data->size(), "text/plain",
[&, data](size_t offset, size_t length, httplib::DataSink &sink) {
sink.write(&data->at(offset), std::min(length, DATA_CHUNK_SIZE));
return true;
},
[data](bool success) { delete data; });
});
- 使用
set_content_provider实现数据流式传输,适合大文件或实时数据传输。
收发数据进阶
服务器端 (Server)
接受数据(处理POST)
1. 处理multipart/form-data
- 用途:处理带有文件上传的表单数据。
svr.Post("/multipart", [&](const auto& req, auto& res) {
auto size = req.files.size(); // 获取上传文件的数量
auto ret = req.has_file("name1"); // 检查是否有名为 "name1" 的文件
const auto& file = req.get_file_value("name1"); // 获取文件内容和元数据
});
2. 使用 ContentReader 处理内容
- 用途:接收和处理大数据流或文件上传,适用于
multipart/form-data以及普通的POST请求。
svr.Post("/content_receiver",
[&](const Request &req, Response &res, const ContentReader &content_reader) {
if (req.is_multipart_form_data()) {
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;
});
}
});
content_reader(...):content_reader 是一个回调函数,用于读取和处理 multipart/form-data 请求的每个部分。它接收两个回调:
- 第一个回调用于处理每个文件部分的元数据(如文件名、内容类型),并将其添加到
files容器中。 - 第二个回调用于处理文件的实际内容,将读取到的数据追加到相应文件的
content字段中。 content_reader会一直阻塞,直到所有表单字段和文件内容都被读取完成。
发送数据(处理GET)
1. 发送数据 - 分块+流式 ContentProvider
- 用途:服务器分块或流式发送响应数据,适合大数据或长时间运行的请求。
#include "httplib.h"
#include <iostream>
const size_t DATA_CHUNK_SIZE = 4; // 每次发送的数据块大小
int main() {
httplib::Server svr;
svr.Get("/stream", [&](const httplib::Request &req, httplib::Response &res) {
auto data = std::make_shared<std::string>("abcdefg"); // 需要发送的完整数据
res.set_content_provider(
data->size(), // 设置内容长度
"text/plain", // 设置内容类型
[data](size_t offset, size_t length, httplib::DataSink &sink) {
// 计算本次要发送的数据长度
size_t chunk_size = std::min(length, DATA_CHUNK_SIZE);
// 发送数据块
sink.write(data->data() + offset, chunk_size);
// 返回 true 继续发送下一块,直到所有数据发送完成
return true;
},
[data](bool success) {
if (success) {
std::cout << "Data sent successfully!" << std::endl;
} else {
std::cerr << "Failed to send data!" << std::endl;
}
}
);
});
svr.listen("localhost", 8080);
return 0;
}
cpp-httplib 内部自动管理 offset 和 length。当你使用 content_provider 时,cpp-httplib 会根据每次调用 sink.write 时的数据长度自动更新 offset。
也可以不通过参数设定数据的总长度:
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;
});
});
2. 分块传输编码 (Chunked Transfer Encoding)
- 用途:处理数据流,使用 HTTP 的
Chunked Transfer Encoding,适合动态生成内容或长时间流式传输的情况。
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;
}
);
});
客户端 (Client)
发送数据(调用POST)
1. 发送 multipart/form-data 数据
- 用途:客户端发送带有文件或字段的
multipart/form-data请求。
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);
multipart/form-data 请求的格式
请求行与请求头
POST /path HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
- Content-Type:
multipart/form-data指定了请求体包含多个部分,并且这些部分之间用boundary分隔。 - boundary: 是一个字符串,用于分隔请求体中的不同部分。它通常是由
----和一个随机生成的字符串组成,如----WebKitFormBoundary7MA4YWxkTrZu0gW。
请求体
请求体包含实际的数据,分为多个部分,每个部分由 boundary 分隔,并且每个部分的格式如下:
普通字段
--boundary
Content-Disposition: form-data; name="field_name"
field_value
文件字段
--boundary
Content-Disposition: form-data; name="file_field_name"; filename="filename.jpg"
Content-Type: image/jpeg
(二进制文件数据)
结束标记
--boundary--
假设你正在上传一个包含文本字段和文件的表单,表单字段包括 text_field 和 file_field。HTTP 请求可能如下所示:
POST /upload HTTP/1.1
Host: www.example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="text_field"
sample text
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file_field"; filename="example.jpg"
Content-Type: image/jpeg
(binary data of example.jpg)
----WebKitFormBoundary7MA4YWxkTrZu0gW--
2. 使用 ContentProvider 发送数据
- 用途:客户端流式发送大数据,通过
ContentProvider动态提供数据块。
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;
},
"text/plain");
3. 分块传输编码 (Chunked Transfer Encoding)
- 用途:客户端使用
Chunked Transfer Encoding发送数据流。
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;
},
"text/plain");
接收数据(调用GET)
1. 使用 ContentReceiver 接收数据
std::string body;
auto res = cli.Get("/large-data",
[&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
2. 进度回调
httplib::Client cli("http://example.com");
std::string body;
auto res = cli.Get(
"/large-data",
[&](const char *data, size_t data_length) {
// 数据接收回调
body.append(data, data_length);
return true; // 返回 true 表示继续接收数据
},
[&](uint64_t len, uint64_t total) {
// 进度回调
printf("%llu / %llu bytes => %d%% complete\n",
len, total,
(int)(len * 100 / total));
return true; // 返回 true 表示继续下载
}
);