Mercurial
diff seobeo/s_http_client.c @ 226:3fa4bf481f42
[merge] Merge hg-web into default
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sun, 02 Aug 2026 14:42:01 -0700 |
| parents | ce7f4400c2de |
| children |
line wrap: on
line diff
--- a/seobeo/s_http_client.c Sun Aug 02 08:52:13 2026 -0700 +++ b/seobeo/s_http_client.c Sun Aug 02 14:42:01 2026 -0700 @@ -1,5 +1,46 @@ #include "seobeo/seobeo.h" #include <ctype.h> +#include <time.h> + +static int64_t Seobeo_Client_Monotonic_Milliseconds(void) +{ + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000; +} + +static boolean Seobeo_Client_Read_Timed_Out(int32 timeout_ms, int64_t last_progress_ms) +{ + return timeout_ms > 0 && + Seobeo_Client_Monotonic_Milliseconds() - last_progress_ms >= timeout_ms; +} + +static size_t Seobeo_Client_Find_Header_Length(const uint8 *buffer, size_t length) +{ + if (!buffer || length < 4) + return 0; + for (size_t i = 0; i + 3 < length; i++) + { + if (buffer[i] == '\r' && buffer[i + 1] == '\n' && + buffer[i + 2] == '\r' && buffer[i + 3] == '\n') + return i + 4; + } + return 0; +} + +static const char *Seobeo_Client_Header_Value( + Seobeo_Request_Entry *headers, + const char *name) +{ + if (!headers || !name) + return NULL; + for (size_t i = 0; i < Dowa_Array_Length(headers); i++) + { + if (headers[i].key && strcasecmp(headers[i].key, name) == 0) + return headers[i].value; + } + return NULL; +} static void Seobeo_Client_Parse_Url(const char *url, char **p_host, char **p_port, char **p_path, boolean *p_use_tls, Dowa_Arena *p_arena) @@ -87,6 +128,7 @@ p_req->follow_redirects = FALSE; p_req->max_redirects = 10; + p_req->timeout_ms = 0; return p_req; } @@ -149,6 +191,13 @@ p_req->max_redirects = max_redirects > 0 ? max_redirects : 10; } +void Seobeo_Client_Request_Set_Timeout_Milliseconds(Seobeo_Client_Request *p_req, int32 timeout_ms) +{ + if (!p_req) + return; + p_req->timeout_ms = timeout_ms > 0 ? timeout_ms : 0; +} + void Seobeo_Client_Request_Set_Download_Path(Seobeo_Client_Request *p_req, const char *path) { if (!p_req || !path) @@ -223,7 +272,10 @@ return offset; } -static Seobeo_Client_Response *Seobeo_Client_Parse_Response(Seobeo_Handle *p_handle, const char *download_path) +static Seobeo_Client_Response *Seobeo_Client_Parse_Response( + Seobeo_Handle *p_handle, + const char *download_path, + int32 timeout_ms) { Seobeo_Client_Response *p_resp = malloc(sizeof(Seobeo_Client_Response)); if (!p_resp) @@ -238,27 +290,49 @@ return NULL; } + int64_t last_progress_ms = Seobeo_Client_Monotonic_Milliseconds(); while (TRUE) { int r = Seobeo_Handle_Read(p_handle); - if (r < 0) - return p_resp; if (r == -2) break; + if (r < 0) + { + Seobeo_Client_Response_Destroy(p_resp); + return NULL; + } + if (r > 0) + last_progress_ms = Seobeo_Client_Monotonic_Milliseconds(); - if (p_handle->read_buffer_len >= 4 && strstr((char*)p_handle->read_buffer, "\r\n\r\n") != NULL) + if (Seobeo_Client_Find_Header_Length( + p_handle->read_buffer, p_handle->read_buffer_len) > 0) break; if (r == 0) + { + if (Seobeo_Client_Read_Timed_Out(timeout_ms, last_progress_ms)) + { + Seobeo_Log(SEOBEO_ERROR, "HTTP response header timed out\n"); + Seobeo_Client_Response_Destroy(p_resp); + return NULL; + } + usleep(1000); continue; + } } - char *buf = (char*)p_handle->read_buffer; - char *hdr_end = strstr(buf, "\r\n\r\n"); - if (!hdr_end) - return p_resp; + size_t hdr_len = Seobeo_Client_Find_Header_Length( + p_handle->read_buffer, p_handle->read_buffer_len); + if (hdr_len == 0) + { + Seobeo_Client_Response_Destroy(p_resp); + return NULL; + } - size_t hdr_len = hdr_end - buf + 4; + char *buf = Dowa_Arena_Allocate(p_resp->p_arena, hdr_len + 1); + memcpy(buf, p_handle->read_buffer, hdr_len); + buf[hdr_len] = '\0'; + char *hdr_end = buf + hdr_len - 4; char version[16]; int status_code; @@ -328,12 +402,12 @@ Seobeo_Handle_Consume(p_handle, (uint32)hdr_len); - void *p_cl_kv = Dowa_HashMap_Get_Ptr(p_resp->headers, "Content-Length"); size_t body_len = 0; - if (p_cl_kv) + const char *content_length = Seobeo_Client_Header_Value( + p_resp->headers, "Content-Length"); + if (content_length) { - const char *content_length_str = ((Seobeo_Request_Entry*)p_cl_kv)->value; - body_len = atoi(content_length_str); + body_len = (size_t)strtoull(content_length, NULL, 10); } FILE *p_file = NULL; @@ -366,15 +440,32 @@ total_read += to_copy; Seobeo_Handle_Consume(p_handle, (uint32)to_copy); + last_progress_ms = Seobeo_Client_Monotonic_Milliseconds(); } if (total_read < body_len) { int r = Seobeo_Handle_Read(p_handle); - if (r < 0 || r == -2) - break; + if (r == -2 || r < 0) + { + if (p_file) fclose(p_file); + Seobeo_Client_Response_Destroy(p_resp); + return NULL; + } + if (r > 0) + last_progress_ms = Seobeo_Client_Monotonic_Milliseconds(); if (r == 0) + { + if (Seobeo_Client_Read_Timed_Out(timeout_ms, last_progress_ms)) + { + Seobeo_Log(SEOBEO_ERROR, "HTTP response body timed out\n"); + if (p_file) fclose(p_file); + Seobeo_Client_Response_Destroy(p_resp); + return NULL; + } + usleep(1000); continue; + } } } @@ -397,9 +488,12 @@ while (1) { - int n = Seobeo_Handle_Read(p_handle); + int n = p_handle->read_buffer_len > 0 + ? (int)p_handle->read_buffer_len + : Seobeo_Handle_Read(p_handle); if (n > 0) { + last_progress_ms = Seobeo_Client_Monotonic_Milliseconds(); if (download_path) { fwrite(p_handle->read_buffer, 1, p_handle->read_buffer_len, p_file); @@ -420,9 +514,23 @@ else if (n == -2) break; else if (n == 0) + { + if (Seobeo_Client_Read_Timed_Out(timeout_ms, last_progress_ms)) + { + Seobeo_Log(SEOBEO_ERROR, "HTTP response body timed out\n"); + if (p_file) fclose(p_file); + Seobeo_Client_Response_Destroy(p_resp); + return NULL; + } + usleep(1000); continue; + } else - break; + { + if (p_file) fclose(p_file); + Seobeo_Client_Response_Destroy(p_resp); + return NULL; + } } if (!download_path) @@ -468,7 +576,8 @@ return NULL; } - Seobeo_Client_Response *p_resp = Seobeo_Client_Parse_Response(p_handle, p_req->download_path); + Seobeo_Client_Response *p_resp = + Seobeo_Client_Parse_Response(p_handle, p_req->download_path, p_req->timeout_ms); Seobeo_Handle_Destroy(p_handle);