5.3. HTTP 客户端
5.3.1. 使用 libcurl
libcurl 是最常用的 HTTP 客户端库。
5.3.1.1. 安装
# Ubuntu
sudo apt install libcurl4-openssl-dev
# macOS
brew install curl
# 编译
g++ -lcurl program.cpp
5.3.1.2. 简单 GET 请求
#include <curl/curl.h>
#include <string>
// 回调函数
static size_t write_callback(char* ptr, size_t size, size_t nmemb,
std::string* data) {
data->append(ptr, size * nmemb);
return size * nmemb;
}
std::string http_get(const std::string& url) {
CURL* curl = curl_easy_init();
if (!curl) {
throw std::runtime_error("curl_easy_init failed");
}
std::string response;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // 跟随重定向
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); // 超时 30 秒
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
curl_easy_cleanup(curl);
throw std::runtime_error(curl_easy_strerror(res));
}
curl_easy_cleanup(curl);
return response;
}
5.3.1.3. POST 请求
std::string http_post(const std::string& url, const std::string& data) {
CURL* curl = curl_easy_init();
if (!curl) {
throw std::runtime_error("curl_easy_init failed");
}
std::string response;
// 设置请求头
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
CURLcode res = curl_easy_perform(curl);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
throw std::runtime_error(curl_easy_strerror(res));
}
return response;
}
5.3.1.4. RAII 封装
class CurlHandle {
CURL* handle_;
public:
CurlHandle() : handle_(curl_easy_init()) {
if (!handle_) {
throw std::runtime_error("curl_easy_init failed");
}
}
~CurlHandle() {
curl_easy_cleanup(handle_);
}
CurlHandle(const CurlHandle&) = delete;
CurlHandle& operator=(const CurlHandle&) = delete;
CURL* get() const { return handle_; }
template<typename T>
CurlHandle& setopt(CURLoption option, T value) {
curl_easy_setopt(handle_, option, value);
return *this;
}
void perform() {
CURLcode res = curl_easy_perform(handle_);
if (res != CURLE_OK) {
throw std::runtime_error(curl_easy_strerror(res));
}
}
};
// 全局初始化
class CurlGlobal {
public:
CurlGlobal() { curl_global_init(CURL_GLOBAL_ALL); }
~CurlGlobal() { curl_global_cleanup(); }
};
static CurlGlobal curl_global;
5.3.1.5. 异步请求(多线程)
#include <curl/curl.h>
#include <future>
#include <vector>
class AsyncHttpClient {
public:
std::future<std::string> get_async(const std::string& url) {
return std::async(std::launch::async, [url]() {
return http_get(url);
});
}
std::vector<std::string> get_multiple(
const std::vector<std::string>& urls)
{
std::vector<std::future<std::string>> futures;
for (const auto& url : urls) {
futures.push_back(get_async(url));
}
std::vector<std::string> results;
for (auto& f : futures) {
results.push_back(f.get());
}
return results;
}
};
5.3.1.6. curl_multi 接口
// 更高效的异步方式
void multi_request() {
CURLM* multi = curl_multi_init();
// 添加多个请求
CURL* curl1 = curl_easy_init();
curl_easy_setopt(curl1, CURLOPT_URL, "http://example.com/1");
curl_multi_add_handle(multi, curl1);
CURL* curl2 = curl_easy_init();
curl_easy_setopt(curl2, CURLOPT_URL, "http://example.com/2");
curl_multi_add_handle(multi, curl2);
// 执行
int running = 0;
do {
curl_multi_perform(multi, &running);
if (running) {
curl_multi_wait(multi, nullptr, 0, 1000, nullptr);
}
} while (running);
// 清理
curl_multi_remove_handle(multi, curl1);
curl_multi_remove_handle(multi, curl2);
curl_easy_cleanup(curl1);
curl_easy_cleanup(curl2);
curl_multi_cleanup(multi);
}
5.3.2. 使用 Boost.Beast
Boost.Beast 提供现代 C++ 风格的 HTTP 支持。
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
using tcp = net::ip::tcp;
std::string beast_http_get(const std::string& host,
const std::string& path) {
net::io_context ioc;
tcp::resolver resolver(ioc);
beast::tcp_stream stream(ioc);
// 解析主机名
auto results = resolver.resolve(host, "80");
// 连接
stream.connect(results);
// 发送请求
http::request<http::string_body> req{http::verb::get, path, 11};
req.set(http::field::host, host);
req.set(http::field::user_agent, "Beast");
http::write(stream, req);
// 读取响应
beast::flat_buffer buffer;
http::response<http::string_body> res;
http::read(stream, buffer, res);
// 关闭连接
beast::error_code ec;
stream.socket().shutdown(tcp::socket::shutdown_both, ec);
return res.body();
}
5.3.3. 使用 httplib(头文件库)
// https://github.com/yhirose/cpp-httplib
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "httplib.h"
void httplib_example() {
httplib::Client cli("https://api.example.com");
// GET
auto res = cli.Get("/path");
if (res && res->status == 200) {
std::cout << res->body << "\n";
}
// POST JSON
httplib::Headers headers = {
{"Content-Type", "application/json"}
};
auto res2 = cli.Post("/api", headers, R"({"key": "value"})",
"application/json");
}
小技巧
HTTP 客户端库选择:
libcurl: 功能全面,跨平台,C 接口
Boost.Beast: 现代 C++,与 Asio 集成
cpp-httplib: 单头文件,简单易用
cpr: libcurl 的 C++ 封装