2#ifndef KURLYK_HEADER_KURLYK_HTTP_HTTP_REQUEST_MANAGER_HPP_INCLUDED
3#define KURLYK_HEADER_KURLYK_HTTP_HTTP_REQUEST_MANAGER_HPP_INCLUDED
38 std::unique_ptr<HttpRequest> request_ptr,
48 std::unique_ptr<HttpRequest> request_ptr,
50 std::lock_guard<std::mutex> lock(
m_mutex);
60# if __cplusplus >= 201402L
61 auto context = std::make_unique<HttpRequestContext>(std::move(request_ptr), std::move(callback));
63 auto context = std::unique_ptr<HttpRequestContext>(
67 const uint64_t group_id = context->request ? context->request->group_id : 0;
69 context->on_group_complete = [
this, group_id]() {
80 if (count_it->second > 1) {
131 uint64_t in_flight_token,
132 const std::string& general_key,
133 const std::string& specific_key) {
135 general_limit, specific_limit, in_flight_token, general_key, specific_key);
147 uint64_t in_flight_token,
148 const std::string& general_key,
149 const std::string& specific_key) {
151 general_limit, specific_limit, in_flight_token, general_key, specific_key);
161 template<
typename Duration = std::chrono::milliseconds>
165 const std::string& general_key,
166 const std::string& specific_key
169 general_limit, specific_limit, general_key, specific_key);
207 std::lock_guard<std::mutex> lock(
m_mutex);
217 bool invoke_now =
false;
218 uint64_t waiter_id = 0;
220 std::lock_guard<std::mutex> lock(
m_mutex);
230 if (invoke_now && callback) {
241 if (group_id == 0 || waiter_id == 0)
return false;
243 std::lock_guard<std::mutex> lock(
m_mutex);
247 auto& waiters = group_it->second;
248 for (
auto waiter_it = waiters.begin(); waiter_it != waiters.end(); ++waiter_it) {
249 if (waiter_it->id != waiter_id)
continue;
250 waiters.erase(waiter_it);
262 if (callback) callback();
265 std::lock_guard<std::mutex> lock(
m_mutex);
274 if (callback) callback();
277 bool invoke_now =
false;
279 std::lock_guard<std::mutex> lock(
m_mutex);
286 if (invoke_now && callback) {
322 std::lock_guard<std::mutex> lock(
m_mutex);
358 if (group_id == 0)
return 0;
367 std::lock_guard<std::mutex> lock(
m_mutex);
374 ready_waiters.emplace(it->first, std::move(it->second));
379 for (
const auto& item : ready_waiters) {
380 for (
const auto& waiter : item.second) {
381 if (waiter.callback) waiter.callback();
389 std::lock_guard<std::mutex> lock(
m_mutex);
394 for (
const auto& item : waiters) {
395 for (
const auto& waiter : item.second) {
396 if (waiter.callback) waiter.callback();
402 if (group_id == 0)
return;
406 std::lock_guard<std::mutex> lock(
m_mutex);
409 if (count_it->second > 1) {
415 ready_waiters = std::move(waiter_it->second);
421 for (
const auto& waiter : ready_waiters) {
422 if (waiter.callback) waiter.callback();
428 std::unique_lock<std::mutex> lock(
m_mutex);
431 std::vector<std::unique_ptr<HttpRequestContext>> pending_request;
432 std::vector<std::unique_ptr<HttpRequestContext>> failed_requests;
437 auto& request = context->request;
440 failed_requests.push_back(std::move(context));
447 auto general_limit = request->general_rate_limit;
448 auto specific_limit = request->specific_rate_limit;
449 uint64_t token = context->in_flight_token;
450 auto general_key = request->general_rate_limit_key;
451 auto specific_key = request->specific_rate_limit_key;
455 auto old_on_complete = std::move(context->on_complete);
456 context->on_complete = [
this, general_limit, specific_limit, token, general_key, specific_key]() {
465 request->general_rate_limit_key,
466 request->specific_rate_limit_key);
468 context->on_complete = std::move(old_on_complete);
473 pending_request.push_back(std::move(context));
479 if (!failed_requests.empty()) {
480 for (
const auto &context : failed_requests) {
481# if __cplusplus >= 201402L
482 auto response = std::make_unique<HttpResponse>();
484 auto response = std::unique_ptr<HttpResponse>(
new HttpResponse());
486 const long BAD_REQUEST = 400;
488 response->status_code = BAD_REQUEST;
489 response->ready =
true;
490 context->invoke_final_callback(std::move(response));
492 failed_requests.clear();
496 if (pending_request.empty())
return;
498# if __cplusplus >= 201402L
504 for (
auto& context : pending_request) {
505 if (!context)
continue;
506# if __cplusplus >= 201402L
507 auto response = std::make_unique<HttpResponse>();
509 auto response = std::unique_ptr<HttpResponse>(
new HttpResponse());
512 response->status_code = 499;
513 response->ready =
true;
514 context->invoke_final_callback(std::move(response));
525 if (!request->process()) {
529 auto failed_requests = request->extract_failed_requests();
531 for (
auto& request : failed_requests) {
541 auto& request_context = *it;
542 if (!request_context || !request_context->request) {
547 const auto now = std::chrono::steady_clock::now();
548 const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - request_context->start_time);
549 const auto& retry_delay_ms = request_context->request->retry_delay_ms;
550 if (duration.count() >= retry_delay_ms) {
551 std::unique_lock<std::mutex> lock(
m_mutex);
563 std::unique_lock<std::mutex> lock(
m_mutex);
571 std::list<std::unique_ptr<HttpRequestContext>> canceled_pending_requests;
574 const auto& ctx = *pending_it;
579 canceled_pending_requests.push_back(std::move(*pending_it));
584 for (
const auto& request_context : canceled_pending_requests) {
590 const auto& request_context = *failed_it;
591 if (!
matches_cancel(request_context, requests_to_cancel, groups_to_cancel)) {
600 handler->cancel_requests(requests_to_cancel, groups_to_cancel);
608 const std::unique_ptr<HttpRequestContext>& ctx,
611 if (!ctx || !ctx->request)
return false;
612 const auto request_id = ctx->request->request_id;
613 const auto group_id = ctx->request->group_id;
615 (request_id != 0 && requests_to_cancel.count(request_id) > 0) ||
616 (group_id != 0 && groups_to_cancel.count(group_id) > 0);
620# if __cplusplus >= 201402L
621 auto response = std::make_unique<HttpResponse>();
623 auto response = std::unique_ptr<HttpResponse>(
new HttpResponse());
626 response->status_code = 499;
627 response->ready =
true;
632 for (
const auto& request : requests_to_cancel) {
633 for (
const auto& callback : request.second) {
634 if (callback) callback();
641 std::unique_lock<std::mutex> lock(
m_mutex);
648 for (
const auto &request_context : pending_requests) {
649# if __cplusplus >= 201402L
650 auto response = std::make_unique<HttpResponse>();
652 auto response = std::unique_ptr<HttpResponse>(
new HttpResponse());
654 const long CANCELED_REQUEST_CODE = 499;
656 response->status_code = CANCELED_REQUEST_CODE;
657 response->ready =
true;
658 request_context->invoke_final_callback(std::move(response));
660 for (
const auto &request_context : failed_requests) {
661# if __cplusplus >= 201402L
662 auto response = std::make_unique<HttpResponse>();
664 auto response = std::unique_ptr<HttpResponse>(
new HttpResponse());
666 const long CANCELED_REQUEST_CODE = 499;
668 response->status_code = CANCELED_REQUEST_CODE;
669 response->ready =
true;
670 request_context->invoke_final_callback(std::move(response));
676 curl_global_init(CURL_GLOBAL_ALL);
681 curl_global_cleanup();
Manages multiple asynchronous HTTP requests using libcurl's multi interface.
Defines the RateLimitDelay result type for time-until-allowed queries.
Defines RAII handle for keeping HTTP rate limits alive.
Defines the HttpRateLimiter class for managing rate limits on HTTP requests.
Defines the HttpRequestContext class for managing HTTP request context, including retries and timing.
HttpBatchRequestHandler(std::vector< std::unique_ptr< HttpRequestContext > > &context_list)
Constructs a handler for managing multiple HTTP requests asynchronously.
Manages rate limits for HTTP requests.
bool remove_limit(long limit_id)
Releases manager-owned handle for the specified limit ID.
HttpRateLimitHandlePtr get_limit(long limit_id)
Returns manager-owned handle by ID.
HttpRateLimitHandlePtr create_limit_handle(long requests_per_period, long period_ms, bool sequential=false)
Creates a new rate limit and returns its RAII handle.
void release_request(const HttpRateLimitHandlePtr &general_limit, const HttpRateLimitHandlePtr &specific_limit, uint64_t in_flight_token, const std::string &general_key, const std::string &specific_key)
Releases in-flight tokens for sequential rate limits.
bool allow_request(const HttpRateLimitHandlePtr &general_limit, const HttpRateLimitHandlePtr &specific_limit, uint64_t in_flight_token, const std::string &general_key, const std::string &specific_key)
Checks if a request is allowed by two optional rate-limit handles with in-flight token tracking for s...
RateLimitDelay< Duration > time_until_next_allowed(const HttpRateLimitHandlePtr &general_limit, const HttpRateLimitHandlePtr &specific_limit, const std::string &general_key, const std::string &specific_key)
HttpRequestContext()=default
std::unordered_map< uint64_t, waiter_list_t > waiter_map_t
void complete_group_request(uint64_t group_id)
HttpRequestManager(const HttpRequestManager &)=delete
Deleted copy constructor to enforce the singleton pattern.
void notify_group_waiters_if_idle()
std::size_t max_pending_requests() const
Returns the current maximum pending request count.
std::atomic< bool > m_shutdown
Flag indicating if shutdown has been requested.
static bool matches_cancel(const std::unique_ptr< HttpRequestContext > &ctx, const cancel_map_t &requests_to_cancel, const cancel_map_t &groups_to_cancel)
uint64_t generate_group_id()
Generates a new group ID.
bool has_requests_by_group_id(uint64_t group_id) const
Checks whether an admitted request remains outstanding for a group.
void set_max_pending_requests(std::size_t max_pending_requests)
Sets the maximum number of pending requests accepted into the global queue.
cancel_map_t m_groups_to_cancel
Map of group IDs to their associated cancellation callbacks.
std::list< std::unique_ptr< HttpRequestContext > > m_failed_requests
List of failed HTTP requests for retrying.
void process_active_requests()
Processes active requests, moving failed ones to the failed requests list for retrying.
std::atomic< uint64_t > m_group_id_counter
Atomic counter for group IDs.
bool remove_limit(const HttpRateLimitHandlePtr &limit)
Releases manager-owned handle for the specified rate-limit handle.
std::size_t group_request_count(uint64_t group_id) const
Counts admitted requests that have not delivered their final callback for a group.
void cancel_request_by_id(uint64_t request_id, std::function< void()> callback)
Cancels one request by request ID.
std::atomic< uint64_t > m_next_in_flight_token
Atomic counter for sequential rate-limit tokens.
static HttpRequestManager & get_instance()
Get the singleton instance of HttpRequestManager.
std::list< std::unique_ptr< HttpBatchRequestHandler > > m_active_request_batches
List of currently active HTTP request batches.
bool is_loaded() const override
Checks if there are active, pending, or failed requests.
std::atomic< uint64_t > m_request_id_counter
Atomic counter for unique request IDs.
std::list< GroupWaiter > waiter_list_t
void process_retry_failed_requests()
Attempts to retry failed requests if their retry delay has passed.
HttpRateLimitHandlePtr get_rate_limit(long limit_id)
Returns a handle for a registered rate limit ID.
std::atomic< std::size_t > m_max_pending_requests
Maximum number of requests accepted into the pending queue, or zero if unbounded.
uint64_t wait_requests_by_group_id(uint64_t group_id, std::function< void()> callback)
Registers a callback invoked after all requests from a group finish.
virtual ~HttpRequestManager()
Private destructor to clean up global resources.
RateLimitDelay< Duration > time_until_next_allowed(const HttpRateLimitHandlePtr &general_limit, const HttpRateLimitHandlePtr &specific_limit, const std::string &general_key, const std::string &specific_key)
Calculates delay until request is allowed by two handles.
SubmitResult submit_request(std::unique_ptr< HttpRequest > request_ptr, HttpResponseCallback callback)
Attempts to enqueue a new HTTP request and reports the admission result.
HttpRateLimiter m_rate_limiter
Rate limiter for controlling request frequency.
std::list< std::function< void()> > callback_list_t
void notify_all_group_waiters()
std::mutex m_mutex
Mutex to protect access to manager state shared with public entry points.
std::size_t group_request_count_unlocked(uint64_t group_id) const
void process_cancel_requests()
Processes and cancels HTTP requests based on their request or group IDs.
bool remove_limit(long limit_id)
Removes an existing rate limit with the specified identifier.
void cancel_requests_by_group_id(uint64_t group_id, std::function< void()> callback)
Cancels all requests with the specified group ID.
bool allow_request(const HttpRateLimitHandlePtr &general_limit, const HttpRateLimitHandlePtr &specific_limit, uint64_t in_flight_token, const std::string &general_key, const std::string &specific_key)
Checks if a request is allowed by two optional rate-limit handles.
cancel_map_t m_requests_to_cancel_by_id
Map of request IDs to their associated cancellation callbacks.
static HttpResponsePtr make_cancelled_response()
void release_request(const HttpRateLimitHandlePtr &general_limit, const HttpRateLimitHandlePtr &specific_limit, uint64_t in_flight_token, const std::string &general_key, const std::string &specific_key)
Releases in-flight tokens for sequential rate limits.
waiter_map_t m_group_waiters
Map of group IDs to callbacks waiting until a group becomes idle.
bool cancel_wait_requests_by_group_id(uint64_t group_id, uint64_t waiter_id)
Removes a previously registered group waiter.
std::unordered_map< uint64_t, callback_list_t > cancel_map_t
uint64_t generate_request_id()
Generates a new unique request ID.
std::unordered_map< uint64_t, std::size_t > m_group_request_counts
Outstanding admitted requests by group.
bool add_request(std::unique_ptr< HttpRequest > request_ptr, HttpResponseCallback callback)
Adds a new HTTP request to the manager.
void shutdown() override
Shuts down the request manager, clearing all active and pending requests.
HttpRequestManager & operator=(const HttpRequestManager &)=delete
Deleted copy assignment operator to enforce the singleton pattern.
static void invoke_cancel_callbacks(const cancel_map_t &requests_to_cancel)
HttpRequestManager()
Private constructor to initialize global resources (e.g., cURL).
void cleanup_pending_requests()
Cleans up pending requests, marking each as failed and invoking its callback.
uint64_t m_waiter_id_counter
Counter for group waiter IDs.
HttpRateLimitHandlePtr create_rate_limit(long requests_per_period, long period_ms, bool sequential=false)
Creates a rate-limit handle with specified parameters.
void process() override
Processes all requests in the manager.
void process_pending_requests()
Processes all pending requests, moving valid requests to active batches or marking them as failed.
std::list< std::unique_ptr< HttpRequestContext > > m_pending_requests
List of pending HTTP requests awaiting processing.
Represents an HTTP response.
Interface for modules managed by NetworkWorker (e.g., HTTP, WebSocket).
@ ShuttingDown
Operation was rejected because the owning subsystem is shutting down.
@ AbortedDuringDestruction
Request handler was destroyed before completion, causing the request to abort.
@ QueueLimitExceeded
Operation was rejected because the bounded queue is already full.
@ CancelledByUser
Request was cancelled explicitly by the user via cancel().
std::error_code make_error_code(ClientError e)
Creates a std::error_code from a ClientError value.
Primary namespace for the Kurlyk library, encompassing initialization, request management,...
std::function< void(HttpResponsePtr response)> HttpResponseCallback
Callback invoked with an HTTP response.
std::unique_ptr< HttpResponse > HttpResponsePtr
Owning pointer to an HTTP response.
std::shared_ptr< HttpRateLimitHandle > HttpRateLimitHandlePtr
Shared RAII handle for HTTP rate limits.
std::function< void()> callback
Result type for time-until-allowed queries.
Represents the synchronous result of trying to enqueue or submit work.
bool accepted
Indicates whether the work item was accepted for processing.