# HTTP Request / HTTP 请求组件
**EN** — Queue-based HTTP/HTTPS request component for ESP-IDF. Requests are submitted from any task, executed one by one in a dedicated background task, and the response body is delivered to your callback in chunks so that large responses never need to be buffered in RAM as a whole.
**中文** — 面向 ESP-IDF 的队列式 HTTP/HTTPS 请求组件。任意任务都可以提交请求,由独立的后台任务串行执行,响应体分片回调给用户,大响应无需整体缓存在 RAM 中。
---
## Features / 功能特性
**EN**
- **Queue-based** — submit a request from any task, never block the caller
- **Chunked streaming callback** — the response body is delivered piece by piece; per-request chunk size
- **Result callback** — every request reports success/failure plus the HTTP status code exactly once
- **All HTTP methods** — GET / POST / PUT / DELETE / HEAD / PATCH / OPTIONS
- **Chunked transfer encoding aware** — responses without `Content-Length` are received correctly
- **HTTPS supported** — server verification through the built-in ESP x509 certificate bundle
- **Zero memory management for the caller** — URL and body strings are copied internally
- **Incomplete transfer detection** — a response cut short by the peer is reported as a failure
**中文**
- **队列驱动**:任意任务提交请求,调用方永不阻塞
- **分片流式回调**:响应体分片回调,可逐片处理,支持自定义分片长度
- **结果回调**:每次请求无论成功失败都回调一次,携带 HTTP 状态码
- **支持全部 HTTP 方法**:GET / POST / PUT / DELETE / HEAD / PATCH / OPTIONS
- **兼容 chunked 编码**:服务器不返回 `Content-Length` 时也能正确接收
- **支持 HTTPS**:使用 ESP 内置 x509 根证书包校验服务器
- **调用方不管内存**:URL 与请求体由组件内部拷贝,可安全传入字符串常量
- **不完整传输检测**:对端提前断开会被识别为失败,而不是当成正常结束
---
## Requirements / 运行环境
**EN**
- ESP-IDF >= 5.0
- Targets: ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6
**中文**
- ESP-IDF >= 5.0
- 支持目标:ESP32、ESP32-S2、ESP32-S3、ESP32-C3、ESP32-C6
---
## Installation / 安装
**EN** — Add the component to your project's `idf_component.yml`:
**中文** — 在工程的 `idf_component.yml` 中添加依赖:
```yaml
dependencies:
junhui93/http_request: "^1.0.0"
```
**EN** — Or copy the `http_request` folder into your project's `components/` directory.
**中文** — 也可以将 `http_request` 文件夹拷贝到工程的 `components/` 目录下。
---
## Quick Start / 快速开始
```c
#include "esp_log.h"
#include "http_request.h"
/* EN: Called once per received chunk. Not a C string — always use data_len. */
/* 中文: 每收到一片数据调用一次。注意不是完整字符串,请按 data_len 使用。 */
static void on_data(const char *data, int data_len, int64_t total_len)
{
ESP_LOGI("app", "got %d bytes (total %lld)", data_len, (long long)total_len);
printf("%.*s", data_len, data);
}
/* EN: Called exactly once per request, success or failure. */
/* 中文: 每次请求调用一次,成功或失败都会调用。 */
static void on_done(http_request_result_t result, int status_code)
{
ESP_LOGI("app", "request finished: result=%d, status=%d", (int)result, status_code);
}
void app_main(void)
{
/* ... connect to WiFi first ... */
/* EN: The queue depth decides how many requests may be pending. */
/* 中文: 队列深度决定最多允许积压多少个请求。 */
QueueHandle_t queue = xQueueCreate(4, sizeof(http_request_item_t));
http_request_init(queue);
http_request_get("http://example.com", on_data, on_done);
http_request_post("http://httpbin.org/post", "{\"hello\":\"world\"}", on_data, on_done);
}
```
**EN** — For full control use the `http_request_config_t` form:
**中文** — 需要完整控制时使用 `http_request_config_t`:
```c
const http_request_config_t config = {
.url = "https://example.com/api/v1/data",
.method = HTTP_METHOD_PUT,
.post_data = "{\"value\":123}",
.content_type = "application/json", /* optional, this is the default */
.chunk_size = 512, /* per-request receive chunk */
.timeout_ms = 10000, /* per-request timeout */
.on_data = on_data,
.on_done = on_done,
};
ESP_ERROR_CHECK(http_request_submit(&config));
```
---
## API / 接口说明
| Function / 函数 | Description / 说明 |
| --- | --- |
| `http_request_init(QueueHandle_t queue)` | **EN** Start the background task. The queue must be created with `xQueueCreate(depth, sizeof(http_request_item_t))`. Repeated calls are ignored. **中文** 启动后台任务。队列必须用 `xQueueCreate(深度, sizeof(http_request_item_t))` 创建。重复调用会被忽略。 |
| `http_request_submit(&config)` | **EN** Submit a request. Strings are copied internally, the caller owns nothing. Returns `ESP_OK` / `ESP_ERR_INVALID_ARG` / `ESP_ERR_INVALID_STATE` / `ESP_ERR_TIMEOUT` (queue full) / `ESP_ERR_NO_MEM`. **中文** 提交请求,字符串由组件内部拷贝,调用方不负责释放。返回 `ESP_OK` / `ESP_ERR_INVALID_ARG` / `ESP_ERR_INVALID_STATE` / `ESP_ERR_TIMEOUT`(队列满)/ `ESP_ERR_NO_MEM`。 |
| `http_request_get(url, on_data, on_done)` | **EN** Convenience wrapper for a GET request. **中文** GET 请求便捷封装。 |
| `http_request_post(url, post_data, on_data, on_done)` | **EN** Convenience wrapper for a POST request. **中文** POST 请求便捷封装。 |
| `http_request_deinit(void)` | **EN** Delete the task, drain the queue and release the HTTP client. Call only when no request is running. **中文** 删除任务、清空队列残留内存并释放 HTTP 客户端。请在确认没有请求正在执行时调用。 |
### Callbacks / 回调
**EN**
- `http_request_callback_t(data, data_len, total_len)` — invoked for every received chunk. `total_len` is `-1` when the server uses chunked encoding. The buffer is terminated with `'\0'`, but always honour `data_len` because binary payloads may contain `'\0'`.
- `http_request_done_callback_t(result, status_code)` — invoked once per request. `status_code` is `0` when no response header was received.
- Both callbacks run in the HTTP task context — do not block for long inside them.
**中文**
- `http_request_callback_t(data, data_len, total_len)` — 每收到一片数据调用一次。服务器使用 chunked 编码时 `total_len` 为 `-1`。缓冲区末尾已补 `'\0'`,但仍请以 `data_len` 为准,因为二进制数据里可能含 `'\0'`。
- `http_request_done_callback_t(result, status_code)` — 每次请求调用一次。未取到响应头时 `status_code` 为 `0`。
- 两个回调都在 HTTP 任务上下文中执行,请不要在里面长时间阻塞。
### Result codes / 结果码
| Value / 取值 | Meaning / 含义 |
| --- | --- |
| `HTTP_REQUEST_OK` | **EN** finished, check `status_code` **中文** 请求完成,具体看 `status_code` |
| `HTTP_REQUEST_FAIL_PARAM` | **EN** invalid URL **中文** 链接非法 |
| `HTTP_REQUEST_FAIL_NO_MEM` | **EN** out of memory **中文** 内存不足 |
| `HTTP_REQUEST_FAIL_OPEN` | **EN** connection could not be established **中文** 建立连接失败 |
| `HTTP_REQUEST_FAIL_WRITE` | **EN** request body could not be sent **中文** 请求体发送失败 |
| `HTTP_REQUEST_FAIL_HEADER` | **EN** response header could not be read **中文** 获取响应头失败 |
| `HTTP_REQUEST_FAIL_RECV` | **EN** body read error or incomplete transfer **中文** 响应体读取出错或不完整 |
---
## Notes / 注意事项
**EN**
- `http_request_deinit()` deletes the task, so do not call it while a request is in flight.
- The HTTP client handle is created once and reused. `esp_http_client_close()` is called after every request to avoid reusing a connection that the server has already dropped, which means **HTTP keep-alive is not used** — each request performs a new TCP connect (and a new TLS handshake for HTTPS). If you need connection reuse, keep the connection open on success and add retry logic for stale sockets.
- Requests are executed strictly one by one. A slow request blocks the ones behind it in the queue.
- A response with a non-2xx status code is still delivered to `on_data`; check `status_code` in `on_done`.
**中文**
- `http_request_deinit()` 会删除后台任务,请不要在有请求正在执行时调用。
- HTTP 客户端句柄常驻复用,但每轮请求结束都会调用 `esp_http_client_close()`,以避免复用到已被服务器关闭的连接。因此**没有使用 HTTP 长连接复用**:每次请求都会重新建立 TCP 连接(HTTPS 还要重新握手)。如果需要连接复用,需要在成功时保留连接,并为失效连接的场景增加重试逻辑。
- 请求严格串行执行,一个慢请求会阻塞排在它后面的请求。
- 非 2xx 的响应同样会回调 `on_data`,请在 `on_done` 里检查 `status_code`。
---
## Example / 示例工程
**EN** — See `examples/basic`. It connects to WiFi via the ESP-IDF example connection helper, then issues one GET and one POST request.
**中文** — 见 `examples/basic`,通过 ESP-IDF 示例连接组件联网后,发起一次 GET 和一次 POST 请求。
```bash
cd examples/basic
idf.py set-target esp32
idf.py menuconfig # Example Connection Configuration -> WiFi SSID / Password
idf.py build flash monitor
```
---
## License / 许可证
**EN** — MIT License, see [LICENSE](LICENSE).
**中文** — MIT 协议,详见 [LICENSE](LICENSE)。
idf.py add-dependency "junhui93/http_request^1.0.0"