add some code

This commit is contained in:
2025-09-05 13:25:11 +08:00
parent 9ff0a99e7a
commit 3cf1229a85
8911 changed files with 2535396 additions and 0 deletions

View File

@@ -0,0 +1,724 @@
#include "http_client.h"
#include "network_interface.h"
#include <esp_log.h>
#include <cstring>
#include <cstdlib>
#include <sstream>
#include <chrono>
#include <algorithm>
#include <cctype>
static const char *TAG = "HttpClient";
HttpClient::HttpClient(NetworkInterface* network, int connect_id) : network_(network), connect_id_(connect_id) {
event_group_handle_ = xEventGroupCreate();
}
HttpClient::~HttpClient() {
if (connected_) {
Close();
}
vEventGroupDelete(event_group_handle_);
}
void HttpClient::SetTimeout(int timeout_ms) {
timeout_ms_ = timeout_ms;
}
void HttpClient::SetHeader(const std::string& key, const std::string& value) {
// 转换key为小写用于存储和查找但保留原始key用于输出
std::string lower_key = key;
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::tolower);
headers_[lower_key] = HeaderEntry(key, value);
}
void HttpClient::SetContent(std::string&& content) {
content_ = std::move(content);
}
bool HttpClient::ParseUrl(const std::string& url) {
// 解析 URL: protocol://host:port/path
size_t protocol_end = url.find("://");
if (protocol_end == std::string::npos) {
ESP_LOGE(TAG, "Invalid URL format: %s", url.c_str());
return false;
}
protocol_ = url.substr(0, protocol_end);
std::transform(protocol_.begin(), protocol_.end(), protocol_.begin(), ::tolower);
size_t host_start = protocol_end + 3;
size_t path_start = url.find("/", host_start);
size_t port_start = url.find(":", host_start);
// 默认端口
if (protocol_ == "https") {
port_ = 443;
} else {
port_ = 80;
}
if (path_start == std::string::npos) {
path_ = "/";
if (port_start != std::string::npos) {
host_ = url.substr(host_start, port_start - host_start);
std::string port_str = url.substr(port_start + 1);
char* endptr;
long port = strtol(port_str.c_str(), &endptr, 10);
if (endptr != port_str.c_str() && *endptr == '\0' && port > 0 && port <= 65535) {
port_ = static_cast<int>(port);
} else {
ESP_LOGE(TAG, "Invalid port: %s", port_str.c_str());
return false;
}
} else {
host_ = url.substr(host_start);
}
} else {
path_ = url.substr(path_start);
if (port_start != std::string::npos && port_start < path_start) {
host_ = url.substr(host_start, port_start - host_start);
std::string port_str = url.substr(port_start + 1, path_start - port_start - 1);
char* endptr;
long port = strtol(port_str.c_str(), &endptr, 10);
if (endptr != port_str.c_str() && *endptr == '\0' && port > 0 && port <= 65535) {
port_ = static_cast<int>(port);
} else {
ESP_LOGE(TAG, "Invalid port: %s", port_str.c_str());
return false;
}
} else {
host_ = url.substr(host_start, path_start - host_start);
}
}
ESP_LOGD(TAG, "Parsed URL: protocol=%s, host=%s, port=%d, path=%s",
protocol_.c_str(), host_.c_str(), port_, path_.c_str());
return true;
}
std::string HttpClient::BuildHttpRequest() {
std::ostringstream request;
// 请求行
request << method_ << " " << path_ << " HTTP/1.1\r\n";
// Host 头
request << "Host: " << host_;
if ((protocol_ == "http" && port_ != 80) || (protocol_ == "https" && port_ != 443)) {
request << ":" << port_;
}
request << "\r\n";
// 用户自定义头部使用原始key输出
for (const auto& [lower_key, header_entry] : headers_) {
request << header_entry.original_key << ": " << header_entry.value << "\r\n";
}
// 内容相关头部(仅在用户未设置时添加)
bool user_set_content_length = headers_.find("content-length") != headers_.end();
bool user_set_transfer_encoding = headers_.find("transfer-encoding") != headers_.end();
bool has_content = content_.has_value() && !content_->empty();
if (has_content && !user_set_content_length) {
request << "Content-Length: " << content_->size() << "\r\n";
} else if ((method_ == "POST" || method_ == "PUT") && !user_set_content_length && !user_set_transfer_encoding) {
if (request_chunked_) {
request << "Transfer-Encoding: chunked\r\n";
} else {
request << "Content-Length: 0\r\n";
}
}
// 连接控制(仅在用户未设置时添加)
if (headers_.find("connection") == headers_.end()) {
request << "Connection: close\r\n";
}
// 结束头部
request << "\r\n";
ESP_LOGD(TAG, "HTTP request headers:\n%s", request.str().c_str());
// 请求体
if (has_content) {
request << *content_;
}
return request.str();
}
bool HttpClient::Open(const std::string& method, const std::string& url) {
method_ = method;
url_ = url;
// 重置状态
status_code_ = -1;
response_headers_.clear();
{
std::lock_guard<std::mutex> read_lock(read_mutex_);
body_chunks_.clear();
}
body_offset_ = 0;
content_length_ = 0;
total_body_received_ = 0;
eof_ = false;
headers_received_ = false;
response_chunked_ = false;
connection_error_ = false; // 重置连接错误状态
parse_state_ = ParseState::STATUS_LINE;
chunk_size_ = 0;
chunk_received_ = 0;
rx_buffer_.clear();
xEventGroupClearBits(event_group_handle_,
EC801E_HTTP_EVENT_HEADERS_RECEIVED |
EC801E_HTTP_EVENT_BODY_RECEIVED |
EC801E_HTTP_EVENT_ERROR |
EC801E_HTTP_EVENT_COMPLETE);
// 解析 URL
if (!ParseUrl(url)) {
return false;
}
// 建立 TCP 连接
if (protocol_ == "https") {
tcp_ = network_->CreateSsl(connect_id_);
} else {
tcp_ = network_->CreateTcp(connect_id_);
}
// 设置 TCP 数据接收回调
tcp_->OnStream([this](const std::string& data) {
OnTcpData(data);
});
// 设置 TCP 断开连接回调
tcp_->OnDisconnected([this]() {
OnTcpDisconnected();
});
if (!tcp_->Connect(host_, port_)) {
ESP_LOGE(TAG, "TCP connection failed");
return false;
}
connected_ = true;
request_chunked_ = (method_ == "POST" || method_ == "PUT") && !content_.has_value();
// 构建并发送 HTTP 请求
std::string http_request = BuildHttpRequest();
if (tcp_->Send(http_request) <= 0) {
ESP_LOGE(TAG, "Send HTTP request failed");
tcp_->Disconnect();
connected_ = false;
return false;
}
return true;
}
void HttpClient::Close() {
if (!connected_) {
return;
}
connected_ = false;
write_cv_.notify_all();
tcp_->Disconnect();
eof_ = true;
cv_.notify_all();
ESP_LOGD(TAG, "HTTP connection closed");
}
void HttpClient::OnTcpData(const std::string& data) {
std::lock_guard<std::mutex> lock(mutex_);
// 检查 body_chunks_ 大小,如果超过 8KB 且 heap 小于 32KB 则阻塞
{
std::unique_lock<std::mutex> read_lock(read_mutex_);
write_cv_.wait(read_lock, [this, size=data.size()] {
size_t total_size = size;
for (const auto& chunk : body_chunks_) {
total_size += chunk.data.size();
}
size_t free_heap = esp_get_free_heap_size();
return total_size < MAX_BODY_CHUNKS_SIZE || !connected_ || free_heap >= 32768;
});
}
rx_buffer_.append(data);
ProcessReceivedData();
cv_.notify_one();
}
void HttpClient::OnTcpDisconnected() {
std::lock_guard<std::mutex> lock(mutex_);
connected_ = false;
// 检查数据是否完整接收
if (headers_received_ && !IsDataComplete()) {
// 如果已接收头部但数据不完整,标记为连接错误
connection_error_ = true;
ESP_LOGE(TAG, "Connection closed prematurely, expected %u bytes but only received %u bytes",
content_length_, total_body_received_);
} else {
// 数据完整或还未开始接收响应体,正常结束
eof_ = true;
}
cv_.notify_all(); // 通知所有等待的读取操作
}
void HttpClient::ProcessReceivedData() {
while (!rx_buffer_.empty() && parse_state_ != ParseState::COMPLETE) {
switch (parse_state_) {
case ParseState::STATUS_LINE: {
if (!HasCompleteLine(rx_buffer_)) return; // 需要更多数据
std::string line = GetNextLine(rx_buffer_);
if (ParseStatusLine(line)) {
parse_state_ = ParseState::HEADERS;
} else {
SetError();
return;
}
break;
}
case ParseState::HEADERS: {
if (!HasCompleteLine(rx_buffer_)) return; // 需要更多数据
std::string line = GetNextLine(rx_buffer_);
// 检查是否为空行(头部结束标记)
// GetNextLine 已经移除了 \r所以空行就是 empty()
if (line.empty()) {
// 检查是否为 chunked 编码
auto it = response_headers_.find("transfer-encoding");
if (it != response_headers_.end() &&
it->second.value.find("chunked") != std::string::npos) {
response_chunked_ = true;
parse_state_ = ParseState::CHUNK_SIZE;
} else {
parse_state_ = ParseState::BODY;
auto cl_it = response_headers_.find("content-length");
if (cl_it != response_headers_.end()) {
char* endptr;
unsigned long length = strtoul(cl_it->second.value.c_str(), &endptr, 10);
if (endptr != cl_it->second.value.c_str() && *endptr == '\0') {
content_length_ = static_cast<size_t>(length);
} else {
ESP_LOGE(TAG, "Invalid Content-Length: %s", cl_it->second.value.c_str());
content_length_ = 0;
}
}
}
// 头部结束
headers_received_ = true;
xEventGroupSetBits(event_group_handle_, EC801E_HTTP_EVENT_HEADERS_RECEIVED);
} else {
if (!ParseHeaderLine(line)) {
SetError();
return;
}
}
break;
}
case ParseState::BODY: {
if (response_chunked_) {
ParseChunkedBody(rx_buffer_);
} else {
ParseRegularBody(rx_buffer_);
}
break;
}
case ParseState::CHUNK_SIZE: {
if (!HasCompleteLine(rx_buffer_)) return; // 需要更多数据
std::string line = GetNextLine(rx_buffer_);
chunk_size_ = ParseChunkSize(line);
chunk_received_ = 0;
if (chunk_size_ == 0) {
parse_state_ = ParseState::CHUNK_TRAILER;
} else {
parse_state_ = ParseState::CHUNK_DATA;
}
break;
}
case ParseState::CHUNK_DATA: {
size_t available = std::min(rx_buffer_.size(), chunk_size_ - chunk_received_);
if (available > 0) {
std::string chunk_data = rx_buffer_.substr(0, available);
AddBodyData(std::move(chunk_data));
total_body_received_ += available;
rx_buffer_.erase(0, available);
chunk_received_ += available;
if (chunk_received_ == chunk_size_) {
// 跳过 chunk 后的 CRLF
if (rx_buffer_.size() >= 2 && rx_buffer_.substr(0, 2) == "\r\n") {
rx_buffer_.erase(0, 2);
}
parse_state_ = ParseState::CHUNK_SIZE;
}
}
if (available == 0) return; // 需要更多数据
break;
}
case ParseState::CHUNK_TRAILER: {
if (!HasCompleteLine(rx_buffer_)) return; // 需要更多数据
std::string line = GetNextLine(rx_buffer_);
if (line.empty()) {
parse_state_ = ParseState::COMPLETE;
eof_ = true;
xEventGroupSetBits(event_group_handle_, EC801E_HTTP_EVENT_COMPLETE);
}
// 忽略 trailer 头部
break;
}
case ParseState::COMPLETE:
return;
}
}
// 检查是否完成(非 chunked 模式)
if (parse_state_ == ParseState::BODY && !response_chunked_ &&
content_length_ > 0 && total_body_received_ >= content_length_) {
parse_state_ = ParseState::COMPLETE;
eof_ = true;
xEventGroupSetBits(event_group_handle_, EC801E_HTTP_EVENT_COMPLETE);
ESP_LOGD(TAG, "HTTP response body received: %u/%u bytes", total_body_received_, content_length_);
}
}
bool HttpClient::ParseStatusLine(const std::string& line) {
// HTTP/1.1 200 OK
std::istringstream iss(line);
std::string version, status_str, reason;
if (!(iss >> version >> status_str)) {
ESP_LOGE(TAG, "Invalid status line: %s", line.c_str());
return false;
}
std::getline(iss, reason); // 获取剩余部分作为原因短语
// 安全地解析状态码
char* endptr;
long status = strtol(status_str.c_str(), &endptr, 10);
if (endptr == status_str.c_str() || *endptr != '\0' || status < 100 || status > 999) {
ESP_LOGE(TAG, "Parse status code failed: %s", status_str.c_str());
return false;
}
status_code_ = static_cast<int>(status);
ESP_LOGD(TAG, "HTTP status code: %d", status_code_);
return true;
}
bool HttpClient::ParseHeaderLine(const std::string& line) {
size_t colon_pos = line.find(':');
if (colon_pos == std::string::npos) {
ESP_LOGE(TAG, "Invalid header line: %s", line.c_str());
return false;
}
std::string key = line.substr(0, colon_pos);
std::string value = line.substr(colon_pos + 1);
// 去除前后空格
key.erase(0, key.find_first_not_of(" \t"));
key.erase(key.find_last_not_of(" \t") + 1);
value.erase(0, value.find_first_not_of(" \t"));
value.erase(value.find_last_not_of(" \t\r\n") + 1);
// 转换为小写键名用于存储和查找同时保存原始key
std::string lower_key = key;
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::tolower);
response_headers_[lower_key] = HeaderEntry(key, value);
return true;
}
void HttpClient::ParseChunkedBody(const std::string& data) {
// Chunked body 在 ProcessReceivedData 中的状态机中处理
}
void HttpClient::ParseRegularBody(const std::string& data) {
if (!data.empty()) {
AddBodyData(data); // 使用新的方法添加数据
total_body_received_ += data.size(); // 累加接收的字节数
rx_buffer_.clear();
}
}
size_t HttpClient::ParseChunkSize(const std::string& chunk_size_line) {
// 解析 chunk size十六进制
std::string size_str = chunk_size_line;
// 移除 CRLF 和任何扩展
size_t semi_pos = size_str.find(';');
if (semi_pos != std::string::npos) {
size_str = size_str.substr(0, semi_pos);
}
size_str.erase(size_str.find_last_not_of(" \t\r\n") + 1);
// 安全地解析十六进制 chunk 大小
char* endptr;
unsigned long chunk_size = strtoul(size_str.c_str(), &endptr, 16);
if (endptr == size_str.c_str() || *endptr != '\0') {
ESP_LOGE(TAG, "Parse chunk size failed: %s", size_str.c_str());
return 0;
}
return static_cast<size_t>(chunk_size);
}
std::string HttpClient::GetNextLine(std::string& buffer) {
size_t pos = buffer.find('\n');
if (pos == std::string::npos) {
return ""; // 没有完整的行
}
std::string line = buffer.substr(0, pos);
buffer.erase(0, pos + 1);
// 移除 CR
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
return line;
}
bool HttpClient::HasCompleteLine(const std::string& buffer) {
return buffer.find('\n') != std::string::npos;
}
void HttpClient::SetError() {
ESP_LOGE(TAG, "HTTP parse error");
xEventGroupSetBits(event_group_handle_, EC801E_HTTP_EVENT_ERROR);
}
int HttpClient::Read(char* buffer, size_t buffer_size) {
std::unique_lock<std::mutex> read_lock(read_mutex_);
// 如果连接异常断开,返回错误
if (connection_error_) {
return -1;
}
// 如果已经到达文件末尾且没有更多数据返回0
if (eof_ && body_chunks_.empty()) {
return 0;
}
// 如果有数据可读,直接返回
while (!body_chunks_.empty()) {
auto& front_chunk = body_chunks_.front();
size_t bytes_read = front_chunk.read(buffer, buffer_size);
if (bytes_read > 0) {
// 如果当前chunk已读完移除它
if (front_chunk.empty()) {
body_chunks_.pop_front();
}
// 通知等待的写入操作
write_cv_.notify_one();
return static_cast<int>(bytes_read);
}
// 如果chunk为空移除它并继续下一个
body_chunks_.pop_front();
}
// 如果连接已断开,检查是否有错误
if (!connected_) {
if (connection_error_) {
return -1; // 连接异常断开
}
return 0; // 正常结束
}
// 等待数据或连接关闭
auto timeout = std::chrono::milliseconds(timeout_ms_);
bool received = cv_.wait_for(read_lock, timeout, [this] {
return !body_chunks_.empty() || eof_ || !connected_ || connection_error_;
});
if (!received) {
ESP_LOGE(TAG, "Wait for HTTP content receive timeout");
return -1;
}
// 再次检查连接错误状态
if (connection_error_) {
return -1;
}
// 再次检查是否有数据可读
while (!body_chunks_.empty()) {
auto& front_chunk = body_chunks_.front();
size_t bytes_read = front_chunk.read(buffer, buffer_size);
if (bytes_read > 0) {
// 如果当前chunk已读完移除它
if (front_chunk.empty()) {
body_chunks_.pop_front();
}
// 通知等待的写入操作
write_cv_.notify_one();
return static_cast<int>(bytes_read);
}
// 如果chunk为空移除它并继续下一个
body_chunks_.pop_front();
}
// 连接已关闭或到达EOF返回0
return 0;
}
int HttpClient::Write(const char* buffer, size_t buffer_size) {
if (!connected_ || !request_chunked_) {
ESP_LOGE(TAG, "Cannot write: connection closed or not chunked mode");
return -1;
}
if (buffer_size == 0) {
// 发送结束 chunk
std::string end_chunk = "0\r\n\r\n";
return tcp_->Send(end_chunk);
}
// 发送 chunk
std::ostringstream chunk;
chunk << std::hex << buffer_size << "\r\n";
chunk.write(buffer, buffer_size);
chunk << "\r\n";
std::string chunk_data = chunk.str();
return tcp_->Send(chunk_data);
}
int HttpClient::GetStatusCode() {
if (!headers_received_) {
// 等待头部接收
auto bits = xEventGroupWaitBits(event_group_handle_,
EC801E_HTTP_EVENT_HEADERS_RECEIVED | EC801E_HTTP_EVENT_ERROR,
pdFALSE, pdFALSE,
pdMS_TO_TICKS(timeout_ms_));
if (bits & EC801E_HTTP_EVENT_ERROR) {
return -1;
}
if (!(bits & EC801E_HTTP_EVENT_HEADERS_RECEIVED)) {
ESP_LOGE(TAG, "Wait for HTTP headers receive timeout");
return -1;
}
}
return status_code_;
}
std::string HttpClient::GetResponseHeader(const std::string& key) const {
// 转换为小写进行查找
std::string lower_key = key;
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::tolower);
auto it = response_headers_.find(lower_key);
if (it != response_headers_.end()) {
return it->second.value;
}
return "";
}
size_t HttpClient::GetBodyLength() {
if (!headers_received_) {
GetStatusCode(); // 这会等待头部接收
}
if (response_chunked_) {
return 0; // Chunked 模式下长度未知
}
return content_length_;
}
void HttpClient::AddBodyData(const std::string& data) {
if (data.empty()) return;
std::lock_guard<std::mutex> read_lock(read_mutex_);
body_chunks_.emplace_back(data); // 使用构造函数,避免额外的拷贝
cv_.notify_one(); // 通知有新数据
write_cv_.notify_one(); // 通知写入操作
}
void HttpClient::AddBodyData(std::string&& data) {
if (data.empty()) return;
std::lock_guard<std::mutex> read_lock(read_mutex_);
body_chunks_.emplace_back(std::move(data)); // 使用移动语义,避免拷贝
cv_.notify_one(); // 通知有新数据
write_cv_.notify_one(); // 通知写入操作
}
std::string HttpClient::ReadAll() {
std::unique_lock<std::mutex> lock(mutex_);
// 等待完成或出错
auto timeout = std::chrono::milliseconds(timeout_ms_);
bool completed = cv_.wait_for(lock, timeout, [this] {
return eof_ || connection_error_;
});
if (!completed) {
ESP_LOGE(TAG, "Wait for HTTP content receive complete timeout");
return ""; // 超时返回空字符串
}
// 如果连接异常断开,返回空字符串并记录错误
if (connection_error_) {
ESP_LOGE(TAG, "Cannot read all data: connection closed prematurely");
return "";
}
// 收集所有数据
std::string result;
std::lock_guard<std::mutex> read_lock(read_mutex_);
for (const auto& chunk : body_chunks_) {
result.append(chunk.data);
}
return result;
}
bool HttpClient::IsDataComplete() const {
// 对于chunked编码如果parse_state_是COMPLETE说明接收完整
if (response_chunked_) {
return parse_state_ == ParseState::COMPLETE;
}
// 对于固定长度检查是否接收了完整的content-length
if (content_length_ > 0) {
return total_body_received_ >= content_length_;
}
// 如果没有content-length且不是chunked当连接关闭时认为完整
// 这种情况通常用于HTTP/1.0或者content-length为0的响应
return true;
}