Kurlyk
Loading...
Searching...
No Matches
HttpRequestHandler.hpp
Go to the documentation of this file.
1#pragma once
2#ifndef KURLYK_HEADER_KURLYK_HTTP_HTTP_REQUEST_MANAGER_HTTP_REQUEST_HANDLER_HPP_INCLUDED
3#define KURLYK_HEADER_KURLYK_HTTP_HTTP_REQUEST_MANAGER_HTTP_REQUEST_HANDLER_HPP_INCLUDED
4
7
8namespace kurlyk {
9
13 public:
14
17 explicit HttpRequestHandler(std::unique_ptr<HttpRequestContext> context)
18 : m_request_context(std::move(context)) {
19 std::fill(m_error_buffer, m_error_buffer + CURL_ERROR_SIZE, '\0');
20# if __cplusplus >= 201402L
21 m_response = std::make_unique<HttpResponse>();
22# else
23 m_response = std::unique_ptr<HttpResponse>(new HttpResponse());
24# endif
25 init_curl();
26 }
27
33 if (m_curl) {
34 curl_easy_cleanup(m_curl);
35 curl_slist_free_all(m_headers);
36 }
39 m_response->status_code = 499; // Client closed request
40 m_response->ready = true;
41 m_callback_called = true;
42 m_done = true;
43 m_request_context->invoke_final_callback(std::move(m_response));
44 }
45 }
46
48 static size_t write_http_response_body(char* data, size_t size, size_t nmemb, void* userdata) {
49 size_t total_size = size * nmemb;
50 auto* handler = static_cast<HttpRequestHandler*>(userdata);
51 if (handler) return handler->write_response_body(data, total_size);
52 return total_size;
53 }
54
56 static size_t parse_http_response_header(char* buffer, size_t size, size_t nitems, void* userdata) {
57 size_t buffer_size = size * nitems;
58 auto* headers = static_cast<Headers*>(userdata);
59 std::string key, value;
60 utils::parse_http_header_pair(buffer, buffer_size, key, value);
61 if (!key.empty()) {
62 headers->emplace(key, value);
63 }
64 return buffer_size;
65 }
66
68 bool handle_curl_message(CURLMsg* message) {
69 if (!m_response) return true;
70
71 m_response->error_message = std::string(m_error_buffer, std::strlen(m_error_buffer));
72 // m_response->error_code = utils::make_error_code(message->data.result);
73 curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &m_response->status_code);
74
75 // If a timeout occurred, override the HTTP response code.
76 if (message->data.result == CURLE_OPERATION_TIMEDOUT) {
77 m_response->status_code = 499; // Client Closed Request
78 }
79
80 if (m_response->status_code == 0 &&
81 message->data.result != CURLE_OK) {
82 m_response->status_code = 451; // Unavailable For Legal Reasons
83 }
84
85 if (message->data.result != CURLE_OK) {
86 m_response->error_code = utils::make_error_code(message->data.result);
87 } else
88 if (m_response->status_code >= 400) {
89 m_response->error_code = utils::make_http_error(m_response->status_code);
90 } else {
91 m_response->error_code = {};
92 }
93
94 const auto& valid_statuses = m_request_context->request->valid_statuses;
95 long retry_attempts = m_request_context->request->retry_attempts;
96 long& retry_attempt = m_request_context->retry_attempt;
97 ++retry_attempt;
98
99 m_response->retry_attempt = retry_attempt;
100 const bool has_curl_error = message->data.result != CURLE_OK;
101 const bool has_valid_status = valid_statuses.count(m_response->status_code) > 0;
102 const bool should_retry =
103 retry_attempts &&
104 (has_curl_error || !has_valid_status) &&
105 retry_attempt < retry_attempts &&
107
108 if (!should_retry) {
110 m_response->ready = true;
111 m_callback_called = true;
112 m_done = true;
113 m_request_context->invoke_final_callback(std::move(m_response));
114 return true;
115 }
116 m_request_context->start_time = std::chrono::steady_clock::now();
117 m_callback_called = true;
118 m_request_context->invoke_callback(std::move(m_response));
119 return false;
120 }
121
124 CURL* get_curl() noexcept { return m_curl; }
125
128 std::unique_ptr<HttpRequestContext> get_request_context() { return std::move(m_request_context); }
129
132 uint64_t get_request_id() { return m_request_context ? m_request_context->request->request_id : 0; }
133
136 uint64_t get_group_id() { return m_request_context ? m_request_context->request->group_id : 0; }
137
140 bool is_done() const noexcept { return m_done; }
141
144 void mark_done() noexcept { m_done = true; }
145
147 void cancel() {
148 if (!m_callback_called) {
150 m_response->status_code = 499; // Client closed request
151 m_response->ready = true;
152 m_callback_called = true;
153 m_done = true;
154 if (m_request_context) {
155 m_request_context->invoke_final_callback(std::move(m_response));
156 }
157 }
158 }
159
160 private:
161 std::unique_ptr<HttpRequestContext> m_request_context;
162 std::unique_ptr<HttpResponse> m_response;
163 CURL* m_curl = nullptr;
164 struct curl_slist* m_headers = nullptr;
165 char m_error_buffer[CURL_ERROR_SIZE];
166 bool m_callback_called = false;
167 bool m_has_stream_chunk = false;
168 bool m_done = false;
169 mutable std::string m_ca_file;
170
172 void init_curl() {
173 if (!m_request_context) return;
174 const auto& request = m_request_context->request;
175 m_curl = curl_easy_init();
176 if (!m_curl) return;
177
178 curl_easy_setopt(m_curl, CURLOPT_URL, request->url.c_str());
179 curl_easy_setopt(m_curl, CURLOPT_NOSIGNAL, 1L); // Disable signals for thread safety.
180 curl_easy_setopt(m_curl, CURLOPT_CUSTOMREQUEST, request->method.c_str());
181 if (request->head_only) {
182 curl_easy_setopt(m_curl, CURLOPT_NOBODY, 1L);
183 }
184 curl_easy_setopt(m_curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
185
186 set_ssl_options(*request);
187 set_request_options(*request);
188
189 curl_easy_setopt(m_curl, CURLOPT_ERRORBUFFER, m_error_buffer);
190 curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, write_http_response_body);
191 curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this);
192 curl_easy_setopt(m_curl, CURLOPT_HEADERDATA, &m_response->headers);
193 curl_easy_setopt(m_curl, CURLOPT_HEADERFUNCTION, parse_http_response_header);
194 curl_easy_setopt(m_curl, CURLOPT_PRIVATE, this);
195 }
196
198 size_t write_response_body(const char* data, size_t total_size) {
199 if (m_response) {
200 m_response->content.append(data, total_size);
201 }
202 emit_stream_chunk(data, total_size);
203 return total_size;
204 }
205
207 void emit_stream_chunk(const char* data, size_t total_size) {
208 if (!data || total_size == 0 || !m_request_context ||
209 !m_request_context->request ||
210 !m_request_context->request->streaming ||
211 !m_request_context->callback) {
212 return;
213 }
214
215# if __cplusplus >= 201402L
216 auto chunk = std::make_unique<HttpResponse>();
217# else
218 auto chunk = std::unique_ptr<HttpResponse>(new HttpResponse());
219# endif
220
221 if (m_response) {
222 chunk->headers = m_response->headers;
223 }
224 chunk->content.assign(data, total_size);
225 chunk->retry_attempt = m_request_context->retry_attempt + 1;
226 chunk->ready = false;
227 chunk->stream_chunk = true;
228 if (m_curl) {
229 curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &chunk->status_code);
230 }
231
232 m_has_stream_chunk = true;
233
234 m_request_context->invoke_callback(std::move(chunk));
235 }
236
238 curl_easy_getinfo(m_curl, CURLINFO_NAMELOOKUP_TIME, &m_response->namelookup_time);
239 curl_easy_getinfo(m_curl, CURLINFO_CONNECT_TIME, &m_response->connect_time);
240 curl_easy_getinfo(m_curl, CURLINFO_APPCONNECT_TIME, &m_response->appconnect_time);
241 curl_easy_getinfo(m_curl, CURLINFO_PRETRANSFER_TIME, &m_response->pretransfer_time);
242 curl_easy_getinfo(m_curl, CURLINFO_STARTTRANSFER_TIME, &m_response->starttransfer_time);
243 curl_easy_getinfo(m_curl, CURLINFO_TOTAL_TIME, &m_response->total_time);
244 }
245
247 void set_ssl_options(const HttpRequest& request) {
248 if (!request.cert_file.empty()) {
249 curl_easy_setopt(m_curl, CURLOPT_SSLCERT, request.cert_file.c_str());
250 }
251 if (!request.key_file.empty()) {
252 curl_easy_setopt(m_curl, CURLOPT_SSLKEY, request.key_file.c_str());
253 }
254 if (!request.ca_file.empty()) {
255 curl_easy_setopt(m_curl, CURLOPT_CAINFO, request.ca_file.c_str());
256 }
257# if defined(_WIN32)
258 else {
259 curl_easy_setopt(m_curl, CURLOPT_CAINFO, get_ca_file_path());
260 }
261# endif
262 if (!request.ca_path.empty()) {
263 curl_easy_setopt(m_curl, CURLOPT_CAPATH, request.ca_path.c_str());
264 }
265 curl_easy_setopt(m_curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_DEFAULT);
266 }
267
269 void set_request_options(const HttpRequest& request) {
270 curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, request.follow_location);
271 curl_easy_setopt(m_curl, CURLOPT_MAXREDIRS, request.max_redirects);
272 curl_easy_setopt(m_curl, CURLOPT_AUTOREFERER, request.auto_referer);
273 curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, request.timeout);
274 curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, request.connect_timeout);
275
276 set_custom_headers(request);
277 set_proxy_options(request);
278 set_cookie_options(request);
279 set_request_body(request);
280 if (request.use_interface && request.proxy_server.empty()) {
281 curl_easy_setopt(m_curl, CURLOPT_INTERFACE, request.interface_name.c_str());
282 }
283 curl_easy_setopt(m_curl, CURLOPT_VERBOSE, request.verbose);
284 curl_easy_setopt(m_curl, CURLOPT_HEADER, request.debug_header);
285 }
286
288 void set_custom_headers(const HttpRequest& request) {
289 if (!request.user_agent.empty() && request.headers.count("User-Agent") == 0) {
290 curl_easy_setopt(m_curl, CURLOPT_USERAGENT, request.user_agent.c_str());
291 }
292 if (!request.accept_encoding.empty() && request.headers.count("Accept-Encoding") == 0) {
293 curl_easy_setopt(m_curl, CURLOPT_ACCEPT_ENCODING, request.accept_encoding.c_str());
294 }
295
296 for (const auto& header : request.headers) {
297 std::string header_line = header.first + ": " + header.second;
298 m_headers = curl_slist_append(m_headers, header_line.c_str());
299 if (!m_headers) {
300 curl_slist_free_all(m_headers);
301 break;
302 }
303 }
304 if (m_headers) {
305 curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_headers);
306 }
307 }
308
310 void set_proxy_options(const HttpRequest& request) {
311 if (!request.proxy_server.empty()) {
312 curl_easy_setopt(m_curl, CURLOPT_PROXY, request.proxy_server.c_str());
313 curl_easy_setopt(m_curl, CURLOPT_PROXYTYPE, to_curl_proxy_type(request.proxy_type));
314 curl_easy_setopt(m_curl, CURLOPT_HTTPPROXYTUNNEL, request.proxy_tunnel);
315 if (!request.proxy_auth.empty()) {
316 curl_easy_setopt(m_curl, CURLOPT_PROXYUSERPWD, request.proxy_auth.c_str());
317 curl_easy_setopt(m_curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
318 }
319 }
320 }
321
323 void set_cookie_options(const HttpRequest& request) {
324 if (!request.cookie.empty() && request.headers.count("Cookie") == 0) {
325 curl_easy_setopt(m_curl, CURLOPT_COOKIE, request.cookie.c_str());
326 } else if (!request.cookie_file.empty()) {
327 curl_easy_setopt(m_curl, CURLOPT_COOKIEFILE, request.cookie_file.c_str());
328 curl_easy_setopt(m_curl, CURLOPT_COOKIEJAR, request.cookie_file.c_str());
329 if (request.clear_cookie_file) {
330 curl_easy_setopt(m_curl, CURLOPT_COOKIELIST, "ALL");
331 }
332 }
333 }
334
336 void set_request_body(const HttpRequest& request) {
337 if (request.head_only) return;
338 if (utils::case_insensitive_equal(request.method, "POST") ||
339 utils::case_insensitive_equal(request.method, "PUT") ||
340 utils::case_insensitive_equal(request.method, "PATCH") ||
341 utils::case_insensitive_equal(request.method, "DELETE")) {
342 curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, request.content.c_str());
343 curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, static_cast<long>(request.content.size()));
344 }
345 }
346
348 const char* get_ca_file_path() const {
349# if defined(_WIN32)
350 m_ca_file = utils::get_exec_dir() + "\\curl-ca-bundle.crt";
351 m_ca_file = utils::utf8_to_ansi(m_ca_file);
352# else
353 m_ca_file = utils::get_exec_dir() + "/curl-ca-bundle.crt";
354# endif
355 return m_ca_file.c_str();
356 }
357 }; // HttpRequestHandler
358
359} // namespace kurlyk
360
361#endif // KURLYK_HEADER_KURLYK_HTTP_HTTP_REQUEST_MANAGER_HTTP_REQUEST_HANDLER_HPP_INCLUDED
void cancel()
Marks the request as cancelled.
~HttpRequestHandler()
Destructor for HttpRequestHandler, handling cleanup of CURL and headers.
CURL * m_curl
CURL handle for the request.
bool is_done() const noexcept
Checks whether this request has already received its final callback.
bool m_has_stream_chunk
Indicates if a streaming body chunk was emitted.
bool m_done
Indicates if the request has received its final callback.
size_t write_response_body(const char *data, size_t total_size)
Stores received body data and emits a streaming chunk when enabled.
HttpRequestHandler(std::unique_ptr< HttpRequestContext > context)
Constructs an HttpRequestHandler with the specified request context.
void set_proxy_options(const HttpRequest &request)
Configures proxy options if set in the request.
static size_t write_http_response_body(char *data, size_t size, size_t nmemb, void *userdata)
Processes body data received from server and appends it to response content.
const char * get_ca_file_path() const
Gets the full path to the CA certificate file.
void set_ssl_options(const HttpRequest &request)
Sets SSL options such as cert, key, and CA file.
std::unique_ptr< HttpRequestContext > m_request_context
Context for the current request.
void set_cookie_options(const HttpRequest &request)
Sets cookie options if cookies are specified in the request.
static size_t parse_http_response_header(char *buffer, size_t size, size_t nitems, void *userdata)
Parses and stores response headers in the Headers container.
void init_curl()
Initializes CURL options for the request, setting headers, method, SSL, timeouts, and other parameter...
bool m_callback_called
Indicates if the callback was called.
bool handle_curl_message(CURLMsg *message)
Processes a CURL message and determines if a callback should be invoked.
uint64_t get_group_id()
Retrieves the group ID of the HTTP request.
std::string m_ca_file
Cached CA file path.
void emit_stream_chunk(const char *data, size_t total_size)
Invokes the response callback with an intermediate body chunk.
uint64_t get_request_id()
Retrieves the unique ID of the HTTP request.
void mark_done() noexcept
Marks the request as done without invoking a callback.
struct curl_slist * m_headers
CURL headers list.
char m_error_buffer[CURL_ERROR_SIZE]
Buffer for CURL error messages.
CURL * get_curl() noexcept
Retrieves the CURL handle associated with this request.
std::unique_ptr< HttpRequestContext > get_request_context()
Returns the unique pointer to the HttpRequestContext object.
void set_request_options(const HttpRequest &request)
Sets general options such as headers, cookies, and proxy.
void set_request_body(const HttpRequest &request)
Sets request body content for applicable HTTP methods.
void set_custom_headers(const HttpRequest &request)
Appends custom headers to the request if provided.
std::unique_ptr< HttpResponse > m_response
Response object.
Represents an HTTP request configuration.
bool proxy_tunnel
Enable proxy tunneling.
std::string user_agent
User-Agent header.
std::string proxy_auth
Proxy authentication in <username:password> format.
long timeout
Request timeout in seconds.
bool head_only
If true, does not download the response body (HEAD-like behavior).
bool follow_location
Automatically follow HTTP redirects.
bool debug_header
Include headers in debug output (CURLOPT_HEADER).
long connect_timeout
Connection timeout in seconds.
std::string cert_file
Path to the client certificate file.
bool verbose
Enable verbose output (CURLOPT_VERBOSE).
bool use_interface
Enable the specified network interface.
std::string interface_name
Network interface name to use for the request.
std::string content
Data payload for the request.
std::string cookie_file
Path to the cookie file; if empty, cookies are not saved.
std::string proxy_server
Proxy address in <ip:port> format.
std::string cookie
Cookie data as a string.
ProxyType proxy_type
Proxy type (e.g., HTTP, SOCKS5).
Headers headers
HTTP request headers.
bool auto_referer
Automatically set Referer header.
std::string accept_encoding
Accept-Encoding header.
std::string ca_file
Path to the CA certificate file.
std::string key_file
Path to the private key for the client certificate.
bool clear_cookie_file
Flag to clear the cookie file at the start of the request.
long max_redirects
Maximum allowed redirects.
std::string ca_path
Path to a directory containing CA certificates.
std::string method
HTTP request method (e.g., "GET", "POST").
Represents an HTTP response.
bool case_insensitive_equal(const std::string &str1, const std::string &str2) noexcept
Compares two strings case-insensitively.
@ AbortedDuringDestruction
Request handler was destroyed before completion, causing the request to abort.
@ CancelledByUser
Request was cancelled explicitly by the user via cancel().
void parse_http_header_pair(const char *buffer, const size_t &size, std::string &key, std::string &value)
Parses a header pair from a buffer.
std::error_code make_http_error(int status_code)
Creates an std::error_code from an HTTP status code.
std::error_code make_error_code(ClientError e)
Creates a std::error_code from a ClientError value.
std::string get_exec_dir()
Retrieves the directory of the executable file.
Primary namespace for the Kurlyk library, encompassing initialization, request management,...
utils::CaseInsensitiveMultimap Headers
Alias for HTTP headers, providing a case-insensitive unordered multimap.
Enables use of ClientError with std::error_code.