TUT/src/http_client.cpp
m1ngsama d80d0a1c6e feat: Implement TUT 2.0 with new rendering architecture
Major features:
- New modular architecture with Terminal, FrameBuffer, Renderer layers
- True Color (24-bit) support with warm, eye-friendly color scheme
- Unicode support with proper CJK character width handling
- Differential rendering for improved performance
- Page caching (LRU, 20 pages, 5-minute expiry)
- Search functionality with highlighting (/, n/N)
- Form rendering (input, button, checkbox, radio, select)
- Image placeholder support ([alt text] or [Image: filename])
- Binary data download via fetch_binary()
- Loading state indicators

New files:
- src/browser_v2.cpp/h - Browser with new rendering system
- src/main_v2.cpp - Entry point for tut2
- src/render/* - Terminal, FrameBuffer, Renderer, Layout, Image modules
- src/utils/unicode.cpp/h - Unicode handling utilities
- tests/* - Test programs for each module

Build with: cmake --build build_v2
Run: ./build_v2/tut2 [URL]
2025-12-26 14:56:17 +08:00

300 lines
No EOL
9.3 KiB
C++

#include "http_client.h"
#include <curl/curl.h>
#include <stdexcept>
// 回调函数用于接收文本数据
static size_t write_callback(void* contents, size_t size, size_t nmemb, std::string* userp) {
size_t total_size = size * nmemb;
userp->append(static_cast<char*>(contents), total_size);
return total_size;
}
// 回调函数用于接收二进制数据
static size_t binary_write_callback(void* contents, size_t size, size_t nmemb, std::vector<uint8_t>* userp) {
size_t total_size = size * nmemb;
uint8_t* data = static_cast<uint8_t*>(contents);
userp->insert(userp->end(), data, data + total_size);
return total_size;
}
class HttpClient::Impl {
public:
CURL* curl;
long timeout;
std::string user_agent;
bool follow_redirects;
std::string cookie_file;
Impl() : timeout(30),
user_agent("TUT-Browser/1.0 (Terminal User Interface Browser)"),
follow_redirects(true) {
curl = curl_easy_init();
if (!curl) {
throw std::runtime_error("Failed to initialize CURL");
}
// Enable cookie engine by default (in-memory)
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
// Enable automatic decompression of supported encodings (gzip, deflate, etc.)
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
}
~Impl() {
if (curl) {
curl_easy_cleanup(curl);
}
}
};
HttpClient::HttpClient() : pImpl(std::make_unique<Impl>()) {}
HttpClient::~HttpClient() = default;
HttpResponse HttpClient::fetch(const std::string& url) {
HttpResponse response;
response.status_code = 0;
if (!pImpl->curl) {
response.error_message = "CURL not initialized";
return response;
}
// 重置选项 (Note: curl_easy_reset clears cookies setting if not careful,
// but here we might want to preserve them or reset and re-apply options)
// Actually curl_easy_reset clears ALL options including cookie engine state?
// No, it resets options to default. It does NOT clear the cookie engine state (cookies held in memory).
// BUT it resets CURLOPT_COOKIEFILE/JAR settings.
curl_easy_reset(pImpl->curl);
// Re-apply settings
// 设置URL
curl_easy_setopt(pImpl->curl, CURLOPT_URL, url.c_str());
// 设置写回调
std::string response_body;
curl_easy_setopt(pImpl->curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(pImpl->curl, CURLOPT_WRITEDATA, &response_body);
// 设置超时
curl_easy_setopt(pImpl->curl, CURLOPT_TIMEOUT, pImpl->timeout);
curl_easy_setopt(pImpl->curl, CURLOPT_CONNECTTIMEOUT, 10L);
// 设置用户代理
curl_easy_setopt(pImpl->curl, CURLOPT_USERAGENT, pImpl->user_agent.c_str());
// 设置是否跟随重定向
if (pImpl->follow_redirects) {
curl_easy_setopt(pImpl->curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(pImpl->curl, CURLOPT_MAXREDIRS, 10L);
}
// 支持 HTTPS
curl_easy_setopt(pImpl->curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(pImpl->curl, CURLOPT_SSL_VERIFYHOST, 2L);
// Cookie settings
if (!pImpl->cookie_file.empty()) {
curl_easy_setopt(pImpl->curl, CURLOPT_COOKIEFILE, pImpl->cookie_file.c_str());
curl_easy_setopt(pImpl->curl, CURLOPT_COOKIEJAR, pImpl->cookie_file.c_str());
} else {
curl_easy_setopt(pImpl->curl, CURLOPT_COOKIEFILE, "");
}
// 执行请求
CURLcode res = curl_easy_perform(pImpl->curl);
if (res != CURLE_OK) {
response.error_message = curl_easy_strerror(res);
return response;
}
// 获取响应码
long http_code = 0;
curl_easy_getinfo(pImpl->curl, CURLINFO_RESPONSE_CODE, &http_code);
response.status_code = static_cast<int>(http_code);
// 获取 Content-Type
char* content_type = nullptr;
curl_easy_getinfo(pImpl->curl, CURLINFO_CONTENT_TYPE, &content_type);
if (content_type) {
response.content_type = content_type;
}
response.body = std::move(response_body);
return response;
}
BinaryResponse HttpClient::fetch_binary(const std::string& url) {
BinaryResponse response;
response.status_code = 0;
if (!pImpl->curl) {
response.error_message = "CURL not initialized";
return response;
}
curl_easy_reset(pImpl->curl);
// 设置URL
curl_easy_setopt(pImpl->curl, CURLOPT_URL, url.c_str());
// 设置写回调
std::vector<uint8_t> response_data;
curl_easy_setopt(pImpl->curl, CURLOPT_WRITEFUNCTION, binary_write_callback);
curl_easy_setopt(pImpl->curl, CURLOPT_WRITEDATA, &response_data);
// 设置超时
curl_easy_setopt(pImpl->curl, CURLOPT_TIMEOUT, pImpl->timeout);
curl_easy_setopt(pImpl->curl, CURLOPT_CONNECTTIMEOUT, 10L);
// 设置用户代理
curl_easy_setopt(pImpl->curl, CURLOPT_USERAGENT, pImpl->user_agent.c_str());
// 设置是否跟随重定向
if (pImpl->follow_redirects) {
curl_easy_setopt(pImpl->curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(pImpl->curl, CURLOPT_MAXREDIRS, 10L);
}
// 支持 HTTPS
curl_easy_setopt(pImpl->curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(pImpl->curl, CURLOPT_SSL_VERIFYHOST, 2L);
// Cookie settings
if (!pImpl->cookie_file.empty()) {
curl_easy_setopt(pImpl->curl, CURLOPT_COOKIEFILE, pImpl->cookie_file.c_str());
curl_easy_setopt(pImpl->curl, CURLOPT_COOKIEJAR, pImpl->cookie_file.c_str());
} else {
curl_easy_setopt(pImpl->curl, CURLOPT_COOKIEFILE, "");
}
// 执行请求
CURLcode res = curl_easy_perform(pImpl->curl);
if (res != CURLE_OK) {
response.error_message = curl_easy_strerror(res);
return response;
}
// 获取响应码
long http_code = 0;
curl_easy_getinfo(pImpl->curl, CURLINFO_RESPONSE_CODE, &http_code);
response.status_code = static_cast<int>(http_code);
// 获取 Content-Type
char* content_type = nullptr;
curl_easy_getinfo(pImpl->curl, CURLINFO_CONTENT_TYPE, &content_type);
if (content_type) {
response.content_type = content_type;
}
response.data = std::move(response_data);
return response;
}
HttpResponse HttpClient::post(const std::string& url, const std::string& data,
const std::string& content_type) {
HttpResponse response;
response.status_code = 0;
if (!pImpl->curl) {
response.error_message = "CURL not initialized";
return response;
}
curl_easy_reset(pImpl->curl);
// Re-apply settings
curl_easy_setopt(pImpl->curl, CURLOPT_URL, url.c_str());
// Set write callback
std::string response_body;
curl_easy_setopt(pImpl->curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(pImpl->curl, CURLOPT_WRITEDATA, &response_body);
// Set timeout
curl_easy_setopt(pImpl->curl, CURLOPT_TIMEOUT, pImpl->timeout);
curl_easy_setopt(pImpl->curl, CURLOPT_CONNECTTIMEOUT, 10L);
// Set user agent
curl_easy_setopt(pImpl->curl, CURLOPT_USERAGENT, pImpl->user_agent.c_str());
// Set redirect following
if (pImpl->follow_redirects) {
curl_easy_setopt(pImpl->curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(pImpl->curl, CURLOPT_MAXREDIRS, 10L);
}
// HTTPS support
curl_easy_setopt(pImpl->curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(pImpl->curl, CURLOPT_SSL_VERIFYHOST, 2L);
// Cookie settings
if (!pImpl->cookie_file.empty()) {
curl_easy_setopt(pImpl->curl, CURLOPT_COOKIEFILE, pImpl->cookie_file.c_str());
curl_easy_setopt(pImpl->curl, CURLOPT_COOKIEJAR, pImpl->cookie_file.c_str());
} else {
curl_easy_setopt(pImpl->curl, CURLOPT_COOKIEFILE, "");
}
// Enable automatic decompression
curl_easy_setopt(pImpl->curl, CURLOPT_ACCEPT_ENCODING, "");
// Set POST method
curl_easy_setopt(pImpl->curl, CURLOPT_POST, 1L);
// Set POST data
curl_easy_setopt(pImpl->curl, CURLOPT_POSTFIELDS, data.c_str());
curl_easy_setopt(pImpl->curl, CURLOPT_POSTFIELDSIZE, data.length());
// Set Content-Type header
struct curl_slist* headers = nullptr;
std::string content_type_header = "Content-Type: " + content_type;
headers = curl_slist_append(headers, content_type_header.c_str());
curl_easy_setopt(pImpl->curl, CURLOPT_HTTPHEADER, headers);
// Perform request
CURLcode res = curl_easy_perform(pImpl->curl);
// Clean up headers
curl_slist_free_all(headers);
if (res != CURLE_OK) {
response.error_message = curl_easy_strerror(res);
return response;
}
// Get response code
long http_code = 0;
curl_easy_getinfo(pImpl->curl, CURLINFO_RESPONSE_CODE, &http_code);
response.status_code = static_cast<int>(http_code);
// Get Content-Type
char* resp_content_type = nullptr;
curl_easy_getinfo(pImpl->curl, CURLINFO_CONTENT_TYPE, &resp_content_type);
if (resp_content_type) {
response.content_type = resp_content_type;
}
response.body = std::move(response_body);
return response;
}
void HttpClient::set_timeout(long timeout_seconds) {
pImpl->timeout = timeout_seconds;
}
void HttpClient::set_user_agent(const std::string& user_agent) {
pImpl->user_agent = user_agent;
}
void HttpClient::set_follow_redirects(bool follow) {
pImpl->follow_redirects = follow;
}
void HttpClient::enable_cookies(const std::string& cookie_file) {
pImpl->cookie_file = cookie_file;
}