Kurlyk
Loading...
Searching...
No Matches
HttpRequestHandler.hpp
Go to the documentation of this file.
1#pragma once
2#ifndef _KURLYK_HTTP_REQUEST_HANDLER_HPP_INCLUDED
3#define _KURLYK_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_request_context->callback(std::move(m_response));
42 }
43 }
44
46 static size_t write_http_response_body(char* data, size_t size, size_t nmemb, void* userdata) {
47 size_t total_size = size * nmemb;
48 auto* buffer = static_cast<std::string*>(userdata);
49 if (buffer) {
50 buffer->append(data, total_size);
51 }
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 if (!retry_attempts ||
101 valid_statuses.count(m_response->status_code) ||
102 retry_attempt >= retry_attempts) {
104 m_response->ready = true;
105 m_request_context->callback(std::move(m_response));
106 m_callback_called = true;
107 return true;
108 }
109 m_request_context->start_time = std::chrono::steady_clock::now();
110 m_request_context->callback(std::move(m_response));
111 m_callback_called = true;
112 return false;
113 }
114
117 CURL* get_curl() noexcept { return m_curl; }
118
121 std::unique_ptr<HttpRequestContext> get_request_context() { return std::move(m_request_context); }
122
125 uint64_t get_request_id() { return m_request_context ? m_request_context->request->request_id : 0; }
126
128 void cancel() {
129 if (!m_callback_called) {
131 m_response->status_code = 499; // Client closed request
132 m_response->ready = true;
133 if (m_request_context) {
134 m_request_context->callback(std::move(m_response));
135 }
136 m_callback_called = true;
137 }
138 }
139
140 private:
141 std::unique_ptr<HttpRequestContext> m_request_context;
142 std::unique_ptr<HttpResponse> m_response;
143 CURL* m_curl = nullptr;
144 struct curl_slist* m_headers = nullptr;
145 char m_error_buffer[CURL_ERROR_SIZE];
146 bool m_callback_called = false;
147 mutable std::string m_ca_file;
148
150 void init_curl() {
151 if (!m_request_context) return;
152 const auto& request = m_request_context->request;
153 m_curl = curl_easy_init();
154 if (!m_curl) return;
155
156 curl_easy_setopt(m_curl, CURLOPT_URL, request->url.c_str());
157 curl_easy_setopt(m_curl, CURLOPT_NOSIGNAL, 1L); // Disable signals for thread safety.
158 curl_easy_setopt(m_curl, CURLOPT_CUSTOMREQUEST, request->method.c_str());
159 if (request->head_only) {
160 curl_easy_setopt(m_curl, CURLOPT_NOBODY, 1L);
161 }
162 curl_easy_setopt(m_curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
163
164 set_ssl_options(*request);
165 set_request_options(*request);
166
167 curl_easy_setopt(m_curl, CURLOPT_ERRORBUFFER, m_error_buffer);
168 curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, write_http_response_body);
169 curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &m_response->content);
170 curl_easy_setopt(m_curl, CURLOPT_HEADERDATA, &m_response->headers);
171 curl_easy_setopt(m_curl, CURLOPT_HEADERFUNCTION, parse_http_response_header);
172 curl_easy_setopt(m_curl, CURLOPT_PRIVATE, this);
173 }
174
176 curl_easy_getinfo(m_curl, CURLINFO_NAMELOOKUP_TIME, &m_response->namelookup_time);
177 curl_easy_getinfo(m_curl, CURLINFO_CONNECT_TIME, &m_response->connect_time);
178 curl_easy_getinfo(m_curl, CURLINFO_APPCONNECT_TIME, &m_response->appconnect_time);
179 curl_easy_getinfo(m_curl, CURLINFO_PRETRANSFER_TIME, &m_response->pretransfer_time);
180 curl_easy_getinfo(m_curl, CURLINFO_STARTTRANSFER_TIME, &m_response->starttransfer_time);
181 curl_easy_getinfo(m_curl, CURLINFO_TOTAL_TIME, &m_response->total_time);
182 }
183
185 void set_ssl_options(const HttpRequest& request) {
186 if (!request.cert_file.empty()) {
187 curl_easy_setopt(m_curl, CURLOPT_SSLCERT, request.cert_file.c_str());
188 }
189 if (!request.key_file.empty()) {
190 curl_easy_setopt(m_curl, CURLOPT_SSLKEY, request.key_file.c_str());
191 }
192 if (!request.ca_file.empty()) {
193 curl_easy_setopt(m_curl, CURLOPT_CAINFO, request.ca_file.c_str());
194 } else {
195 curl_easy_setopt(m_curl, CURLOPT_CAINFO, get_ca_file_path());
196 }
197 if (!request.ca_path.empty()) {
198 curl_easy_setopt(m_curl, CURLOPT_CAPATH, request.ca_path.c_str());
199 }
200 curl_easy_setopt(m_curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_DEFAULT);
201 }
202
204 void set_request_options(const HttpRequest& request) {
205 curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, request.follow_location);
206 curl_easy_setopt(m_curl, CURLOPT_MAXREDIRS, request.max_redirects);
207 curl_easy_setopt(m_curl, CURLOPT_AUTOREFERER, request.auto_referer);
208 curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, request.timeout);
209 curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, request.connect_timeout);
210
211 set_custom_headers(request);
212 set_proxy_options(request);
213 set_cookie_options(request);
214 set_request_body(request);
215 if (request.use_interface && request.proxy_server.empty()) {
216 curl_easy_setopt(m_curl, CURLOPT_INTERFACE, request.interface_name.c_str());
217 }
218 curl_easy_setopt(m_curl, CURLOPT_VERBOSE, request.verbose);
219 curl_easy_setopt(m_curl, CURLOPT_HEADER, request.debug_header);
220 }
221
223 void set_custom_headers(const HttpRequest& request) {
224 if (!request.user_agent.empty() && request.headers.count("User-Agent") == 0) {
225 curl_easy_setopt(m_curl, CURLOPT_USERAGENT, request.user_agent.c_str());
226 }
227 if (!request.accept_encoding.empty() && request.headers.count("Accept-Encoding") == 0) {
228 curl_easy_setopt(m_curl, CURLOPT_ACCEPT_ENCODING, request.accept_encoding.c_str());
229 }
230
231 for (const auto& header : request.headers) {
232 std::string header_line = header.first + ": " + header.second;
233 m_headers = curl_slist_append(m_headers, header_line.c_str());
234 if (!m_headers) {
235 curl_slist_free_all(m_headers);
236 break;
237 }
238 }
239 if (m_headers) {
240 curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_headers);
241 }
242 }
243
245 void set_proxy_options(const HttpRequest& request) {
246 if (!request.proxy_server.empty()) {
247 curl_easy_setopt(m_curl, CURLOPT_PROXY, request.proxy_server.c_str());
248 curl_easy_setopt(m_curl, CURLOPT_PROXYTYPE, to_curl_proxy_type(request.proxy_type));
249 curl_easy_setopt(m_curl, CURLOPT_HTTPPROXYTUNNEL, request.proxy_tunnel);
250 if (!request.proxy_auth.empty()) {
251 curl_easy_setopt(m_curl, CURLOPT_PROXYUSERPWD, request.proxy_auth.c_str());
252 curl_easy_setopt(m_curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
253 }
254 }
255 }
256
258 void set_cookie_options(const HttpRequest& request) {
259 if (!request.cookie.empty() && request.headers.count("Cookie") == 0) {
260 curl_easy_setopt(m_curl, CURLOPT_COOKIE, request.cookie.c_str());
261 } else if (!request.cookie_file.empty()) {
262 curl_easy_setopt(m_curl, CURLOPT_COOKIEFILE, request.cookie_file.c_str());
263 curl_easy_setopt(m_curl, CURLOPT_COOKIEJAR, request.cookie_file.c_str());
264 if (request.clear_cookie_file) {
265 curl_easy_setopt(m_curl, CURLOPT_COOKIELIST, "ALL");
266 }
267 }
268 }
269
271 void set_request_body(const HttpRequest& request) {
272 if (request.head_only) return;
273 if (utils::case_insensitive_equal(request.method, "POST") ||
274 utils::case_insensitive_equal(request.method, "PUT") ||
275 utils::case_insensitive_equal(request.method, "PATCH") ||
276 utils::case_insensitive_equal(request.method, "DELETE")) {
277 curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, request.content.c_str());
278 curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, static_cast<long>(request.content.size()));
279 }
280 }
281
283 const char* get_ca_file_path() const {
284# if defined(_WIN32)
285 m_ca_file = utils::get_exec_dir() + "\\curl-ca-bundle.crt";
286 m_ca_file = utils::utf8_to_ansi(m_ca_file);
287# else
288 m_ca_file = utils::get_exec_dir() + "/curl-ca-bundle.crt";
289# endif
290 return m_ca_file.c_str();
291 }
292 }; // HttpRequestHandler
293
294} // namespace kurlyk
295
296#endif // _KURLYK_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.
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 the body data received from the server and appends it to the 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.
std::string m_ca_file
Cached CA file path.
uint64_t get_request_id()
Retrieves the unique ID of the HTTP request.
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.
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, sends the request without a 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 the response received from an HTTP request, including headers, content,...
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.