---
title: "cpp-httplib使用指南"
author: "Perrin Yong"
author_profile: https://www.pystone.net/profile/
published_by: "Perrin Yong"
canonical: https://www.pystone.net/notes/cpp-httplib-usage-guide/
type: note
content_role: unspecified
visibility: public
id_stability: rename-stable
source_path: "10-计算机、信息技术与工程/02-编程语言与运行时/cpp-httplib使用指南.md"
content_hash: e68c4d22173c13190bbba85cd1f6b50003608b87e526b89adf21579bff4576e1
knowledge_version: 224c990773de.5fa8af6e39fa
site_commit: 224c990773de166d23a886306577dd90379529ce
notes_commit: 5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a
---
`cpp-httplib` 是一个轻量级的 C++ HTTP/HTTPS 库，用于构建 HTTP 客户端和服务器。

# cpp-httplib使用指南
## **GET 请求格式**
GET 请求通常用于请求数据，它的特点是将请求参数附加在 URL 中，并且请求体通常为空。

**请求行**：
```text
GET /path/resource?key1=value1&key2=value2 HTTP/1.1
```
- **GET**：请求方法，表示请求资源。
- **/path/resource?key1=value1&key2=value2**：请求的路径及查询参数，查询参数通过 `?` 和 `&` 分隔。
- **HTTP/1.1**：HTTP 协议版本。

**请求头**：
```yaml
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
```

- **Host**：目标服务器的域名。
- **User-Agent**：客户端（如浏览器）的信息。
- **Accept**：客户端可以处理的内容类型，如 `text/html`。

- **空行**：
    - 请求头后跟一个空行，表示请求头结束。

- **请求体**：
    - **GET 请求没有请求体**，所有数据通过 URL 查询字符串传递。

## GET 响应格式
**状态行**：
```text
HTTP/1.1 200 OK
```
- **HTTP/1.1**：HTTP 协议版本。
- **200**：状态码，表示请求成功。
- **OK**：状态描述。

**响应头**：
```yaml
Content-Type: text/html
Content-Length: 138
```
- **Content-Type**：响应体的 MIME 类型，如 `text/html` 表示 HTML 页面。
- **Content-Length**：响应体的长度，以字节为单位。

**空行**：
- 响应头后跟一个空行，表示响应头结束。

**响应体**：
```markdown
<h1>Hello, World!</h1>
```
- **响应体**包含实际的内容，如 HTML 页面、JSON 数据等。

## POST 请求格式
POST 请求用于向服务器提交数据，如表单数据或文件上传。与 GET 不同，POST 请求的数据在请求体中传输，而不是通过 URL。

**请求行**：
```text
POST /path/resource HTTP/1.1
```
- **POST**：请求方法，表示提交数据。
- **/path/resource**：请求的路径，不包括查询参数。
- **HTTP/1.1**：HTTP 协议版本。

**请求头**：
```text
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**：请求体的长度。

**空行**：
- 请求头后跟一个空行，表示请求头结束。

**请求体**：
```text
key1=value1&key2=value2
```
- **请求体**包含实际发送的数据，如表单字段或文件内容。

## **POST 响应格式**
与 GET 响应类似，POST 响应包含状态行、响应头、空行和响应体。
**状态行**：
```text
HTTP/1.1 200 OK
```

**响应头**：
```text
Content-Type: application/json
Content-Length: 85
```

- **空行**：
    - 响应头后跟一个空行，表示响应头结束。
- **响应体**：
```json
{
  "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 客户端

```cpp
httplib::Client cli("httpbin.org");
```

- 创建一个连接到 `httpbin.org` 的 HTTP 客户端。

### 发起 GET 请求

```cpp
auto res = cli.Get("/hello");
```

- 发送一个 `GET` 请求到 `httpbin.org/hello`，并返回响应对象 `res`。

`cli.Get(const char* path, const Headers& headers, ResponseHandler response_handler, ContentReceiver content_receiver)`
最完整的形式，既可以指定请求头，还可以添加两个回调函数：

```cpp
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]` 的含义：
1. **`&`（按引用捕获）**：
    - 捕获列表中的 `&` 表示按引用捕获外部作用域中的所有变量。这意味着 lambda 表达式可以通过引用访问其作用域中的所有变量，因此可以修改这些变量的值。
    - 例如，如果外部作用域中有一个变量 `counter`，那么在 lambda 中对 `counter` 的修改会直接影响到外部的 `counter` 变量。
2. **`data`（按值捕获）**：
    - 捕获列表中的 `data` 指定了一个特定的变量 `data`，并且是按值捕获。这意味着 lambda 表达式内部有一个 `data` 的副本，lambda 内对 `data` 的修改不会影响到外部的 `data` 变量。
    - 在这段代码中，`data` 是一个指针，按值捕获意味着 lambda 捕获了 `data` 指针的副本，而不是指针所指向的对象的副本。换句话说，lambda 内部可以使用 `data` 指针访问实际的字符串数据，但不能改变 `data` 指针本身指向的地址。

### 发起 POST 请求

```cpp
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` 请求时，它会：
1. 检查 URL `/resource/foo` 对应的资源是否存在。
2. 使用请求体中的 `"text"` 作为新的资源内容，替换或创建资源。
3. 返回一个响应，通常带有状态码（如 200 OK 表示成功，或 201 Created 表示资源已创建）。

使用场景:
- **更新资源**：客户端向服务器发送更新请求，例如更新用户信息、配置文件或其他可修改的资源。
- **创建资源**：如果服务器支持在目标资源不存在时创建新资源，`PUT` 也可以用于创建新资源。


## Client 配置选项

### 启用 Keep-Alive

```cpp
cli.set_keep_alive(true);
```

- 启用 HTTP Keep-Alive，保持与服务器的连接，避免每次请求都重新建立连接。

### 处理重定向

```cpp
cli.set_follow_location(true);
auto res = cli.Get("/");
```

- 自动跟随服务器返回的重定向（例如 `301`、`302`）。

### 自定义请求头
```cpp
auto res = cli.Get("/path", {{"Authorization", "Bearer token"}});
```

- 在请求中添加自定义头信息。

### 分块传输响应处理

```cpp
auto res = cli.Get("/stream", Headers(),
  [&](const Response &response) { return true; },
  [&](const char *data, size_t data_length) { return true; });

```

- 使用回调函数逐块处理服务器返回的数据，适合处理大数据或流媒体内容。
### Range 请求
```cpp
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 服务器

```cpp
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 请求。

### 文件请求处理
```cpp
svr.set_file_request_handler([](const httplib::Request &req, httplib::Response &res) {
  // Custom logic before serving a file
});

```
- 处理静态文件请求前自定义处理逻辑。


### 错误处理
```cpp
svr.set_error_handler([](const httplib::Request &req, httplib::Response &res) {
  res.set_content("Error Status: %d", "text/html");
});

```

- 当服务器返回非 2xx 状态码时，使用自定义错误处理函数生成错误响应。

### 预处理路由

在处理客户端请求之前，这个处理器会先执行，允许你在实际路由处理器之前对请求进行拦截和处理。

```cpp
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 数据处理

```cpp
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 数据传输
```cpp
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`

- **用途**：处理带有文件上传的表单数据。

```cpp
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` 请求。

```cpp
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

- **用途**：服务器分块或流式发送响应数据，适合大数据或长时间运行的请求。

```cpp
#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`。

也可以不通过参数设定数据的总长度：
```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;
    });
});

```

#### 2. 分块传输编码 (Chunked Transfer Encoding)

- **用途**：处理数据流，使用 HTTP 的 `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;
    }
  );
});
```

## 客户端 (Client)
### 发送数据（调用POST）

#### 1. 发送 `multipart/form-data` 数据
- **用途**：客户端发送带有文件或字段的 `multipart/form-data` 请求。

```cpp
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` 请求的格式**

**请求行与请求头**
```css
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` 分隔，并且每个部分的格式如下：
**普通字段**
```css
--boundary
Content-Disposition: form-data; name="field_name"

field_value
```
**文件字段**
```css
--boundary
Content-Disposition: form-data; name="file_field_name"; filename="filename.jpg"
Content-Type: image/jpeg

(二进制文件数据)
```

**结束标记**
```css
--boundary--
```

假设你正在上传一个包含文本字段和文件的表单，表单字段包括 `text_field` 和 `file_field`。HTTP 请求可能如下所示：
```css
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` 动态提供数据块。

```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;
  },
  "text/plain");
```

#### 3. 分块传输编码 (Chunked Transfer Encoding)
- **用途**：客户端使用 `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;
  },
  "text/plain");

```

### 接收数据（调用GET）

#### 1. 使用 ContentReceiver 接收数据

```cpp
std::string body;

auto res = cli.Get("/large-data",
  [&](const char *data, size_t data_length) {
    body.append(data, data_length);
    return true;
  });

```


#### 2. 进度回调

```cpp
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 表示继续下载
    }
);

```
