Files
2026-08-13 16:50:52 +08:00

484 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LISA HTTP 组件
基于 HTTP 协议的客户端组件,为 ARCS 平台提供完整的 HTTP 通信功能,支持多种请求方法、同步和异步传输、文件下载、分块传输等功能。
## 功能特性
- **HTTP 方法支持**: 支持 GET、POST、PUT、PATCH、DELETE 等标准 HTTP 方法
- **传输模式**: 支持同步传输、分块传输和文件下载多种传输模式
- **HTTPS 兼容**: 自动将 HTTPS 转换为 HTTP 处理,简化安全连接使用
- **灵活配置**: 支持自定义头部、请求体、超时时间等配置选项
- **回调机制**: 提供灵活的数据接收回调函数,支持流式数据处理
- **内存管理**: 自动内存管理,支持动态分配和释放 HTTP 实例
- **错误处理**: 完善的错误代码体系,便于问题定位和处理
- **大文件支持**: 分块传输模式支持大文件下载,单个数据块最大 4096 字节
## 配置选项
`prj.conf` 中启用组件:
```kconfig
CONFIG_LISA_HTTP=y # 启用 HTTP 组件
```
## API 接口
### 生命周期管理
```c
lisa_http_t *lisa_http_init(lisa_http_request_t *req);
lisa_http_err_e lisa_http_cleanup(lisa_http_t *ins);
```
### HTTP 请求接口
```c
lisa_http_err_e lisa_http_perform(lisa_http_t *ins);
lisa_http_err_e lisa_http_download(lisa_http_t *ins);
```
### 分块传输接口
```c
lisa_http_err_e lisa_http_perform_chunked(lisa_http_t *ins);
lisa_http_err_e lisa_http_perform_chunked_with_cb(lisa_http_t *ins,
int (*on_chunk)(lisa_http_data_t *));
```
## 使用示例
### 基础 HTTP GET 请求示例
```c
#include "lisa_http.h"
#include "lisa_log.h"
// HTTP 响应数据回调函数
static void http_response_callback(lisa_http_data_t *data)
{
if (data && data->buf && data->len > 0) {
LISA_INFO("收到 HTTP 响应数据,长度: %d", data->len);
// 将数据转换为字符串并输出(假设是文本数据)
char *response = lisa_mem_alloc(data->len + 1);
if (response) {
memcpy(response, data->buf, data->len);
response[data->len] = '\0';
LISA_INFO("响应内容: %s", response);
lisa_mem_free(response);
}
}
}
int basic_http_get_example(void)
{
// 1. 配置 HTTP 请求
lisa_http_request_t request = {
.method = LISA_HTTP_GET,
.url = (uint8_t *)"http://httpbin.org/get",
.headers = NULL, // 使用默认头部
.body = NULL,
.body_len = 0,
.timeout = 10000, // 10秒超时
.user = NULL,
.on_data = http_response_callback,
};
// 2. 创建 HTTP 实例
lisa_http_t *http = lisa_http_init(&request);
if (!http) {
LISA_ERR("创建 HTTP 实例失败");
return -1;
}
// 3. 执行 HTTP 请求
lisa_http_err_e result = lisa_http_perform(http);
if (result == LISA_HTTP_OK) {
LISA_INFO("HTTP 请求执行成功");
} else {
LISA_ERR("HTTP 请求失败,错误代码: %d", result);
}
// 4. 清理资源
lisa_http_cleanup(http);
return (result == LISA_HTTP_OK) ? 0 : -1;
}
```
### HTTP POST 请求示例
```c
#include "lisa_http.h"
#include "lisa_log.h"
#include "cJSON.h"
// POST 请求响应回调函数
static void post_response_callback(lisa_http_data_t *data)
{
if (data && data->buf && data->len > 0) {
char *response = lisa_mem_alloc(data->len + 1);
if (response) {
memcpy(response, data->buf, data->len);
response[data->len] = '\0';
LISA_INFO("POST 响应: %s", response);
// 解析 JSON 响应(如果需要)
cJSON *json = cJSON_Parse(response);
if (json) {
// 处理 JSON 数据
LISA_INFO("JSON 解析成功");
cJSON_Delete(json);
}
lisa_mem_free(response);
}
}
}
int http_post_example(void)
{
// 1. 准备 POST 数据JSON 格式)
cJSON *json_data = cJSON_CreateObject();
if (!json_data) {
LISA_ERR("创建 JSON 对象失败");
return -1;
}
cJSON_AddStringToObject(json_data, "name", "LISA HTTP Client");
cJSON_AddNumberToObject(json_data, "version", 1.0);
cJSON_AddStringToObject(json_data, "message", "Hello from LISA");
char *json_string = cJSON_PrintUnformatted(json_data);
cJSON_Delete(json_data);
if (!json_string) {
LISA_ERR("JSON 序列化失败");
return -1;
}
// 2. 配置 HTTP POST 请求
lisa_http_request_t request = {
.method = LISA_HTTP_POST,
.url = (uint8_t *)"http://httpbin.org/post",
.headers = (uint8_t *)"Content-Type: application/json\r\n", // 设置 JSON 头部
.body = json_string,
.body_len = strlen(json_string),
.timeout = 15000, // 15秒超时
.user = NULL,
.on_data = post_response_callback,
};
// 3. 创建并执行 HTTP 请求
lisa_http_t *http = lisa_http_init(&request);
if (!http) {
LISA_ERR("创建 HTTP 实例失败");
free(json_string);
return -1;
}
lisa_http_err_e result = lisa_http_perform(http);
// 4. 清理资源
lisa_http_cleanup(http);
free(json_string);
return (result == LISA_HTTP_OK) ? 0 : -1;
}
```
### 文件下载示例
```c
#include "lisa_http.h"
#include "lisa_log.h"
#include "lisa_fs.h" // 假设有文件系统接口
// 文件下载上下文
typedef struct {
FILE *file;
uint32_t total_size;
uint32_t downloaded_size;
} download_context_t;
// 文件下载回调函数
static void download_callback(lisa_http_data_t *data)
{
download_context_t *ctx = (download_context_t *)data->user;
if (ctx && ctx->file && data && data->buf && data->len > 0) {
// 写入文件
size_t written = fwrite(data->buf, 1, data->len, ctx->file);
if (written == data->len) {
ctx->downloaded_size += written;
LISA_INFO("下载进度: %d/%d 字节 (%.1f%%)",
ctx->downloaded_size, ctx->total_size,
(float)ctx->downloaded_size * 100.0f / ctx->total_size);
} else {
LISA_ERR("文件写入失败");
}
}
}
int file_download_example(void)
{
// 1. 准备下载上下文
download_context_t download_ctx = {0};
download_ctx.file = fopen("/tmp/downloaded_file.bin", "wb");
if (!download_ctx.file) {
LISA_ERR("无法创建下载文件");
return -1;
}
download_ctx.total_size = 0; // 初始不知道文件大小
// 2. 配置下载请求
lisa_http_request_t request = {
.method = LISA_HTTP_GET,
.url = (uint8_t *)"http://httpbin.org/bytes/1024", // 下载 1KB 数据
.headers = NULL,
.body = NULL,
.body_len = 0,
.timeout = 30000, // 30秒超时
.user = &download_ctx,
.on_data = download_callback,
};
// 3. 执行下载
lisa_http_t *http = lisa_http_init(&request);
if (!http) {
LISA_ERR("创建 HTTP 实例失败");
fclose(download_ctx.file);
return -1;
}
lisa_http_err_e result = lisa_http_download(http);
// 4. 清理资源
fclose(download_ctx.file);
lisa_http_cleanup(http);
if (result == LISA_HTTP_OK) {
LISA_INFO("文件下载完成,总大小: %d 字节", download_ctx.downloaded_size);
} else {
LISA_ERR("文件下载失败,错误代码: %d", result);
}
return (result == LISA_HTTP_OK) ? 0 : -1;
}
```
### 分块传输示例
```c
#include "lisa_http.h"
#include "lisa_log.h"
// 分块数据处理回调函数
static int chunk_callback(lisa_http_data_t *data)
{
if (data && data->buf && data->len > 0) {
LISA_INFO("收到数据块,大小: %d 字节", data->len);
// 处理数据块(这里简单计算校验和)
uint8_t checksum = 0;
uint8_t *chunk_data = (uint8_t *)data->buf;
for (int i = 0; i < data->len; i++) {
checksum ^= chunk_data[i];
}
LISA_INFO("数据块校验和: 0x%02X", checksum);
// 检查是否需要继续接收数据
// 返回 0 继续,返回非 0 停止传输
static int chunk_count = 0;
chunk_count++;
if (chunk_count >= 5) {
LISA_INFO("接收到 5 个数据块,停止传输");
return -1; // 停止传输
}
}
return 0; // 继续接收数据
}
int chunked_transfer_example(void)
{
// 1. 配置分块传输请求
lisa_http_request_t request = {
.method = LISA_HTTP_GET,
.url = (uint8_t *)"http://httpbin.org/stream/20", // 流式数据接口
.headers = NULL,
.body = NULL,
.body_len = 0,
.timeout = 20000, // 20秒超时
.user = NULL,
.on_data = NULL, // 使用自定义回调,不需要这个
};
// 2. 创建 HTTP 实例
lisa_http_t *http = lisa_http_init(&request);
if (!http) {
LISA_ERR("创建 HTTP 实例失败");
return -1;
}
// 3. 执行分块传输(使用自定义回调)
lisa_http_err_e result = lisa_http_perform_chunked_with_cb(http, chunk_callback);
// 4. 清理资源
lisa_http_cleanup(http);
if (result == LISA_HTTP_OK) {
LISA_INFO("分块传输完成");
} else {
LISA_ERR("分块传输失败,错误代码: %d", result);
}
return (result == LISA_HTTP_OK) ? 0 : -1;
}
```
### 多请求管理示例
```c
#include "lisa_http.h"
#include "lisa_log.h"
// 请求管理结构
typedef struct {
lisa_http_t *http;
char *name;
bool completed;
lisa_http_err_e result;
} http_request_t;
// 统一的响应处理函数
static void unified_response_handler(lisa_http_data_t *data)
{
// 从 user_data 中获取请求管理结构
http_request_t *req_info = (http_request_t *)data->user;
if (req_info) {
req_info->completed = true;
if (data && data->buf && data->len > 0) {
LISA_INFO("请求 '%s' 收到响应,长度: %d", req_info->name, data->len);
} else {
LISA_INFO("请求 '%s' 完成,无响应数据", req_info->name);
}
}
}
int multi_request_example(void)
{
// 1. 定义多个请求
http_request_t requests[3];
const char *urls[] = {
"http://httpbin.org/get",
"http://httpbin.org/uuid",
"http://httpbin.org/ip"
};
const char *names[] = {"GET请求", "UUID请求", "IP请求"};
// 2. 创建并配置多个 HTTP 实例
for (int i = 0; i < 3; i++) {
lisa_http_request_t request = {
.method = LISA_HTTP_GET,
.url = (uint8_t *)urls[i],
.headers = NULL,
.body = NULL,
.body_len = 0,
.timeout = 10000,
.user = &requests[i],
.on_data = unified_response_handler,
};
requests[i].http = lisa_http_init(&request);
requests[i].name = (char *)names[i];
requests[i].completed = false;
requests[i].result = LISA_HTTP_COMMON_ERR;
if (!requests[i].http) {
LISA_ERR("创建请求 '%s' 失败", names[i]);
}
}
// 3. 执行所有请求
for (int i = 0; i < 3; i++) {
if (requests[i].http) {
requests[i].result = lisa_http_perform(requests[i].http);
LISA_INFO("请求 '%s' 执行结果: %d", names[i], requests[i].result);
}
}
// 4. 等待所有请求完成(在实际应用中可能需要更复杂的同步机制)
for (int i = 0; i < 3; i++) {
if (requests[i].completed) {
LISA_INFO("请求 '%s' 已完成", names[i]);
}
}
// 5. 清理所有资源
for (int i = 0; i < 3; i++) {
if (requests[i].http) {
lisa_http_cleanup(requests[i].http);
}
}
return 0;
}
```
## HTTP 方法说明
### 支持的 HTTP 方法
| 方法 | 用途 | 请求体 | 适用场景 |
|------|------|--------|---------|
| `LISA_HTTP_GET` | 获取资源 | 不支持 | 数据查询、信息获取 |
| `LISA_HTTP_POST` | 提交数据 | 支持 | 数据创建、表单提交 |
| `LISA_HTTP_PUT` | 更新资源 | 支持 | 完整资源更新 |
| `LISA_HTTP_PATCH` | 部分更新 | 支持 | 部分资源更新 |
| `LISA_HTTP_DELETE` | 删除资源 | 不支持 | 资源删除 |
### 传输模式对比
| 模式 | 同步性 | 适用场景 | 特点 |
|------|--------|---------|------|
| `lisa_http_perform()` | 同步 | 小数据量、简单请求 | 简单易用,等待完整响应 |
| `lisa_http_download()` | 同步 | 文件下载 | 优化的文件传输 |
| `lisa_http_perform_chunked()` | 异步 | 大数据流处理 | 内部回调,分块处理 |
| `lisa_http_perform_chunked_with_cb()` | 异步 | 自定义数据处理 | 用户控制数据处理流程 |
## 错误代码说明
| 错误代码 | 说明 | 可能原因 | 解决方法 |
|---------|------|---------|---------|
| `LISA_HTTP_OK` | 操作成功 | - | - |
| `LISA_HTTP_COMMON_ERR` | 一般错误 | 参数错误、内存不足 | 检查参数和内存状态 |
| `LISA_HTTP_PARAM_ERROR` | 参数错误 | URL 为空、回调函数为 NULL | 检查请求配置 |
| `LISA_HTTP_NET_ERR` | 网络错误 | 连接失败、超时 | 检查网络连接和超时设置 |
## 使用注意事项
1. **参数验证**: 创建 HTTP 实例前必须验证 URL 和回调函数不为 NULL
2. **内存管理**: 请求体数据在请求执行期间必须保持有效
3. **HTTPS 处理**: 组件会自动将 HTTPS URL 转换为 HTTP 处理
4. **超时设置**: 根据网络环境和数据大小合理设置超时时间
5. **回调函数**: 回调函数应尽量简短,避免阻塞网络处理线程
6. **分块大小**: 分块传输时单个数据块最大支持 4096 字节
7. **资源清理**: 请求完成后必须调用 `lisa_http_cleanup()` 释放资源
8. **错误处理**: 所有 API 调用都应检查返回值并处理错误情况
9. **URL 长度**: URL 最大长度为 1023 字符(包含结束符)
10. **请求体大小**: 请求体最大长度为 1024 字节
11. **线程安全**: 每个 HTTP 实例独立工作,但多线程使用时需要注意资源同步
12. **文件操作**: 文件下载时需要确保文件系统可用且有写入权限
## 文件说明
- `lisa_http.h` - HTTP 组件头文件,包含 API 接口和数据结构定义
- `lisa_http.c` - HTTP 组件实现文件,基于 HTTPCUsr_api 实现
- `Kconfig` - 组件配置选项定义文件
- `CMakeLists.txt` - 组件构建配置文件