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