// retoor #include "http_client.h" #include #include #include #include #include #include #include #include #define HTTP_MAX_RETRIES 3 #define HTTP_RETRY_DELAY_MS 2000 struct http_client_t { char *bearer_token; long timeout_seconds; long connect_timeout_seconds; bool show_spinner; }; struct response_buffer_t { char *data; size_t size; }; static struct timespec spinner_start_time = {0, 0}; static volatile int spinner_running = 0; static double get_elapsed_seconds(void) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); return (now.tv_sec - spinner_start_time.tv_sec) + (now.tv_nsec - spinner_start_time.tv_nsec) / 1e9; } static void *spinner_thread(void *arg) { (void)arg; const char *frames[] = {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}; int frame = 0; while (spinner_running) { double elapsed = get_elapsed_seconds(); fprintf(stderr, "\r%s Querying AI... (%.1fs) ", frames[frame % 10], elapsed); fflush(stderr); frame++; usleep(80000); } return NULL; } static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) { size_t total_size = size * nmemb; struct response_buffer_t *response = (struct response_buffer_t *)userp; if (total_size > SIZE_MAX - response->size - 1) { return 0; } char *ptr = realloc(response->data, response->size + total_size + 1); if (!ptr) { return 0; } response->data = ptr; memcpy(&(response->data[response->size]), contents, total_size); response->size += total_size; response->data[response->size] = '\0'; return total_size; } http_client_handle http_client_create(const char *bearer_token) { struct http_client_t *client = calloc(1, sizeof(struct http_client_t)); if (!client) return NULL; if (bearer_token) { client->bearer_token = strdup(bearer_token); if (!client->bearer_token) { free(client); return NULL; } } client->timeout_seconds = 300; client->connect_timeout_seconds = 10; client->show_spinner = true; return client; } void http_client_destroy(http_client_handle client) { if (!client) return; free(client->bearer_token); free(client); } void http_client_set_show_spinner(http_client_handle client, bool show) { if (client) client->show_spinner = show; } void http_client_set_timeout(http_client_handle client, long timeout_seconds) { if (client) client->timeout_seconds = timeout_seconds; } void http_client_set_connect_timeout(http_client_handle client, long timeout_seconds) { if (client) client->connect_timeout_seconds = timeout_seconds; } r_status_t http_post(http_client_handle client, const char *url, const char *data, char **response) { if (!client || !url || !response) return R_ERROR_INVALID_ARG; CURL *curl = NULL; struct curl_slist *headers = NULL; struct response_buffer_t resp = {NULL, 0}; int retry_count = 0; pthread_t spinner_tid = 0; r_status_t status = R_SUCCESS; *response = NULL; if (client->show_spinner) { clock_gettime(CLOCK_MONOTONIC, &spinner_start_time); spinner_running = 1; pthread_create(&spinner_tid, NULL, spinner_thread, NULL); } while (retry_count < HTTP_MAX_RETRIES) { free(resp.data); resp.data = malloc(1); resp.size = 0; if (!resp.data) { status = R_ERROR_OUT_OF_MEMORY; goto cleanup; } resp.data[0] = '\0'; curl = curl_easy_init(); if (!curl) { status = R_ERROR_HTTP_CONNECTION; goto cleanup; } curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, client->connect_timeout_seconds); curl_easy_setopt(curl, CURLOPT_TIMEOUT, client->timeout_seconds); headers = curl_slist_append(headers, "Content-Type: application/json"); if (client->bearer_token) { char bearer_header[2048]; snprintf(bearer_header, sizeof(bearer_header), "Authorization: Bearer %s", client->bearer_token); headers = curl_slist_append(headers, bearer_header); } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&resp); CURLcode res = curl_easy_perform(curl); curl_slist_free_all(headers); headers = NULL; curl_easy_cleanup(curl); curl = NULL; if (res == CURLE_OK) { *response = resp.data; resp.data = NULL; status = R_SUCCESS; goto cleanup; } retry_count++; if (client->show_spinner) { spinner_running = 0; pthread_join(spinner_tid, NULL); spinner_tid = 0; fprintf(stderr, "\r \r"); } fprintf(stderr, "Network error: %s (attempt %d/%d)\n", curl_easy_strerror(res), retry_count, HTTP_MAX_RETRIES); if (retry_count < HTTP_MAX_RETRIES) { fprintf(stderr, "Retrying in %d seconds...\n", HTTP_RETRY_DELAY_MS / 1000); usleep(HTTP_RETRY_DELAY_MS * 1000); if (client->show_spinner) { clock_gettime(CLOCK_MONOTONIC, &spinner_start_time); spinner_running = 1; pthread_create(&spinner_tid, NULL, spinner_thread, NULL); } } } status = R_ERROR_HTTP_TIMEOUT; cleanup: if (client->show_spinner && spinner_tid) { spinner_running = 0; pthread_join(spinner_tid, NULL); fprintf(stderr, "\r \r"); fflush(stderr); } if (headers) curl_slist_free_all(headers); if (curl) curl_easy_cleanup(curl); free(resp.data); return status; } r_status_t http_get(http_client_handle client, const char *url, char **response) { if (!client || !url || !response) return R_ERROR_INVALID_ARG; CURL *curl = NULL; struct curl_slist *headers = NULL; struct response_buffer_t resp = {NULL, 0}; int retry_count = 0; r_status_t status = R_SUCCESS; *response = NULL; while (retry_count < HTTP_MAX_RETRIES) { free(resp.data); resp.data = malloc(1); resp.size = 0; if (!resp.data) { status = R_ERROR_OUT_OF_MEMORY; goto cleanup; } resp.data[0] = '\0'; curl = curl_easy_init(); if (!curl) { status = R_ERROR_HTTP_CONNECTION; goto cleanup; } curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, client->connect_timeout_seconds); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L); headers = curl_slist_append(headers, "Content-Type: application/json"); if (client->bearer_token) { char bearer_header[2048]; snprintf(bearer_header, sizeof(bearer_header), "Authorization: Bearer %s", client->bearer_token); headers = curl_slist_append(headers, bearer_header); } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&resp); CURLcode res = curl_easy_perform(curl); curl_slist_free_all(headers); headers = NULL; curl_easy_cleanup(curl); curl = NULL; if (res == CURLE_OK) { *response = resp.data; resp.data = NULL; status = R_SUCCESS; goto cleanup; } retry_count++; fprintf(stderr, "Network error: %s (attempt %d/%d)\n", curl_easy_strerror(res), retry_count, HTTP_MAX_RETRIES); if (retry_count < HTTP_MAX_RETRIES) { fprintf(stderr, "Retrying in %d seconds...\n", HTTP_RETRY_DELAY_MS / 1000); usleep(HTTP_RETRY_DELAY_MS * 1000); } } status = R_ERROR_HTTP_TIMEOUT; cleanup: if (headers) curl_slist_free_all(headers); if (curl) curl_easy_cleanup(curl); free(resp.data); return status; } r_status_t http_post_simple(const char *url, const char *bearer_token, const char *data, char **response) { http_client_handle client = http_client_create(bearer_token); if (!client) return R_ERROR_OUT_OF_MEMORY; r_status_t status = http_post(client, url, data, response); http_client_destroy(client); return status; } r_status_t http_get_simple(const char *url, const char *bearer_token, char **response) { http_client_handle client = http_client_create(bearer_token); if (!client) return R_ERROR_OUT_OF_MEMORY; http_client_set_show_spinner(client, false); r_status_t status = http_get(client, url, response); http_client_destroy(client); return status; }