Skip to content

Commit aaad656

Browse files
committed
feat: added HTTP client with RAII
1 parent ee18993 commit aaad656

File tree

1 file changed

+211
-0
lines changed

1 file changed

+211
-0
lines changed

include/httpClient.h

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
#ifndef PERPLEXITY_CPP_HTTPCLIENT_H
2+
#define PERPLEXITY_CPP_HTTPCLIENT_H
3+
#pragma once
4+
5+
#include <curl/curl.h>
6+
#include <string>
7+
#include <memory>
8+
#include <stdexcept>
9+
#include "exceptions.h"
10+
#include "config.h"
11+
12+
namespace perplexity {
13+
14+
/**
15+
* @brief RAII wrapper for libcurl
16+
*
17+
* Applies the RAII pattern for automatic CURL resource management.
18+
*/
19+
class HttpClient {
20+
private:
21+
CURL* curl_ = nullptr;
22+
curl_slist* headers_ = nullptr;
23+
std::string response_buffer_;
24+
long response_code_ = 0;
25+
26+
static size_t write_callback(void* contents, size_t size, size_t nmemb, void* userp) {
27+
size_t total_size = size * nmemb;
28+
std::string* buffer = static_cast<std::string*>(userp);
29+
buffer->append(static_cast<char*>(contents), total_size);
30+
return total_size;
31+
}
32+
33+
void initialize(const Config& config) {
34+
curl_ = curl_easy_init();
35+
if (!curl_) {
36+
throw NetworkError("Failed to initialize CURL");
37+
}
38+
39+
curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, write_callback);
40+
curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &response_buffer_);
41+
curl_easy_setopt(curl_, CURLOPT_TIMEOUT, config.get_timeout().count());
42+
43+
// SSL verification
44+
if (!config.should_verify_ssl()) {
45+
curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYPEER, 0L);
46+
curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYHOST, 0L);
47+
}
48+
49+
// Proxy
50+
if (config.get_proxy()) {
51+
curl_easy_setopt(curl_, CURLOPT_PROXY, config.get_proxy()->c_str());
52+
}
53+
54+
// User-Agent
55+
std::string user_agent = config.get_user_agent().value_or("perplexity-cpp/1.0");
56+
curl_easy_setopt(curl_, CURLOPT_USERAGENT, user_agent.c_str());
57+
58+
// Follow redirects
59+
curl_easy_setopt(curl_, CURLOPT_FOLLOWLOCATION, 1L);
60+
curl_easy_setopt(curl_, CURLOPT_MAXREDIRS, 5L);
61+
}
62+
63+
void cleanup() {
64+
if (headers_) {
65+
curl_slist_free_all(headers_);
66+
headers_ = nullptr;
67+
}
68+
if (curl_) {
69+
curl_easy_cleanup(curl_);
70+
curl_ = nullptr;
71+
}
72+
}
73+
74+
public:
75+
76+
explicit HttpClient(const Config& config) {
77+
initialize(config);
78+
}
79+
80+
~HttpClient() {
81+
cleanup();
82+
}
83+
84+
HttpClient(const HttpClient&) = delete;
85+
HttpClient& operator=(const HttpClient&) = delete;
86+
87+
HttpClient(HttpClient&& other) noexcept
88+
: curl_(other.curl_)
89+
, headers_(other.headers_)
90+
, response_buffer_(std::move(other.response_buffer_))
91+
, response_code_(other.response_code_) {
92+
other.curl_ = nullptr;
93+
other.headers_ = nullptr;
94+
}
95+
96+
HttpClient& operator=(HttpClient&& other) noexcept {
97+
if (this != &other) {
98+
cleanup();
99+
curl_ = other.curl_;
100+
headers_ = other.headers_;
101+
response_buffer_ = std::move(other.response_buffer_);
102+
response_code_ = other.response_code_;
103+
other.curl_ = nullptr;
104+
other.headers_ = nullptr;
105+
}
106+
return *this;
107+
}
108+
109+
void add_header(const std::string& header) {
110+
headers_ = curl_slist_append(headers_, header.c_str());
111+
}
112+
113+
// POST request
114+
std::string post(const std::string& url, const std::string& data) {
115+
if (!curl_) {
116+
throw NetworkError("CURL not initialized");
117+
}
118+
119+
response_buffer_.clear();
120+
121+
curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
122+
curl_easy_setopt(curl_, CURLOPT_POST, 1L);
123+
curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, data.c_str());
124+
curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, headers_);
125+
126+
CURLcode res = curl_easy_perform(curl_);
127+
128+
if (res != CURLE_OK) {
129+
std::string error_msg = curl_easy_strerror(res);
130+
131+
if (res == CURLE_OPERATION_TIMEDOUT) {
132+
throw TimeoutError("Request timed out: " + error_msg);
133+
}
134+
throw NetworkError("CURL request failed: " + error_msg);
135+
}
136+
137+
curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &response_code_);
138+
139+
return response_buffer_;
140+
}
141+
142+
// GET request
143+
std::string get(const std::string& url) {
144+
if (!curl_) {
145+
throw NetworkError("CURL not initialized");
146+
}
147+
148+
response_buffer_.clear();
149+
150+
curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
151+
curl_easy_setopt(curl_, CURLOPT_HTTPGET, 1L);
152+
curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, headers_);
153+
154+
CURLcode res = curl_easy_perform(curl_);
155+
156+
if (res != CURLE_OK) {
157+
std::string error_msg = curl_easy_strerror(res);
158+
159+
if (res == CURLE_OPERATION_TIMEDOUT) {
160+
throw TimeoutError("Request timed out: " + error_msg);
161+
}
162+
throw NetworkError("CURL request failed: " + error_msg);
163+
}
164+
165+
curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &response_code_);
166+
167+
return response_buffer_;
168+
}
169+
170+
long get_response_code() const {
171+
return response_code_;
172+
}
173+
174+
void reset() {
175+
response_buffer_.clear();
176+
response_code_ = 0;
177+
178+
if (headers_) {
179+
curl_slist_free_all(headers_);
180+
headers_ = nullptr;
181+
}
182+
}
183+
};
184+
185+
/**
186+
* @brief RAII initializer for libcurl (global)
187+
*
188+
* Must be created once at the start of the program.
189+
*/
190+
class CurlGlobalInit {
191+
public:
192+
CurlGlobalInit() {
193+
CURLcode res = curl_global_init(CURL_GLOBAL_ALL);
194+
if (res != CURLE_OK) {
195+
throw NetworkError("Failed to initialize CURL globally");
196+
}
197+
}
198+
199+
~CurlGlobalInit() {
200+
curl_global_cleanup();
201+
}
202+
203+
CurlGlobalInit(const CurlGlobalInit&) = delete;
204+
CurlGlobalInit& operator=(const CurlGlobalInit&) = delete;
205+
CurlGlobalInit(CurlGlobalInit&&) = delete;
206+
CurlGlobalInit& operator=(CurlGlobalInit&&) = delete;
207+
};
208+
209+
} // namespace perplexity
210+
211+
#endif //PERPLEXITY_CPP_HTTPCLIENT_H

0 commit comments

Comments
 (0)