From dbc5bbb03d0b7892b9f969c7a3e036b68eceddbe Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 11 Feb 2026 12:23:50 +0100 Subject: [PATCH 01/15] refactor: extract abstract HTTP transport from curl and WinHTTP Introduce sentry_http_transport as a shared base for curl and WinHTTP transports. This centralizes bgworker management (start, flush, shutdown, send_envelope, dump) so each backend only provides state, start, and send_task. WinHTTP's force-close-on-timeout behavior is supported via an optional shutdown_hook parameter. Co-Authored-By: Claude Opus 4.6 --- src/CMakeLists.txt | 6 + src/transports/sentry_http_transport.c | 138 +++++++++++++++++++++ src/transports/sentry_http_transport.h | 22 ++++ src/transports/sentry_transport_curl.c | 102 +++------------- src/transports/sentry_transport_winhttp.c | 141 ++++++---------------- 5 files changed, 221 insertions(+), 188 deletions(-) create mode 100644 src/transports/sentry_http_transport.c create mode 100644 src/transports/sentry_http_transport.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3b874a887..b994e1427 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -117,6 +117,12 @@ elseif(AIX) endif() # transport +if(SENTRY_TRANSPORT_CURL OR SENTRY_TRANSPORT_WINHTTP) + sentry_target_sources_cwd(sentry + transports/sentry_http_transport.c + transports/sentry_http_transport.h + ) +endif() if(SENTRY_TRANSPORT_CURL) sentry_target_sources_cwd(sentry transports/sentry_transport_curl.c diff --git a/src/transports/sentry_http_transport.c b/src/transports/sentry_http_transport.c new file mode 100644 index 000000000..988ed04bd --- /dev/null +++ b/src/transports/sentry_http_transport.c @@ -0,0 +1,138 @@ +#include "sentry_http_transport.h" +#include "sentry_alloc.h" +#include "sentry_database.h" +#include "sentry_envelope.h" +#include "sentry_options.h" +#include "sentry_transport.h" + +#include + +typedef struct { + void *backend_state; + void (*free_backend_state)(void *); + int (*start_backend)(const sentry_options_t *, void *); + sentry_task_exec_func_t send_task; + void (*shutdown_hook)(void *backend_state); +} http_transport_state_t; + +static void +http_transport_state_free(void *_state) +{ + http_transport_state_t *state = _state; + if (state->free_backend_state) { + state->free_backend_state(state->backend_state); + } + sentry_free(state); +} + +static void +http_send_task(void *_envelope, void *_state) +{ + http_transport_state_t *state = _state; + state->send_task(_envelope, state->backend_state); +} + +static int +http_transport_start(const sentry_options_t *options, void *transport_state) +{ + sentry_bgworker_t *bgworker = transport_state; + http_transport_state_t *state = sentry__bgworker_get_state(bgworker); + + sentry__bgworker_setname(bgworker, options->transport_thread_name); + + if (state->start_backend) { + int rv = state->start_backend(options, state->backend_state); + if (rv != 0) { + return rv; + } + } + + return sentry__bgworker_start(bgworker); +} + +static int +http_transport_flush(uint64_t timeout, void *transport_state) +{ + sentry_bgworker_t *bgworker = transport_state; + return sentry__bgworker_flush(bgworker, timeout); +} + +static int +http_transport_shutdown(uint64_t timeout, void *transport_state) +{ + sentry_bgworker_t *bgworker = transport_state; + http_transport_state_t *state = sentry__bgworker_get_state(bgworker); + + int rv = sentry__bgworker_shutdown(bgworker, timeout); + if (rv != 0 && state->shutdown_hook) { + state->shutdown_hook(state->backend_state); + } + return rv; +} + +static void +http_transport_send_envelope(sentry_envelope_t *envelope, void *transport_state) +{ + sentry_bgworker_t *bgworker = transport_state; + sentry__bgworker_submit(bgworker, http_send_task, + (void (*)(void *))sentry_envelope_free, envelope); +} + +static bool +http_dump_task_cb(void *envelope, void *run) +{ + sentry__run_write_envelope( + (sentry_run_t *)run, (sentry_envelope_t *)envelope); + return true; +} + +static size_t +http_dump_queue(sentry_run_t *run, void *transport_state) +{ + sentry_bgworker_t *bgworker = transport_state; + return sentry__bgworker_foreach_matching( + bgworker, http_send_task, http_dump_task_cb, run); +} + +sentry_transport_t * +sentry__http_transport_new(void *backend_state, + void (*free_backend_state)(void *), + int (*start_backend)(const sentry_options_t *, void *), + sentry_task_exec_func_t send_task, + void (*shutdown_hook)(void *backend_state)) +{ + http_transport_state_t *state = SENTRY_MAKE(http_transport_state_t); + if (!state) { + return NULL; + } + memset(state, 0, sizeof(http_transport_state_t)); + state->backend_state = backend_state; + state->free_backend_state = free_backend_state; + state->start_backend = start_backend; + state->send_task = send_task; + state->shutdown_hook = shutdown_hook; + + sentry_bgworker_t *bgworker + = sentry__bgworker_new(state, http_transport_state_free); + if (!bgworker) { + http_transport_state_free(state); + return NULL; + } + + sentry_transport_t *transport + = sentry_transport_new(http_transport_send_envelope); + if (!transport) { + sentry__bgworker_decref(bgworker); + return NULL; + } + + sentry_transport_set_state(transport, bgworker); + sentry_transport_set_free_func( + transport, (void (*)(void *))sentry__bgworker_decref); + sentry_transport_set_startup_func(transport, http_transport_start); + sentry_transport_set_flush_func(transport, http_transport_flush); + sentry_transport_set_shutdown_func(transport, http_transport_shutdown); + sentry__transport_set_dump_func(transport, http_dump_queue); + + return transport; +} diff --git a/src/transports/sentry_http_transport.h b/src/transports/sentry_http_transport.h new file mode 100644 index 000000000..3cd647a6f --- /dev/null +++ b/src/transports/sentry_http_transport.h @@ -0,0 +1,22 @@ +#ifndef SENTRY_HTTP_TRANSPORT_H_INCLUDED +#define SENTRY_HTTP_TRANSPORT_H_INCLUDED + +#include "sentry_boot.h" +#include "sentry_sync.h" + +/** + * Creates a new HTTP transport with the given backend. + * + * The transport manages bgworker lifecycle (start, flush, shutdown, dump) + * and delegates actual HTTP sending to the backend's `send_task`. + * + * `shutdown_hook` is optional (NULL for curl). WinHTTP uses it to force-close + * handles when bgworker_shutdown times out. + */ +sentry_transport_t *sentry__http_transport_new(void *backend_state, + void (*free_backend_state)(void *), + int (*start_backend)(const sentry_options_t *, void *), + sentry_task_exec_func_t send_task, + void (*shutdown_hook)(void *backend_state)); + +#endif diff --git a/src/transports/sentry_transport_curl.c b/src/transports/sentry_transport_curl.c index 4ae3ea324..5c584c993 100644 --- a/src/transports/sentry_transport_curl.c +++ b/src/transports/sentry_transport_curl.c @@ -1,11 +1,10 @@ #include "sentry_alloc.h" #include "sentry_core.h" -#include "sentry_database.h" #include "sentry_envelope.h" +#include "sentry_http_transport.h" #include "sentry_options.h" #include "sentry_ratelimiter.h" #include "sentry_string.h" -#include "sentry_sync.h" #include "sentry_transport.h" #include "sentry_utils.h" @@ -17,7 +16,7 @@ # include "sentry_transport_curl_nx.h" #endif -typedef struct curl_transport_state_s { +typedef struct { sentry_dsn_t *dsn; CURL *curl_handle; char *user_agent; @@ -28,21 +27,21 @@ typedef struct curl_transport_state_s { #ifdef SENTRY_PLATFORM_NX void *nx_state; #endif -} curl_bgworker_state_t; +} curl_state_t; struct header_info { char *x_sentry_rate_limits; char *retry_after; }; -static curl_bgworker_state_t * -sentry__curl_bgworker_state_new(void) +static curl_state_t * +curl_state_new(void) { - curl_bgworker_state_t *state = SENTRY_MAKE(curl_bgworker_state_t); + curl_state_t *state = SENTRY_MAKE(curl_state_t); if (!state) { return NULL; } - memset(state, 0, sizeof(curl_bgworker_state_t)); + memset(state, 0, sizeof(curl_state_t)); state->ratelimiter = sentry__rate_limiter_new(); #ifdef SENTRY_PLATFORM_NX @@ -52,9 +51,9 @@ sentry__curl_bgworker_state_new(void) } static void -sentry__curl_bgworker_state_free(void *_state) +curl_state_free(void *_state) { - curl_bgworker_state_t *state = _state; + curl_state_t *state = _state; if (state->curl_handle) { curl_easy_cleanup(state->curl_handle); curl_global_cleanup(); @@ -71,9 +70,10 @@ sentry__curl_bgworker_state_free(void *_state) } static int -sentry__curl_transport_start( - const sentry_options_t *options, void *transport_state) +curl_start_backend(const sentry_options_t *options, void *_state) { + curl_state_t *state = _state; + static bool curl_initialized = false; if (!curl_initialized) { CURLcode rv = curl_global_init(CURL_GLOBAL_ALL); @@ -109,9 +109,6 @@ sentry__curl_transport_start( } } - sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; - curl_bgworker_state_t *state = sentry__bgworker_get_state(bgworker); - state->dsn = sentry__dsn_incref(options->dsn); state->proxy = sentry__string_clone(options->proxy); state->user_agent = sentry__string_clone(options->user_agent); @@ -119,11 +116,7 @@ sentry__curl_transport_start( state->curl_handle = curl_easy_init(); state->debug = options->debug; - sentry__bgworker_setname(bgworker, options->transport_thread_name); - if (!state->curl_handle) { - // In this case we don’t start the worker at all, which means we can - // still dump all unsent envelopes to disk on shutdown. SENTRY_WARN("`curl_easy_init` failed"); return 1; } @@ -134,21 +127,7 @@ sentry__curl_transport_start( } #endif - return sentry__bgworker_start(bgworker); -} - -static int -sentry__curl_transport_flush(uint64_t timeout, void *transport_state) -{ - sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; - return sentry__bgworker_flush(bgworker, timeout); -} - -static int -sentry__curl_transport_shutdown(uint64_t timeout, void *transport_state) -{ - sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; - return sentry__bgworker_shutdown(bgworker, timeout); + return 0; } static size_t @@ -184,10 +163,10 @@ header_callback(char *buffer, size_t size, size_t nitems, void *userdata) } static void -sentry__curl_send_task(void *_envelope, void *_state) +curl_send_task(void *_envelope, void *_state) { sentry_envelope_t *envelope = (sentry_envelope_t *)_envelope; - curl_bgworker_state_t *state = (curl_bgworker_state_t *)_state; + curl_state_t *state = (curl_state_t *)_state; #ifdef SENTRY_PLATFORM_NX if (!sentry_nx_curl_connect(state->nx_state)) { @@ -290,60 +269,15 @@ sentry__curl_send_task(void *_envelope, void *_state) sentry__prepared_http_request_free(req); } -static void -sentry__curl_transport_send_envelope( - sentry_envelope_t *envelope, void *transport_state) -{ - sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; - sentry__bgworker_submit(bgworker, sentry__curl_send_task, - (void (*)(void *))sentry_envelope_free, envelope); -} - -static bool -sentry__curl_dump_task(void *envelope, void *run) -{ - sentry__run_write_envelope( - (sentry_run_t *)run, (sentry_envelope_t *)envelope); - return true; -} - -size_t -sentry__curl_dump_queue(sentry_run_t *run, void *transport_state) -{ - sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; - return sentry__bgworker_foreach_matching( - bgworker, sentry__curl_send_task, sentry__curl_dump_task, run); -} - sentry_transport_t * sentry__transport_new_default(void) { SENTRY_INFO("initializing curl transport"); - curl_bgworker_state_t *state = sentry__curl_bgworker_state_new(); + curl_state_t *state = curl_state_new(); if (!state) { return NULL; } - sentry_bgworker_t *bgworker - = sentry__bgworker_new(state, sentry__curl_bgworker_state_free); - if (!bgworker) { - return NULL; - } - - sentry_transport_t *transport - = sentry_transport_new(sentry__curl_transport_send_envelope); - if (!transport) { - sentry__bgworker_decref(bgworker); - return NULL; - } - sentry_transport_set_state(transport, bgworker); - sentry_transport_set_free_func( - transport, (void (*)(void *))sentry__bgworker_decref); - sentry_transport_set_startup_func(transport, sentry__curl_transport_start); - sentry_transport_set_flush_func(transport, sentry__curl_transport_flush); - sentry_transport_set_shutdown_func( - transport, sentry__curl_transport_shutdown); - sentry__transport_set_dump_func(transport, sentry__curl_dump_queue); - - return transport; + return sentry__http_transport_new( + state, curl_state_free, curl_start_backend, curl_send_task, NULL); } diff --git a/src/transports/sentry_transport_winhttp.c b/src/transports/sentry_transport_winhttp.c index e60a6cc96..1cd222013 100644 --- a/src/transports/sentry_transport_winhttp.c +++ b/src/transports/sentry_transport_winhttp.c @@ -1,11 +1,10 @@ #include "sentry_alloc.h" #include "sentry_core.h" -#include "sentry_database.h" #include "sentry_envelope.h" +#include "sentry_http_transport.h" #include "sentry_options.h" #include "sentry_ratelimiter.h" #include "sentry_string.h" -#include "sentry_sync.h" #include "sentry_transport.h" #include "sentry_utils.h" @@ -28,16 +27,16 @@ typedef struct { HINTERNET connect; HINTERNET request; bool debug; -} winhttp_bgworker_state_t; +} winhttp_state_t; -static winhttp_bgworker_state_t * -sentry__winhttp_bgworker_state_new(void) +static winhttp_state_t * +winhttp_state_new(void) { - winhttp_bgworker_state_t *state = SENTRY_MAKE(winhttp_bgworker_state_t); + winhttp_state_t *state = SENTRY_MAKE(winhttp_state_t); if (!state) { return NULL; } - memset(state, 0, sizeof(winhttp_bgworker_state_t)); + memset(state, 0, sizeof(winhttp_state_t)); state->ratelimiter = sentry__rate_limiter_new(); @@ -45,9 +44,9 @@ sentry__winhttp_bgworker_state_new(void) } static void -sentry__winhttp_bgworker_state_free(void *_state) +winhttp_state_free(void *_state) { - winhttp_bgworker_state_t *state = _state; + winhttp_state_t *state = _state; if (state->connect) { WinHttpCloseHandle(state->connect); } @@ -63,14 +62,12 @@ sentry__winhttp_bgworker_state_free(void *_state) sentry_free(state); } -// Function to extract and set credentials static void -set_proxy_credentials(winhttp_bgworker_state_t *state, const char *proxy) +set_proxy_credentials(winhttp_state_t *state, const char *proxy) { sentry_url_t url; sentry__url_parse(&url, proxy, false); if (url.username && url.password) { - // Convert user and pass to LPCWSTR int user_wlen = MultiByteToWideChar(CP_UTF8, 0, url.username, -1, NULL, 0); int pass_wlen @@ -89,18 +86,14 @@ set_proxy_credentials(winhttp_bgworker_state_t *state, const char *proxy) } static int -sentry__winhttp_transport_start( - const sentry_options_t *opts, void *transport_state) +winhttp_start_backend(const sentry_options_t *opts, void *_state) { - sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; - winhttp_bgworker_state_t *state = sentry__bgworker_get_state(bgworker); + winhttp_state_t *state = _state; state->dsn = sentry__dsn_incref(opts->dsn); state->user_agent = sentry__string_to_wstr(opts->user_agent); state->debug = opts->debug; - sentry__bgworker_setname(bgworker, opts->transport_thread_name); - const char *env_proxy = opts->dsn ? getenv(opts->dsn->is_secure ? "https_proxy" : "http_proxy") : NULL; @@ -147,51 +140,37 @@ sentry__winhttp_transport_start( return 1; } - return sentry__bgworker_start(bgworker); -} - -static int -sentry__winhttp_transport_flush(uint64_t timeout, void *transport_state) -{ - sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; - return sentry__bgworker_flush(bgworker, timeout); + return 0; } -static int -sentry__winhttp_transport_shutdown(uint64_t timeout, void *transport_state) +static void +winhttp_shutdown_hook(void *_state) { - sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; - winhttp_bgworker_state_t *state = sentry__bgworker_get_state(bgworker); - - int rv = sentry__bgworker_shutdown(bgworker, timeout); - if (rv != 0) { - // Seems like some requests are taking too long/hanging - // Just close them to make sure the background thread is exiting. - if (state->connect) { - WinHttpCloseHandle(state->connect); - state->connect = NULL; - } - - // NOTE: We need to close the session before closing the request. - // This will cancel all other requests which might be queued as well. - if (state->session) { - WinHttpCloseHandle(state->session); - state->session = NULL; - } - if (state->request) { - WinHttpCloseHandle(state->request); - state->request = NULL; - } + winhttp_state_t *state = _state; + // Seems like some requests are taking too long/hanging + // Just close them to make sure the background thread is exiting. + if (state->connect) { + WinHttpCloseHandle(state->connect); + state->connect = NULL; } - return rv; + // NOTE: We need to close the session before closing the request. + // This will cancel all other requests which might be queued as well. + if (state->session) { + WinHttpCloseHandle(state->session); + state->session = NULL; + } + if (state->request) { + WinHttpCloseHandle(state->request); + state->request = NULL; + } } static void -sentry__winhttp_send_task(void *_envelope, void *_state) +winhttp_send_task(void *_envelope, void *_state) { sentry_envelope_t *envelope = (sentry_envelope_t *)_envelope; - winhttp_bgworker_state_t *state = (winhttp_bgworker_state_t *)_state; + winhttp_state_t *state = (winhttp_state_t *)_state; uint64_t started = sentry__monotonic_time(); @@ -265,7 +244,7 @@ sentry__winhttp_send_task(void *_envelope, void *_state) sentry_free(headers_buf); if (!headers) { - SENTRY_WARN("sentry__winhttp_send_task: failed to allocate headers"); + SENTRY_WARN("winhttp_send_task: failed to allocate headers"); goto exit; } @@ -305,7 +284,7 @@ sentry__winhttp_send_task(void *_envelope, void *_state) } } - // lets just assume we won’t have headers > 2k + // lets just assume we won't have headers > 2k wchar_t buf[2048]; DWORD buf_size = sizeof(buf); @@ -356,61 +335,15 @@ sentry__winhttp_send_task(void *_envelope, void *_state) sentry__prepared_http_request_free(req); } -static void -sentry__winhttp_transport_send_envelope( - sentry_envelope_t *envelope, void *transport_state) -{ - sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; - sentry__bgworker_submit(bgworker, sentry__winhttp_send_task, - (void (*)(void *))sentry_envelope_free, envelope); -} - -static bool -sentry__winhttp_dump_task(void *envelope, void *run) -{ - sentry__run_write_envelope( - (sentry_run_t *)run, (sentry_envelope_t *)envelope); - return true; -} - -static size_t -sentry__winhttp_dump_queue(sentry_run_t *run, void *transport_state) -{ - sentry_bgworker_t *bgworker = (sentry_bgworker_t *)transport_state; - return sentry__bgworker_foreach_matching( - bgworker, sentry__winhttp_send_task, sentry__winhttp_dump_task, run); -} - sentry_transport_t * sentry__transport_new_default(void) { SENTRY_INFO("initializing winhttp transport"); - winhttp_bgworker_state_t *state = sentry__winhttp_bgworker_state_new(); + winhttp_state_t *state = winhttp_state_new(); if (!state) { return NULL; } - sentry_bgworker_t *bgworker - = sentry__bgworker_new(state, sentry__winhttp_bgworker_state_free); - if (!bgworker) { - return NULL; - } - - sentry_transport_t *transport - = sentry_transport_new(sentry__winhttp_transport_send_envelope); - if (!transport) { - sentry__bgworker_decref(bgworker); - return NULL; - } - sentry_transport_set_state(transport, bgworker); - sentry_transport_set_free_func( - transport, (void (*)(void *))sentry__bgworker_decref); - sentry_transport_set_startup_func( - transport, sentry__winhttp_transport_start); - sentry_transport_set_flush_func(transport, sentry__winhttp_transport_flush); - sentry_transport_set_shutdown_func( - transport, sentry__winhttp_transport_shutdown); - sentry__transport_set_dump_func(transport, sentry__winhttp_dump_queue); - - return transport; + return sentry__http_transport_new(state, winhttp_state_free, + winhttp_start_backend, winhttp_send_task, winhttp_shutdown_hook); } From bde5c0fbb3e59af57d9f436ee39f5a5c3c0cd99e Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 11 Feb 2026 12:45:26 +0100 Subject: [PATCH 02/15] refactor: lift dsn, user_agent, ratelimiter into abstract HTTP transport Move fields duplicated across curl and WinHTTP backends into the shared http_transport_state_t. The shared http_send_task() now calls sentry__prepare_http_request() and passes the prepared request to backend send functions via a new sentry_http_send_func_t callback. Co-Authored-By: Claude Opus 4.6 --- src/transports/sentry_http_transport.c | 28 +++++++++++-- src/transports/sentry_http_transport.h | 9 ++++- src/transports/sentry_transport_curl.c | 29 +++----------- src/transports/sentry_transport_winhttp.c | 48 +++++++---------------- 4 files changed, 52 insertions(+), 62 deletions(-) diff --git a/src/transports/sentry_http_transport.c b/src/transports/sentry_http_transport.c index 988ed04bd..2242a9869 100644 --- a/src/transports/sentry_http_transport.c +++ b/src/transports/sentry_http_transport.c @@ -3,15 +3,19 @@ #include "sentry_database.h" #include "sentry_envelope.h" #include "sentry_options.h" +#include "sentry_string.h" #include "sentry_transport.h" #include typedef struct { + sentry_dsn_t *dsn; + char *user_agent; + sentry_rate_limiter_t *ratelimiter; void *backend_state; void (*free_backend_state)(void *); int (*start_backend)(const sentry_options_t *, void *); - sentry_task_exec_func_t send_task; + sentry_http_send_func_t send_func; void (*shutdown_hook)(void *backend_state); } http_transport_state_t; @@ -22,14 +26,26 @@ http_transport_state_free(void *_state) if (state->free_backend_state) { state->free_backend_state(state->backend_state); } + sentry__dsn_decref(state->dsn); + sentry_free(state->user_agent); + sentry__rate_limiter_free(state->ratelimiter); sentry_free(state); } static void http_send_task(void *_envelope, void *_state) { + sentry_envelope_t *envelope = _envelope; http_transport_state_t *state = _state; - state->send_task(_envelope, state->backend_state); + + sentry_prepared_http_request_t *req = sentry__prepare_http_request( + envelope, state->dsn, state->ratelimiter, state->user_agent); + if (!req) { + return; + } + + state->send_func(req, state->ratelimiter, state->backend_state); + sentry__prepared_http_request_free(req); } static int @@ -40,6 +56,9 @@ http_transport_start(const sentry_options_t *options, void *transport_state) sentry__bgworker_setname(bgworker, options->transport_thread_name); + state->dsn = sentry__dsn_incref(options->dsn); + state->user_agent = sentry__string_clone(options->user_agent); + if (state->start_backend) { int rv = state->start_backend(options, state->backend_state); if (rv != 0) { @@ -98,7 +117,7 @@ sentry_transport_t * sentry__http_transport_new(void *backend_state, void (*free_backend_state)(void *), int (*start_backend)(const sentry_options_t *, void *), - sentry_task_exec_func_t send_task, + sentry_http_send_func_t send_func, void (*shutdown_hook)(void *backend_state)) { http_transport_state_t *state = SENTRY_MAKE(http_transport_state_t); @@ -106,10 +125,11 @@ sentry__http_transport_new(void *backend_state, return NULL; } memset(state, 0, sizeof(http_transport_state_t)); + state->ratelimiter = sentry__rate_limiter_new(); state->backend_state = backend_state; state->free_backend_state = free_backend_state; state->start_backend = start_backend; - state->send_task = send_task; + state->send_func = send_func; state->shutdown_hook = shutdown_hook; sentry_bgworker_t *bgworker diff --git a/src/transports/sentry_http_transport.h b/src/transports/sentry_http_transport.h index 3cd647a6f..8d831333c 100644 --- a/src/transports/sentry_http_transport.h +++ b/src/transports/sentry_http_transport.h @@ -2,13 +2,18 @@ #define SENTRY_HTTP_TRANSPORT_H_INCLUDED #include "sentry_boot.h" +#include "sentry_ratelimiter.h" #include "sentry_sync.h" +#include "sentry_transport.h" + +typedef void (*sentry_http_send_func_t)(sentry_prepared_http_request_t *req, + sentry_rate_limiter_t *rl, void *backend_state); /** * Creates a new HTTP transport with the given backend. * * The transport manages bgworker lifecycle (start, flush, shutdown, dump) - * and delegates actual HTTP sending to the backend's `send_task`. + * and delegates actual HTTP sending to the backend's `send_func`. * * `shutdown_hook` is optional (NULL for curl). WinHTTP uses it to force-close * handles when bgworker_shutdown times out. @@ -16,7 +21,7 @@ sentry_transport_t *sentry__http_transport_new(void *backend_state, void (*free_backend_state)(void *), int (*start_backend)(const sentry_options_t *, void *), - sentry_task_exec_func_t send_task, + sentry_http_send_func_t send_func, void (*shutdown_hook)(void *backend_state)); #endif diff --git a/src/transports/sentry_transport_curl.c b/src/transports/sentry_transport_curl.c index 5c584c993..0f1268bf5 100644 --- a/src/transports/sentry_transport_curl.c +++ b/src/transports/sentry_transport_curl.c @@ -17,12 +17,9 @@ #endif typedef struct { - sentry_dsn_t *dsn; CURL *curl_handle; - char *user_agent; char *proxy; char *ca_certs; - sentry_rate_limiter_t *ratelimiter; bool debug; #ifdef SENTRY_PLATFORM_NX void *nx_state; @@ -43,7 +40,6 @@ curl_state_new(void) } memset(state, 0, sizeof(curl_state_t)); - state->ratelimiter = sentry__rate_limiter_new(); #ifdef SENTRY_PLATFORM_NX state->nx_state = sentry_nx_curl_state_new(); #endif @@ -58,10 +54,7 @@ curl_state_free(void *_state) curl_easy_cleanup(state->curl_handle); curl_global_cleanup(); } - sentry__dsn_decref(state->dsn); - sentry__rate_limiter_free(state->ratelimiter); sentry_free(state->ca_certs); - sentry_free(state->user_agent); sentry_free(state->proxy); #ifdef SENTRY_PLATFORM_NX sentry_nx_curl_state_free(state->nx_state); @@ -109,9 +102,7 @@ curl_start_backend(const sentry_options_t *options, void *_state) } } - state->dsn = sentry__dsn_incref(options->dsn); state->proxy = sentry__string_clone(options->proxy); - state->user_agent = sentry__string_clone(options->user_agent); state->ca_certs = sentry__string_clone(options->ca_certs); state->curl_handle = curl_easy_init(); state->debug = options->debug; @@ -163,22 +154,16 @@ header_callback(char *buffer, size_t size, size_t nitems, void *userdata) } static void -curl_send_task(void *_envelope, void *_state) +curl_send_task(sentry_prepared_http_request_t *req, sentry_rate_limiter_t *rl, + void *_state) { - sentry_envelope_t *envelope = (sentry_envelope_t *)_envelope; curl_state_t *state = (curl_state_t *)_state; #ifdef SENTRY_PLATFORM_NX if (!sentry_nx_curl_connect(state->nx_state)) { - return; // TODO should we dump the envelope to disk? - } -#endif - - sentry_prepared_http_request_t *req = sentry__prepare_http_request( - envelope, state->dsn, state->ratelimiter, state->user_agent); - if (!req) { return; } +#endif struct curl_slist *headers = NULL; headers = curl_slist_append(headers, "expect:"); @@ -198,7 +183,6 @@ curl_send_task(void *_envelope, void *_state) if (state->debug) { curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); curl_easy_setopt(curl, CURLOPT_WRITEDATA, stderr); - // CURLOPT_WRITEFUNCTION will `fwrite` by default } else { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, swallow_data); } @@ -242,12 +226,12 @@ curl_send_task(void *_envelope, void *_state) if (info.x_sentry_rate_limits) { sentry__rate_limiter_update_from_header( - state->ratelimiter, info.x_sentry_rate_limits); + rl, info.x_sentry_rate_limits); } else if (info.retry_after) { sentry__rate_limiter_update_from_http_retry_after( - state->ratelimiter, info.retry_after); + rl, info.retry_after); } else if (response_code == 429) { - sentry__rate_limiter_update_from_429(state->ratelimiter); + sentry__rate_limiter_update_from_429(rl); } } else { size_t len = strlen(error_buf); @@ -266,7 +250,6 @@ curl_send_task(void *_envelope, void *_state) curl_slist_free_all(headers); sentry_free(info.retry_after); sentry_free(info.x_sentry_rate_limits); - sentry__prepared_http_request_free(req); } sentry_transport_t * diff --git a/src/transports/sentry_transport_winhttp.c b/src/transports/sentry_transport_winhttp.c index 1cd222013..895c6c623 100644 --- a/src/transports/sentry_transport_winhttp.c +++ b/src/transports/sentry_transport_winhttp.c @@ -17,12 +17,9 @@ #include typedef struct { - sentry_dsn_t *dsn; - wchar_t *user_agent; wchar_t *proxy; wchar_t *proxy_username; wchar_t *proxy_password; - sentry_rate_limiter_t *ratelimiter; HINTERNET session; HINTERNET connect; HINTERNET request; @@ -38,8 +35,6 @@ winhttp_state_new(void) } memset(state, 0, sizeof(winhttp_state_t)); - state->ratelimiter = sentry__rate_limiter_new(); - return state; } @@ -53,9 +48,6 @@ winhttp_state_free(void *_state) if (state->session) { WinHttpCloseHandle(state->session); } - sentry__dsn_decref(state->dsn); - sentry__rate_limiter_free(state->ratelimiter); - sentry_free(state->user_agent); sentry_free(state->proxy_username); sentry_free(state->proxy_password); sentry_free(state->proxy); @@ -90,8 +82,7 @@ winhttp_start_backend(const sentry_options_t *opts, void *_state) { winhttp_state_t *state = _state; - state->dsn = sentry__dsn_incref(opts->dsn); - state->user_agent = sentry__string_to_wstr(opts->user_agent); + wchar_t *user_agent = sentry__string_to_wstr(opts->user_agent); state->debug = opts->debug; const char *env_proxy = opts->dsn @@ -119,22 +110,24 @@ winhttp_start_backend(const sentry_options_t *opts, void *_state) if (state->proxy) { state->session - = WinHttpOpen(state->user_agent, WINHTTP_ACCESS_TYPE_NAMED_PROXY, + = WinHttpOpen(user_agent, WINHTTP_ACCESS_TYPE_NAMED_PROXY, state->proxy, WINHTTP_NO_PROXY_BYPASS, 0); } else { #if _WIN32_WINNT >= 0x0603 - state->session = WinHttpOpen(state->user_agent, - WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, - WINHTTP_NO_PROXY_BYPASS, 0); + state->session + = WinHttpOpen(user_agent, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, + WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); #endif // On windows 8.0 or lower, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY does // not work on error we fallback to WINHTTP_ACCESS_TYPE_DEFAULT_PROXY if (!state->session) { - state->session = WinHttpOpen(state->user_agent, - WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, - WINHTTP_NO_PROXY_BYPASS, 0); + state->session + = WinHttpOpen(user_agent, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); } } + sentry_free(user_agent); + if (!state->session) { SENTRY_WARN("`WinHttpOpen` failed"); return 1; @@ -167,21 +160,13 @@ winhttp_shutdown_hook(void *_state) } static void -winhttp_send_task(void *_envelope, void *_state) +winhttp_send_task(sentry_prepared_http_request_t *req, + sentry_rate_limiter_t *rl, void *_state) { - sentry_envelope_t *envelope = (sentry_envelope_t *)_envelope; winhttp_state_t *state = (winhttp_state_t *)_state; uint64_t started = sentry__monotonic_time(); - char *user_agent = sentry__string_from_wstr(state->user_agent); - sentry_prepared_http_request_t *req = sentry__prepare_http_request( - envelope, state->dsn, state->ratelimiter, user_agent); - if (!req) { - sentry_free(user_agent); - return; - } - wchar_t *url = sentry__string_to_wstr(req->url); wchar_t *headers = NULL; @@ -296,7 +281,7 @@ winhttp_send_task(void *_envelope, void *_state) WINHTTP_NO_HEADER_INDEX)) { char *h = sentry__string_from_wstr(buf); if (h) { - sentry__rate_limiter_update_from_header(state->ratelimiter, h); + sentry__rate_limiter_update_from_header(rl, h); sentry_free(h); } } else if (WinHttpQueryHeaders(state->request, WINHTTP_QUERY_CUSTOM, @@ -304,8 +289,7 @@ winhttp_send_task(void *_envelope, void *_state) WINHTTP_NO_HEADER_INDEX)) { char *h = sentry__string_from_wstr(buf); if (h) { - sentry__rate_limiter_update_from_http_retry_after( - state->ratelimiter, h); + sentry__rate_limiter_update_from_http_retry_after(rl, h); sentry_free(h); } } else if (WinHttpQueryHeaders(state->request, @@ -313,7 +297,7 @@ winhttp_send_task(void *_envelope, void *_state) WINHTTP_HEADER_NAME_BY_INDEX, &status_code, &status_code_size, WINHTTP_NO_HEADER_INDEX) && status_code == 429) { - sentry__rate_limiter_update_from_429(state->ratelimiter); + sentry__rate_limiter_update_from_429(rl); } } else { SENTRY_WARNF( @@ -329,10 +313,8 @@ winhttp_send_task(void *_envelope, void *_state) state->request = NULL; WinHttpCloseHandle(request); } - sentry_free(user_agent); sentry_free(url); sentry_free(headers); - sentry__prepared_http_request_free(req); } sentry_transport_t * From d10c067096749e59ec6128165fb93e13143a4b1b Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 11 Feb 2026 15:36:10 +0100 Subject: [PATCH 03/15] refactor: rename HTTP transport "backend" to "client" Avoid ambiguity with sentry_backend_t by renaming backend_state, free_backend_state, start_backend, and platform-specific types/functions (curl_state_t, winhttp_state_t, etc.) to use "client" terminology. Co-Authored-By: Claude Opus 4.6 --- src/transports/sentry_http_transport.c | 34 +++--- src/transports/sentry_http_transport.h | 15 ++- src/transports/sentry_transport_curl.c | 74 ++++++------ src/transports/sentry_transport_winhttp.c | 136 +++++++++++----------- 4 files changed, 128 insertions(+), 131 deletions(-) diff --git a/src/transports/sentry_http_transport.c b/src/transports/sentry_http_transport.c index 2242a9869..cc072ef67 100644 --- a/src/transports/sentry_http_transport.c +++ b/src/transports/sentry_http_transport.c @@ -12,19 +12,19 @@ typedef struct { sentry_dsn_t *dsn; char *user_agent; sentry_rate_limiter_t *ratelimiter; - void *backend_state; - void (*free_backend_state)(void *); - int (*start_backend)(const sentry_options_t *, void *); + void *client; + void (*free_client)(void *); + int (*start_client)(const sentry_options_t *, void *); sentry_http_send_func_t send_func; - void (*shutdown_hook)(void *backend_state); + void (*shutdown_hook)(void *client); } http_transport_state_t; static void http_transport_state_free(void *_state) { http_transport_state_t *state = _state; - if (state->free_backend_state) { - state->free_backend_state(state->backend_state); + if (state->free_client) { + state->free_client(state->client); } sentry__dsn_decref(state->dsn); sentry_free(state->user_agent); @@ -44,7 +44,7 @@ http_send_task(void *_envelope, void *_state) return; } - state->send_func(req, state->ratelimiter, state->backend_state); + state->send_func(req, state->ratelimiter, state->client); sentry__prepared_http_request_free(req); } @@ -59,8 +59,8 @@ http_transport_start(const sentry_options_t *options, void *transport_state) state->dsn = sentry__dsn_incref(options->dsn); state->user_agent = sentry__string_clone(options->user_agent); - if (state->start_backend) { - int rv = state->start_backend(options, state->backend_state); + if (state->start_client) { + int rv = state->start_client(options, state->client); if (rv != 0) { return rv; } @@ -84,7 +84,7 @@ http_transport_shutdown(uint64_t timeout, void *transport_state) int rv = sentry__bgworker_shutdown(bgworker, timeout); if (rv != 0 && state->shutdown_hook) { - state->shutdown_hook(state->backend_state); + state->shutdown_hook(state->client); } return rv; } @@ -114,11 +114,9 @@ http_dump_queue(sentry_run_t *run, void *transport_state) } sentry_transport_t * -sentry__http_transport_new(void *backend_state, - void (*free_backend_state)(void *), - int (*start_backend)(const sentry_options_t *, void *), - sentry_http_send_func_t send_func, - void (*shutdown_hook)(void *backend_state)) +sentry__http_transport_new(void *client, void (*free_client)(void *), + int (*start_client)(const sentry_options_t *, void *), + sentry_http_send_func_t send_func, void (*shutdown_hook)(void *client)) { http_transport_state_t *state = SENTRY_MAKE(http_transport_state_t); if (!state) { @@ -126,9 +124,9 @@ sentry__http_transport_new(void *backend_state, } memset(state, 0, sizeof(http_transport_state_t)); state->ratelimiter = sentry__rate_limiter_new(); - state->backend_state = backend_state; - state->free_backend_state = free_backend_state; - state->start_backend = start_backend; + state->client = client; + state->free_client = free_client; + state->start_client = start_client; state->send_func = send_func; state->shutdown_hook = shutdown_hook; diff --git a/src/transports/sentry_http_transport.h b/src/transports/sentry_http_transport.h index 8d831333c..f2f033e10 100644 --- a/src/transports/sentry_http_transport.h +++ b/src/transports/sentry_http_transport.h @@ -7,21 +7,20 @@ #include "sentry_transport.h" typedef void (*sentry_http_send_func_t)(sentry_prepared_http_request_t *req, - sentry_rate_limiter_t *rl, void *backend_state); + sentry_rate_limiter_t *rl, void *client); /** - * Creates a new HTTP transport with the given backend. + * Creates a new HTTP transport with the given client. * * The transport manages bgworker lifecycle (start, flush, shutdown, dump) - * and delegates actual HTTP sending to the backend's `send_func`. + * and delegates actual HTTP sending to the client's `send_func`. * * `shutdown_hook` is optional (NULL for curl). WinHTTP uses it to force-close * handles when bgworker_shutdown times out. */ -sentry_transport_t *sentry__http_transport_new(void *backend_state, - void (*free_backend_state)(void *), - int (*start_backend)(const sentry_options_t *, void *), - sentry_http_send_func_t send_func, - void (*shutdown_hook)(void *backend_state)); +sentry_transport_t *sentry__http_transport_new(void *client, + void (*free_client)(void *), + int (*start_client)(const sentry_options_t *, void *), + sentry_http_send_func_t send_func, void (*shutdown_hook)(void *client)); #endif diff --git a/src/transports/sentry_transport_curl.c b/src/transports/sentry_transport_curl.c index 0f1268bf5..dcf1a8a0c 100644 --- a/src/transports/sentry_transport_curl.c +++ b/src/transports/sentry_transport_curl.c @@ -24,48 +24,48 @@ typedef struct { #ifdef SENTRY_PLATFORM_NX void *nx_state; #endif -} curl_state_t; +} curl_client_t; struct header_info { char *x_sentry_rate_limits; char *retry_after; }; -static curl_state_t * -curl_state_new(void) +static curl_client_t * +curl_client_new(void) { - curl_state_t *state = SENTRY_MAKE(curl_state_t); - if (!state) { + curl_client_t *client = SENTRY_MAKE(curl_client_t); + if (!client) { return NULL; } - memset(state, 0, sizeof(curl_state_t)); + memset(client, 0, sizeof(curl_client_t)); #ifdef SENTRY_PLATFORM_NX - state->nx_state = sentry_nx_curl_state_new(); + client->nx_state = sentry_nx_curl_state_new(); #endif - return state; + return client; } static void -curl_state_free(void *_state) +curl_client_free(void *_client) { - curl_state_t *state = _state; - if (state->curl_handle) { - curl_easy_cleanup(state->curl_handle); + curl_client_t *client = _client; + if (client->curl_handle) { + curl_easy_cleanup(client->curl_handle); curl_global_cleanup(); } - sentry_free(state->ca_certs); - sentry_free(state->proxy); + sentry_free(client->ca_certs); + sentry_free(client->proxy); #ifdef SENTRY_PLATFORM_NX - sentry_nx_curl_state_free(state->nx_state); + sentry_nx_curl_state_free(client->nx_state); #endif - sentry_free(state); + sentry_free(client); } static int -curl_start_backend(const sentry_options_t *options, void *_state) +curl_start_client(const sentry_options_t *options, void *_client) { - curl_state_t *state = _state; + curl_client_t *client = _client; static bool curl_initialized = false; if (!curl_initialized) { @@ -102,18 +102,18 @@ curl_start_backend(const sentry_options_t *options, void *_state) } } - state->proxy = sentry__string_clone(options->proxy); - state->ca_certs = sentry__string_clone(options->ca_certs); - state->curl_handle = curl_easy_init(); - state->debug = options->debug; + client->proxy = sentry__string_clone(options->proxy); + client->ca_certs = sentry__string_clone(options->ca_certs); + client->curl_handle = curl_easy_init(); + client->debug = options->debug; - if (!state->curl_handle) { + if (!client->curl_handle) { SENTRY_WARN("`curl_easy_init` failed"); return 1; } #ifdef SENTRY_PLATFORM_NX - if (!sentry_nx_transport_start(state->nx_state, options)) { + if (!sentry_nx_transport_start(client->nx_state, options)) { return 1; } #endif @@ -155,12 +155,12 @@ header_callback(char *buffer, size_t size, size_t nitems, void *userdata) static void curl_send_task(sentry_prepared_http_request_t *req, sentry_rate_limiter_t *rl, - void *_state) + void *_client) { - curl_state_t *state = (curl_state_t *)_state; + curl_client_t *client = (curl_client_t *)_client; #ifdef SENTRY_PLATFORM_NX - if (!sentry_nx_curl_connect(state->nx_state)) { + if (!sentry_nx_curl_connect(client->nx_state)) { return; } #endif @@ -178,9 +178,9 @@ curl_send_task(sentry_prepared_http_request_t *req, sentry_rate_limiter_t *rl, headers = curl_slist_append(headers, buf); } - CURL *curl = state->curl_handle; + CURL *curl = client->curl_handle; curl_easy_reset(curl); - if (state->debug) { + if (client->debug) { curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); curl_easy_setopt(curl, CURLOPT_WRITEDATA, stderr); } else { @@ -203,15 +203,15 @@ curl_send_task(sentry_prepared_http_request_t *req, sentry_rate_limiter_t *rl, curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)&info); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); - if (state->proxy) { - curl_easy_setopt(curl, CURLOPT_PROXY, state->proxy); + if (client->proxy) { + curl_easy_setopt(curl, CURLOPT_PROXY, client->proxy); } - if (state->ca_certs) { - curl_easy_setopt(curl, CURLOPT_CAINFO, state->ca_certs); + if (client->ca_certs) { + curl_easy_setopt(curl, CURLOPT_CAINFO, client->ca_certs); } #ifdef SENTRY_PLATFORM_NX - CURLcode rv = sentry_nx_curl_easy_setopt(state->nx_state, curl, req); + CURLcode rv = sentry_nx_curl_easy_setopt(client->nx_state, curl, req); #else CURLcode rv = CURLE_OK; #endif @@ -256,11 +256,11 @@ sentry_transport_t * sentry__transport_new_default(void) { SENTRY_INFO("initializing curl transport"); - curl_state_t *state = curl_state_new(); - if (!state) { + curl_client_t *client = curl_client_new(); + if (!client) { return NULL; } return sentry__http_transport_new( - state, curl_state_free, curl_start_backend, curl_send_task, NULL); + client, curl_client_free, curl_start_client, curl_send_task, NULL); } diff --git a/src/transports/sentry_transport_winhttp.c b/src/transports/sentry_transport_winhttp.c index 895c6c623..0edda3d93 100644 --- a/src/transports/sentry_transport_winhttp.c +++ b/src/transports/sentry_transport_winhttp.c @@ -24,38 +24,38 @@ typedef struct { HINTERNET connect; HINTERNET request; bool debug; -} winhttp_state_t; +} winhttp_client_t; -static winhttp_state_t * -winhttp_state_new(void) +static winhttp_client_t * +winhttp_client_new(void) { - winhttp_state_t *state = SENTRY_MAKE(winhttp_state_t); - if (!state) { + winhttp_client_t *client = SENTRY_MAKE(winhttp_client_t); + if (!client) { return NULL; } - memset(state, 0, sizeof(winhttp_state_t)); + memset(client, 0, sizeof(winhttp_client_t)); - return state; + return client; } static void -winhttp_state_free(void *_state) +winhttp_client_free(void *_client) { - winhttp_state_t *state = _state; - if (state->connect) { - WinHttpCloseHandle(state->connect); + winhttp_client_t *client = _client; + if (client->connect) { + WinHttpCloseHandle(client->connect); } - if (state->session) { - WinHttpCloseHandle(state->session); + if (client->session) { + WinHttpCloseHandle(client->session); } - sentry_free(state->proxy_username); - sentry_free(state->proxy_password); - sentry_free(state->proxy); - sentry_free(state); + sentry_free(client->proxy_username); + sentry_free(client->proxy_password); + sentry_free(client->proxy); + sentry_free(client); } static void -set_proxy_credentials(winhttp_state_t *state, const char *proxy) +set_proxy_credentials(winhttp_client_t *state, const char *proxy) { sentry_url_t url; sentry__url_parse(&url, proxy, false); @@ -78,12 +78,12 @@ set_proxy_credentials(winhttp_state_t *state, const char *proxy) } static int -winhttp_start_backend(const sentry_options_t *opts, void *_state) +winhttp_start_client(const sentry_options_t *opts, void *_client) { - winhttp_state_t *state = _state; + winhttp_client_t *client = _client; wchar_t *user_agent = sentry__string_to_wstr(opts->user_agent); - state->debug = opts->debug; + client->debug = opts->debug; const char *env_proxy = opts->dsn ? getenv(opts->dsn->is_secure ? "https_proxy" : "http_proxy") @@ -97,38 +97,38 @@ winhttp_start_backend(const sentry_options_t *opts, void *_state) const char *slash = strchr(ptr, '/'); if (at_sign && (!slash || at_sign < slash)) { ptr = at_sign + 1; - set_proxy_credentials(state, proxy); + set_proxy_credentials(client, proxy); } if (slash) { char *copy = sentry__string_clone_n(ptr, (size_t)(slash - ptr)); - state->proxy = sentry__string_to_wstr(copy); + client->proxy = sentry__string_to_wstr(copy); sentry_free(copy); } else { - state->proxy = sentry__string_to_wstr(ptr); + client->proxy = sentry__string_to_wstr(ptr); } } - if (state->proxy) { - state->session + if (client->proxy) { + client->session = WinHttpOpen(user_agent, WINHTTP_ACCESS_TYPE_NAMED_PROXY, - state->proxy, WINHTTP_NO_PROXY_BYPASS, 0); + client->proxy, WINHTTP_NO_PROXY_BYPASS, 0); } else { #if _WIN32_WINNT >= 0x0603 - state->session + client->session = WinHttpOpen(user_agent, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); #endif // On windows 8.0 or lower, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY does // not work on error we fallback to WINHTTP_ACCESS_TYPE_DEFAULT_PROXY - if (!state->session) { - state->session + if (!client->session) { + client->session = WinHttpOpen(user_agent, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); } } sentry_free(user_agent); - if (!state->session) { + if (!client->session) { SENTRY_WARN("`WinHttpOpen` failed"); return 1; } @@ -137,33 +137,33 @@ winhttp_start_backend(const sentry_options_t *opts, void *_state) } static void -winhttp_shutdown_hook(void *_state) +winhttp_shutdown_hook(void *_client) { - winhttp_state_t *state = _state; + winhttp_client_t *client = _client; // Seems like some requests are taking too long/hanging // Just close them to make sure the background thread is exiting. - if (state->connect) { - WinHttpCloseHandle(state->connect); - state->connect = NULL; + if (client->connect) { + WinHttpCloseHandle(client->connect); + client->connect = NULL; } // NOTE: We need to close the session before closing the request. // This will cancel all other requests which might be queued as well. - if (state->session) { - WinHttpCloseHandle(state->session); - state->session = NULL; + if (client->session) { + WinHttpCloseHandle(client->session); + client->session = NULL; } - if (state->request) { - WinHttpCloseHandle(state->request); - state->request = NULL; + if (client->request) { + WinHttpCloseHandle(client->request); + client->request = NULL; } } static void winhttp_send_task(sentry_prepared_http_request_t *req, - sentry_rate_limiter_t *rl, void *_state) + sentry_rate_limiter_t *rl, void *_client) { - winhttp_state_t *state = (winhttp_state_t *)_state; + winhttp_client_t *client = (winhttp_client_t *)_client; uint64_t started = sentry__monotonic_time(); @@ -190,20 +190,20 @@ winhttp_send_task(sentry_prepared_http_request_t *req, } #endif - if (!state->connect) { - state->connect = WinHttpConnect(state->session, + if (!client->connect) { + client->connect = WinHttpConnect(client->session, url_components.lpszHostName, url_components.nPort, 0); } - if (!state->connect) { + if (!client->connect) { SENTRY_WARNF("`WinHttpConnect` failed with code `%d`", GetLastError()); goto exit; } bool is_secure = strstr(req->url, "https") == req->url; - state->request = WinHttpOpenRequest(state->connect, L"POST", + client->request = WinHttpOpenRequest(client->connect, L"POST", url_components.lpszUrlPath, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, is_secure ? WINHTTP_FLAG_SECURE : 0); - if (!state->request) { + if (!client->request) { SENTRY_WARNF( "`WinHttpOpenRequest` failed with code `%d`", GetLastError()); goto exit; @@ -233,22 +233,22 @@ winhttp_send_task(sentry_prepared_http_request_t *req, goto exit; } - if (state->proxy_username && state->proxy_password) { - WinHttpSetCredentials(state->request, WINHTTP_AUTH_TARGET_PROXY, - WINHTTP_AUTH_SCHEME_BASIC, state->proxy_username, - state->proxy_password, 0); + if (client->proxy_username && client->proxy_password) { + WinHttpSetCredentials(client->request, WINHTTP_AUTH_TARGET_PROXY, + WINHTTP_AUTH_SCHEME_BASIC, client->proxy_username, + client->proxy_password, 0); } - if (WinHttpSendRequest(state->request, headers, (DWORD)-1, + if (WinHttpSendRequest(client->request, headers, (DWORD)-1, (LPVOID)req->body, (DWORD)req->body_len, (DWORD)req->body_len, 0)) { - WinHttpReceiveResponse(state->request, NULL); + WinHttpReceiveResponse(client->request, NULL); - if (state->debug) { + if (client->debug) { // this is basically the example from: // https://docs.microsoft.com/en-us/windows/win32/api/winhttp/nf-winhttp-winhttpqueryheaders#examples DWORD dwSize = 0; LPVOID lpOutBuffer = NULL; - WinHttpQueryHeaders(state->request, WINHTTP_QUERY_RAW_HEADERS_CRLF, + WinHttpQueryHeaders(client->request, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, NULL, &dwSize, WINHTTP_NO_HEADER_INDEX); @@ -258,7 +258,7 @@ winhttp_send_task(sentry_prepared_http_request_t *req, // Now, use WinHttpQueryHeaders to retrieve the header. if (lpOutBuffer - && WinHttpQueryHeaders(state->request, + && WinHttpQueryHeaders(client->request, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, lpOutBuffer, &dwSize, WINHTTP_NO_HEADER_INDEX)) { @@ -276,7 +276,7 @@ winhttp_send_task(sentry_prepared_http_request_t *req, DWORD status_code = 0; DWORD status_code_size = sizeof(status_code); - if (WinHttpQueryHeaders(state->request, WINHTTP_QUERY_CUSTOM, + if (WinHttpQueryHeaders(client->request, WINHTTP_QUERY_CUSTOM, L"x-sentry-rate-limits", buf, &buf_size, WINHTTP_NO_HEADER_INDEX)) { char *h = sentry__string_from_wstr(buf); @@ -284,7 +284,7 @@ winhttp_send_task(sentry_prepared_http_request_t *req, sentry__rate_limiter_update_from_header(rl, h); sentry_free(h); } - } else if (WinHttpQueryHeaders(state->request, WINHTTP_QUERY_CUSTOM, + } else if (WinHttpQueryHeaders(client->request, WINHTTP_QUERY_CUSTOM, L"retry-after", buf, &buf_size, WINHTTP_NO_HEADER_INDEX)) { char *h = sentry__string_from_wstr(buf); @@ -292,7 +292,7 @@ winhttp_send_task(sentry_prepared_http_request_t *req, sentry__rate_limiter_update_from_http_retry_after(rl, h); sentry_free(h); } - } else if (WinHttpQueryHeaders(state->request, + } else if (WinHttpQueryHeaders(client->request, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &status_code, &status_code_size, WINHTTP_NO_HEADER_INDEX) @@ -308,9 +308,9 @@ winhttp_send_task(sentry_prepared_http_request_t *req, SENTRY_DEBUGF("request handled in %llums", now - started); exit: - if (state->request) { - HINTERNET request = state->request; - state->request = NULL; + if (client->request) { + HINTERNET request = client->request; + client->request = NULL; WinHttpCloseHandle(request); } sentry_free(url); @@ -321,11 +321,11 @@ sentry_transport_t * sentry__transport_new_default(void) { SENTRY_INFO("initializing winhttp transport"); - winhttp_state_t *state = winhttp_state_new(); - if (!state) { + winhttp_client_t *client = winhttp_client_new(); + if (!client) { return NULL; } - return sentry__http_transport_new(state, winhttp_state_free, - winhttp_start_backend, winhttp_send_task, winhttp_shutdown_hook); + return sentry__http_transport_new(client, winhttp_client_free, + winhttp_start_client, winhttp_send_task, winhttp_shutdown_hook); } From 2f94348e5476402a55c5cf47b4a9015853ec7464 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 11 Feb 2026 15:53:46 +0100 Subject: [PATCH 04/15] refactor: move bgworker getter from generic transport to HTTP transport Add sentry__transport_get_state as a generic internal accessor and move the HTTP-specific bgworker helper to sentry__http_transport_get_bgworker. Co-Authored-By: Claude Opus 4.6 --- src/sentry_transport.c | 5 +---- src/sentry_transport.h | 8 +------- src/transports/sentry_http_transport.c | 8 ++++++++ src/transports/sentry_http_transport.h | 4 ++++ tests/unit/test_basic.c | 3 ++- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/sentry_transport.c b/src/sentry_transport.c index e53a6f8af..8ce3b7ff7 100644 --- a/src/sentry_transport.c +++ b/src/sentry_transport.c @@ -158,14 +158,11 @@ sentry_transport_free(sentry_transport_t *transport) sentry_free(transport); } -#ifdef SENTRY_UNITTEST void * -sentry__transport_get_bgworker(sentry_transport_t *transport) +sentry__transport_get_state(sentry_transport_t *transport) { - // For HTTP transports (curl/winhttp), the transport state is the bgworker return transport ? transport->state : NULL; } -#endif #ifdef SENTRY_TRANSPORT_COMPRESSION static bool diff --git a/src/sentry_transport.h b/src/sentry_transport.h index 723d15085..0328edc2a 100644 --- a/src/sentry_transport.h +++ b/src/sentry_transport.h @@ -56,13 +56,7 @@ sentry_transport_t *sentry__transport_new_default(void); size_t sentry__transport_dump_queue( sentry_transport_t *transport, sentry_run_t *run); -#ifdef SENTRY_UNITTEST -/** - * Test helper function to get the bgworker from a transport. - * Only available in unit tests and only works for HTTP transports. - */ -void *sentry__transport_get_bgworker(sentry_transport_t *transport); -#endif +void *sentry__transport_get_state(sentry_transport_t *transport); typedef struct sentry_prepared_http_header_s { const char *key; diff --git a/src/transports/sentry_http_transport.c b/src/transports/sentry_http_transport.c index cc072ef67..6cfec8e86 100644 --- a/src/transports/sentry_http_transport.c +++ b/src/transports/sentry_http_transport.c @@ -154,3 +154,11 @@ sentry__http_transport_new(void *client, void (*free_client)(void *), return transport; } + +#ifdef SENTRY_UNITTEST +void * +sentry__http_transport_get_bgworker(sentry_transport_t *transport) +{ + return sentry__transport_get_state(transport); +} +#endif diff --git a/src/transports/sentry_http_transport.h b/src/transports/sentry_http_transport.h index f2f033e10..defb94f40 100644 --- a/src/transports/sentry_http_transport.h +++ b/src/transports/sentry_http_transport.h @@ -23,4 +23,8 @@ sentry_transport_t *sentry__http_transport_new(void *client, int (*start_client)(const sentry_options_t *, void *), sentry_http_send_func_t send_func, void (*shutdown_hook)(void *client)); +#ifdef SENTRY_UNITTEST +void *sentry__http_transport_get_bgworker(sentry_transport_t *transport); +#endif + #endif diff --git a/tests/unit/test_basic.c b/tests/unit/test_basic.c index 919a52616..7c6ce7306 100644 --- a/tests/unit/test_basic.c +++ b/tests/unit/test_basic.c @@ -5,6 +5,7 @@ #include "sentry_sync.h" #include "sentry_testsupport.h" #include "sentry_transport.h" +#include "transports/sentry_http_transport.h" static void send_envelope_test_basic(sentry_envelope_t *envelope, void *data) @@ -328,7 +329,7 @@ SENTRY_TEST(basic_transport_thread_name) // Get the bgworker from the transport (for HTTP transports) sentry_bgworker_t *bgworker - = (sentry_bgworker_t *)sentry__transport_get_bgworker( + = (sentry_bgworker_t *)sentry__http_transport_get_bgworker( runtime_options->transport); TEST_ASSERT(!!bgworker); From ba09b3ff850a1a70545529699ef5b596d59acbe2 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 11 Feb 2026 16:14:46 +0100 Subject: [PATCH 05/15] refactor: use setter API for HTTP transport client callbacks Replace the monolithic constructor with individual setters, mirroring the public transport API. Rename shutdown_hook to shutdown_client for consistency. Co-Authored-By: Claude Opus 4.6 --- src/transports/sentry_http_transport.c | 41 ++++++++++++++++++----- src/transports/sentry_http_transport.h | 22 ++++++------ src/transports/sentry_transport_curl.c | 12 +++++-- src/transports/sentry_transport_winhttp.c | 11 ++++-- 4 files changed, 61 insertions(+), 25 deletions(-) diff --git a/src/transports/sentry_http_transport.c b/src/transports/sentry_http_transport.c index 6cfec8e86..a068872ea 100644 --- a/src/transports/sentry_http_transport.c +++ b/src/transports/sentry_http_transport.c @@ -16,7 +16,7 @@ typedef struct { void (*free_client)(void *); int (*start_client)(const sentry_options_t *, void *); sentry_http_send_func_t send_func; - void (*shutdown_hook)(void *client); + void (*shutdown_client)(void *client); } http_transport_state_t; static void @@ -83,8 +83,8 @@ http_transport_shutdown(uint64_t timeout, void *transport_state) http_transport_state_t *state = sentry__bgworker_get_state(bgworker); int rv = sentry__bgworker_shutdown(bgworker, timeout); - if (rv != 0 && state->shutdown_hook) { - state->shutdown_hook(state->client); + if (rv != 0 && state->shutdown_client) { + state->shutdown_client(state->client); } return rv; } @@ -113,10 +113,15 @@ http_dump_queue(sentry_run_t *run, void *transport_state) bgworker, http_send_task, http_dump_task_cb, run); } +static http_transport_state_t * +http_transport_get_state(sentry_transport_t *transport) +{ + sentry_bgworker_t *bgworker = sentry__transport_get_state(transport); + return sentry__bgworker_get_state(bgworker); +} + sentry_transport_t * -sentry__http_transport_new(void *client, void (*free_client)(void *), - int (*start_client)(const sentry_options_t *, void *), - sentry_http_send_func_t send_func, void (*shutdown_hook)(void *client)) +sentry__http_transport_new(void *client, sentry_http_send_func_t send_func) { http_transport_state_t *state = SENTRY_MAKE(http_transport_state_t); if (!state) { @@ -125,10 +130,7 @@ sentry__http_transport_new(void *client, void (*free_client)(void *), memset(state, 0, sizeof(http_transport_state_t)); state->ratelimiter = sentry__rate_limiter_new(); state->client = client; - state->free_client = free_client; - state->start_client = start_client; state->send_func = send_func; - state->shutdown_hook = shutdown_hook; sentry_bgworker_t *bgworker = sentry__bgworker_new(state, http_transport_state_free); @@ -155,6 +157,27 @@ sentry__http_transport_new(void *client, void (*free_client)(void *), return transport; } +void +sentry__http_transport_set_free_client( + sentry_transport_t *transport, void (*free_client)(void *)) +{ + http_transport_get_state(transport)->free_client = free_client; +} + +void +sentry__http_transport_set_start_client(sentry_transport_t *transport, + int (*start_client)(const sentry_options_t *, void *)) +{ + http_transport_get_state(transport)->start_client = start_client; +} + +void +sentry__http_transport_set_shutdown_client( + sentry_transport_t *transport, void (*shutdown_client)(void *)) +{ + http_transport_get_state(transport)->shutdown_client = shutdown_client; +} + #ifdef SENTRY_UNITTEST void * sentry__http_transport_get_bgworker(sentry_transport_t *transport) diff --git a/src/transports/sentry_http_transport.h b/src/transports/sentry_http_transport.h index defb94f40..4ac1b667a 100644 --- a/src/transports/sentry_http_transport.h +++ b/src/transports/sentry_http_transport.h @@ -10,18 +10,18 @@ typedef void (*sentry_http_send_func_t)(sentry_prepared_http_request_t *req, sentry_rate_limiter_t *rl, void *client); /** - * Creates a new HTTP transport with the given client. - * - * The transport manages bgworker lifecycle (start, flush, shutdown, dump) - * and delegates actual HTTP sending to the client's `send_func`. - * - * `shutdown_hook` is optional (NULL for curl). WinHTTP uses it to force-close - * handles when bgworker_shutdown times out. + * Creates a new HTTP transport with the given client and send function. + * Use the setter functions below to configure optional client callbacks. */ -sentry_transport_t *sentry__http_transport_new(void *client, - void (*free_client)(void *), - int (*start_client)(const sentry_options_t *, void *), - sentry_http_send_func_t send_func, void (*shutdown_hook)(void *client)); +sentry_transport_t *sentry__http_transport_new( + void *client, sentry_http_send_func_t send_func); + +void sentry__http_transport_set_free_client( + sentry_transport_t *transport, void (*free_client)(void *)); +void sentry__http_transport_set_start_client(sentry_transport_t *transport, + int (*start_client)(const sentry_options_t *, void *)); +void sentry__http_transport_set_shutdown_client( + sentry_transport_t *transport, void (*shutdown_client)(void *)); #ifdef SENTRY_UNITTEST void *sentry__http_transport_get_bgworker(sentry_transport_t *transport); diff --git a/src/transports/sentry_transport_curl.c b/src/transports/sentry_transport_curl.c index dcf1a8a0c..7523e249c 100644 --- a/src/transports/sentry_transport_curl.c +++ b/src/transports/sentry_transport_curl.c @@ -108,6 +108,8 @@ curl_start_client(const sentry_options_t *options, void *_client) client->debug = options->debug; if (!client->curl_handle) { + // In this case we don't start the worker at all, which means we can + // still dump all unsent envelopes to disk on shutdown. SENTRY_WARN("`curl_easy_init` failed"); return 1; } @@ -161,7 +163,7 @@ curl_send_task(sentry_prepared_http_request_t *req, sentry_rate_limiter_t *rl, #ifdef SENTRY_PLATFORM_NX if (!sentry_nx_curl_connect(client->nx_state)) { - return; + return; // TODO should we dump the envelope to disk? } #endif @@ -183,6 +185,7 @@ curl_send_task(sentry_prepared_http_request_t *req, sentry_rate_limiter_t *rl, if (client->debug) { curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); curl_easy_setopt(curl, CURLOPT_WRITEDATA, stderr); + // CURLOPT_WRITEFUNCTION will `fwrite` by default } else { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, swallow_data); } @@ -261,6 +264,9 @@ sentry__transport_new_default(void) return NULL; } - return sentry__http_transport_new( - client, curl_client_free, curl_start_client, curl_send_task, NULL); + sentry_transport_t *transport + = sentry__http_transport_new(client, curl_send_task); + sentry__http_transport_set_free_client(transport, curl_client_free); + sentry__http_transport_set_start_client(transport, curl_start_client); + return transport; } diff --git a/src/transports/sentry_transport_winhttp.c b/src/transports/sentry_transport_winhttp.c index 0edda3d93..ae5de4cb2 100644 --- a/src/transports/sentry_transport_winhttp.c +++ b/src/transports/sentry_transport_winhttp.c @@ -54,12 +54,14 @@ winhttp_client_free(void *_client) sentry_free(client); } +// Function to extract and set credentials static void set_proxy_credentials(winhttp_client_t *state, const char *proxy) { sentry_url_t url; sentry__url_parse(&url, proxy, false); if (url.username && url.password) { + // Convert user and pass to LPCWSTR int user_wlen = MultiByteToWideChar(CP_UTF8, 0, url.username, -1, NULL, 0); int pass_wlen @@ -326,6 +328,11 @@ sentry__transport_new_default(void) return NULL; } - return sentry__http_transport_new(client, winhttp_client_free, - winhttp_start_client, winhttp_send_task, winhttp_shutdown_hook); + sentry_transport_t *transport + = sentry__http_transport_new(client, winhttp_send_task); + sentry__http_transport_set_free_client(transport, winhttp_client_free); + sentry__http_transport_set_start_client(transport, winhttp_start_client); + sentry__http_transport_set_shutdown_client( + transport, winhttp_shutdown_hook); + return transport; } From 479a00d6f63d1bc50ccc06840173a262d3272d8b Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 11 Feb 2026 17:21:19 +0100 Subject: [PATCH 06/15] refactor: rename curl/winhttp transport files and normalize function names Rename sentry_transport_{curl,winhttp}.c to sentry_http_transport_{curl,winhttp}.c to group with the base sentry_http_transport. Normalize local function names to consistent {prefix}_client_{verb} pattern. Co-Authored-By: Claude Opus 4.6 --- src/CMakeLists.txt | 4 ++-- ...ntry_transport_curl.c => sentry_http_transport_curl.c} | 4 ++-- ...ransport_winhttp.c => sentry_http_transport_winhttp.c} | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) rename src/transports/{sentry_transport_curl.c => sentry_http_transport_curl.c} (98%) rename src/transports/{sentry_transport_winhttp.c => sentry_http_transport_winhttp.c} (97%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b994e1427..7a2d4865e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -125,11 +125,11 @@ if(SENTRY_TRANSPORT_CURL OR SENTRY_TRANSPORT_WINHTTP) endif() if(SENTRY_TRANSPORT_CURL) sentry_target_sources_cwd(sentry - transports/sentry_transport_curl.c + transports/sentry_http_transport_curl.c ) elseif(SENTRY_TRANSPORT_WINHTTP) sentry_target_sources_cwd(sentry - transports/sentry_transport_winhttp.c + transports/sentry_http_transport_winhttp.c ) elseif(SENTRY_TRANSPORT_NONE) sentry_target_sources_cwd(sentry diff --git a/src/transports/sentry_transport_curl.c b/src/transports/sentry_http_transport_curl.c similarity index 98% rename from src/transports/sentry_transport_curl.c rename to src/transports/sentry_http_transport_curl.c index 7523e249c..de8eced9b 100644 --- a/src/transports/sentry_transport_curl.c +++ b/src/transports/sentry_http_transport_curl.c @@ -63,7 +63,7 @@ curl_client_free(void *_client) } static int -curl_start_client(const sentry_options_t *options, void *_client) +curl_client_start(const sentry_options_t *options, void *_client) { curl_client_t *client = _client; @@ -267,6 +267,6 @@ sentry__transport_new_default(void) sentry_transport_t *transport = sentry__http_transport_new(client, curl_send_task); sentry__http_transport_set_free_client(transport, curl_client_free); - sentry__http_transport_set_start_client(transport, curl_start_client); + sentry__http_transport_set_start_client(transport, curl_client_start); return transport; } diff --git a/src/transports/sentry_transport_winhttp.c b/src/transports/sentry_http_transport_winhttp.c similarity index 97% rename from src/transports/sentry_transport_winhttp.c rename to src/transports/sentry_http_transport_winhttp.c index ae5de4cb2..569f69210 100644 --- a/src/transports/sentry_transport_winhttp.c +++ b/src/transports/sentry_http_transport_winhttp.c @@ -80,7 +80,7 @@ set_proxy_credentials(winhttp_client_t *state, const char *proxy) } static int -winhttp_start_client(const sentry_options_t *opts, void *_client) +winhttp_client_start(const sentry_options_t *opts, void *_client) { winhttp_client_t *client = _client; @@ -139,7 +139,7 @@ winhttp_start_client(const sentry_options_t *opts, void *_client) } static void -winhttp_shutdown_hook(void *_client) +winhttp_client_shutdown(void *_client) { winhttp_client_t *client = _client; // Seems like some requests are taking too long/hanging @@ -331,8 +331,8 @@ sentry__transport_new_default(void) sentry_transport_t *transport = sentry__http_transport_new(client, winhttp_send_task); sentry__http_transport_set_free_client(transport, winhttp_client_free); - sentry__http_transport_set_start_client(transport, winhttp_start_client); + sentry__http_transport_set_start_client(transport, winhttp_client_start); sentry__http_transport_set_shutdown_client( - transport, winhttp_shutdown_hook); + transport, winhttp_client_shutdown); return transport; } From 293b2028de89bcdeab7e686ea2e225ac77604a7d Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 11 Feb 2026 17:36:44 +0100 Subject: [PATCH 07/15] refactor: put client as first arg in HTTP transport callbacks Co-Authored-By: Claude Opus 4.6 --- src/transports/sentry_http_transport.c | 8 ++++---- src/transports/sentry_http_transport.h | 6 +++--- src/transports/sentry_http_transport_curl.c | 6 +++--- src/transports/sentry_http_transport_winhttp.c | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/transports/sentry_http_transport.c b/src/transports/sentry_http_transport.c index a068872ea..5fe1e8579 100644 --- a/src/transports/sentry_http_transport.c +++ b/src/transports/sentry_http_transport.c @@ -14,7 +14,7 @@ typedef struct { sentry_rate_limiter_t *ratelimiter; void *client; void (*free_client)(void *); - int (*start_client)(const sentry_options_t *, void *); + int (*start_client)(void *, const sentry_options_t *); sentry_http_send_func_t send_func; void (*shutdown_client)(void *client); } http_transport_state_t; @@ -44,7 +44,7 @@ http_send_task(void *_envelope, void *_state) return; } - state->send_func(req, state->ratelimiter, state->client); + state->send_func(state->client, req, state->ratelimiter); sentry__prepared_http_request_free(req); } @@ -60,7 +60,7 @@ http_transport_start(const sentry_options_t *options, void *transport_state) state->user_agent = sentry__string_clone(options->user_agent); if (state->start_client) { - int rv = state->start_client(options, state->client); + int rv = state->start_client(state->client, options); if (rv != 0) { return rv; } @@ -166,7 +166,7 @@ sentry__http_transport_set_free_client( void sentry__http_transport_set_start_client(sentry_transport_t *transport, - int (*start_client)(const sentry_options_t *, void *)) + int (*start_client)(void *, const sentry_options_t *)) { http_transport_get_state(transport)->start_client = start_client; } diff --git a/src/transports/sentry_http_transport.h b/src/transports/sentry_http_transport.h index 4ac1b667a..92fc53f4f 100644 --- a/src/transports/sentry_http_transport.h +++ b/src/transports/sentry_http_transport.h @@ -6,8 +6,8 @@ #include "sentry_sync.h" #include "sentry_transport.h" -typedef void (*sentry_http_send_func_t)(sentry_prepared_http_request_t *req, - sentry_rate_limiter_t *rl, void *client); +typedef void (*sentry_http_send_func_t)(void *client, + sentry_prepared_http_request_t *req, sentry_rate_limiter_t *rl); /** * Creates a new HTTP transport with the given client and send function. @@ -19,7 +19,7 @@ sentry_transport_t *sentry__http_transport_new( void sentry__http_transport_set_free_client( sentry_transport_t *transport, void (*free_client)(void *)); void sentry__http_transport_set_start_client(sentry_transport_t *transport, - int (*start_client)(const sentry_options_t *, void *)); + int (*start_client)(void *, const sentry_options_t *)); void sentry__http_transport_set_shutdown_client( sentry_transport_t *transport, void (*shutdown_client)(void *)); diff --git a/src/transports/sentry_http_transport_curl.c b/src/transports/sentry_http_transport_curl.c index de8eced9b..3e8e01fbd 100644 --- a/src/transports/sentry_http_transport_curl.c +++ b/src/transports/sentry_http_transport_curl.c @@ -63,7 +63,7 @@ curl_client_free(void *_client) } static int -curl_client_start(const sentry_options_t *options, void *_client) +curl_client_start(void *_client, const sentry_options_t *options) { curl_client_t *client = _client; @@ -156,8 +156,8 @@ header_callback(char *buffer, size_t size, size_t nitems, void *userdata) } static void -curl_send_task(sentry_prepared_http_request_t *req, sentry_rate_limiter_t *rl, - void *_client) +curl_send_task(void *_client, sentry_prepared_http_request_t *req, + sentry_rate_limiter_t *rl) { curl_client_t *client = (curl_client_t *)_client; diff --git a/src/transports/sentry_http_transport_winhttp.c b/src/transports/sentry_http_transport_winhttp.c index 569f69210..0e84cd1e5 100644 --- a/src/transports/sentry_http_transport_winhttp.c +++ b/src/transports/sentry_http_transport_winhttp.c @@ -80,7 +80,7 @@ set_proxy_credentials(winhttp_client_t *state, const char *proxy) } static int -winhttp_client_start(const sentry_options_t *opts, void *_client) +winhttp_client_start(void *_client, const sentry_options_t *opts) { winhttp_client_t *client = _client; @@ -162,8 +162,8 @@ winhttp_client_shutdown(void *_client) } static void -winhttp_send_task(sentry_prepared_http_request_t *req, - sentry_rate_limiter_t *rl, void *_client) +winhttp_send_task(void *_client, sentry_prepared_http_request_t *req, + sentry_rate_limiter_t *rl) { winhttp_client_t *client = (winhttp_client_t *)_client; From 4a0814eb48e4d3ce450f605fb75e1f25dcc8a69b Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 11 Feb 2026 17:54:50 +0100 Subject: [PATCH 08/15] refactor: centralize rate limiter updates in HTTP transport Introduce sentry_http_response_t so clients just populate response metadata (status_code, retry_after, x_sentry_rate_limits) and the abstract transport handles rate limiting in one place. Co-Authored-By: Claude Opus 4.6 --- src/transports/sentry_http_transport.c | 19 +++++++++++- src/transports/sentry_http_transport.h | 9 ++++-- src/transports/sentry_http_transport_curl.c | 30 +++---------------- .../sentry_http_transport_winhttp.c | 29 ++++++------------ 4 files changed, 38 insertions(+), 49 deletions(-) diff --git a/src/transports/sentry_http_transport.c b/src/transports/sentry_http_transport.c index 5fe1e8579..225779d42 100644 --- a/src/transports/sentry_http_transport.c +++ b/src/transports/sentry_http_transport.c @@ -3,6 +3,7 @@ #include "sentry_database.h" #include "sentry_envelope.h" #include "sentry_options.h" +#include "sentry_ratelimiter.h" #include "sentry_string.h" #include "sentry_transport.h" @@ -44,7 +45,23 @@ http_send_task(void *_envelope, void *_state) return; } - state->send_func(state->client, req, state->ratelimiter); + sentry_http_response_t resp; + memset(&resp, 0, sizeof(resp)); + + state->send_func(state->client, req, &resp); + + if (resp.x_sentry_rate_limits) { + sentry__rate_limiter_update_from_header( + state->ratelimiter, resp.x_sentry_rate_limits); + } else if (resp.retry_after) { + sentry__rate_limiter_update_from_http_retry_after( + state->ratelimiter, resp.retry_after); + } else if (resp.status_code == 429) { + sentry__rate_limiter_update_from_429(state->ratelimiter); + } + + sentry_free(resp.retry_after); + sentry_free(resp.x_sentry_rate_limits); sentry__prepared_http_request_free(req); } diff --git a/src/transports/sentry_http_transport.h b/src/transports/sentry_http_transport.h index 92fc53f4f..fddd48426 100644 --- a/src/transports/sentry_http_transport.h +++ b/src/transports/sentry_http_transport.h @@ -2,12 +2,17 @@ #define SENTRY_HTTP_TRANSPORT_H_INCLUDED #include "sentry_boot.h" -#include "sentry_ratelimiter.h" #include "sentry_sync.h" #include "sentry_transport.h" +typedef struct { + long status_code; + char *retry_after; + char *x_sentry_rate_limits; +} sentry_http_response_t; + typedef void (*sentry_http_send_func_t)(void *client, - sentry_prepared_http_request_t *req, sentry_rate_limiter_t *rl); + sentry_prepared_http_request_t *req, sentry_http_response_t *resp); /** * Creates a new HTTP transport with the given client and send function. diff --git a/src/transports/sentry_http_transport_curl.c b/src/transports/sentry_http_transport_curl.c index 3e8e01fbd..f9a4894a3 100644 --- a/src/transports/sentry_http_transport_curl.c +++ b/src/transports/sentry_http_transport_curl.c @@ -3,7 +3,6 @@ #include "sentry_envelope.h" #include "sentry_http_transport.h" #include "sentry_options.h" -#include "sentry_ratelimiter.h" #include "sentry_string.h" #include "sentry_transport.h" #include "sentry_utils.h" @@ -26,11 +25,6 @@ typedef struct { #endif } curl_client_t; -struct header_info { - char *x_sentry_rate_limits; - char *retry_after; -}; - static curl_client_t * curl_client_new(void) { @@ -134,7 +128,7 @@ static size_t header_callback(char *buffer, size_t size, size_t nitems, void *userdata) { size_t bytes = size * nitems; - struct header_info *info = userdata; + sentry_http_response_t *info = userdata; char *header = sentry__string_clone_n(buffer, bytes); if (!header) { return bytes; @@ -157,7 +151,7 @@ header_callback(char *buffer, size_t size, size_t nitems, void *userdata) static void curl_send_task(void *_client, sentry_prepared_http_request_t *req, - sentry_rate_limiter_t *rl) + sentry_http_response_t *resp) { curl_client_t *client = (curl_client_t *)_client; @@ -200,10 +194,7 @@ curl_send_task(void *_client, sentry_prepared_http_request_t *req, error_buf[0] = 0; curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buf); - struct header_info info; - info.retry_after = NULL; - info.x_sentry_rate_limits = NULL; - curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)&info); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)resp); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); if (client->proxy) { @@ -224,18 +215,7 @@ curl_send_task(void *_client, sentry_prepared_http_request_t *req, } if (rv == CURLE_OK) { - long response_code; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); - - if (info.x_sentry_rate_limits) { - sentry__rate_limiter_update_from_header( - rl, info.x_sentry_rate_limits); - } else if (info.retry_after) { - sentry__rate_limiter_update_from_http_retry_after( - rl, info.retry_after); - } else if (response_code == 429) { - sentry__rate_limiter_update_from_429(rl); - } + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &resp->status_code); } else { size_t len = strlen(error_buf); if (len) { @@ -251,8 +231,6 @@ curl_send_task(void *_client, sentry_prepared_http_request_t *req, } curl_slist_free_all(headers); - sentry_free(info.retry_after); - sentry_free(info.x_sentry_rate_limits); } sentry_transport_t * diff --git a/src/transports/sentry_http_transport_winhttp.c b/src/transports/sentry_http_transport_winhttp.c index 0e84cd1e5..fbf761add 100644 --- a/src/transports/sentry_http_transport_winhttp.c +++ b/src/transports/sentry_http_transport_winhttp.c @@ -3,7 +3,6 @@ #include "sentry_envelope.h" #include "sentry_http_transport.h" #include "sentry_options.h" -#include "sentry_ratelimiter.h" #include "sentry_string.h" #include "sentry_transport.h" #include "sentry_utils.h" @@ -163,7 +162,7 @@ winhttp_client_shutdown(void *_client) static void winhttp_send_task(void *_client, sentry_prepared_http_request_t *req, - sentry_rate_limiter_t *rl) + sentry_http_response_t *resp) { winhttp_client_t *client = (winhttp_client_t *)_client; @@ -275,31 +274,21 @@ winhttp_send_task(void *_client, sentry_prepared_http_request_t *req, wchar_t buf[2048]; DWORD buf_size = sizeof(buf); - DWORD status_code = 0; - DWORD status_code_size = sizeof(status_code); + DWORD status_code_size = sizeof(resp->status_code); + + WinHttpQueryHeaders(client->request, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, &resp->status_code, &status_code_size, + WINHTTP_NO_HEADER_INDEX); if (WinHttpQueryHeaders(client->request, WINHTTP_QUERY_CUSTOM, L"x-sentry-rate-limits", buf, &buf_size, WINHTTP_NO_HEADER_INDEX)) { - char *h = sentry__string_from_wstr(buf); - if (h) { - sentry__rate_limiter_update_from_header(rl, h); - sentry_free(h); - } + resp->x_sentry_rate_limits = sentry__string_from_wstr(buf); } else if (WinHttpQueryHeaders(client->request, WINHTTP_QUERY_CUSTOM, L"retry-after", buf, &buf_size, WINHTTP_NO_HEADER_INDEX)) { - char *h = sentry__string_from_wstr(buf); - if (h) { - sentry__rate_limiter_update_from_http_retry_after(rl, h); - sentry_free(h); - } - } else if (WinHttpQueryHeaders(client->request, - WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, - WINHTTP_HEADER_NAME_BY_INDEX, &status_code, - &status_code_size, WINHTTP_NO_HEADER_INDEX) - && status_code == 429) { - sentry__rate_limiter_update_from_429(rl); + resp->retry_after = sentry__string_from_wstr(buf); } } else { SENTRY_WARNF( From 40c3e9a105838b51d1230a105705d57c7914fe35 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 11 Feb 2026 18:03:53 +0100 Subject: [PATCH 09/15] refactor: move HTTP request types from generic transport to HTTP transport Co-Authored-By: Claude Opus 4.6 --- src/sentry_transport.c | 167 ------------------------- src/sentry_transport.h | 33 ----- src/transports/sentry_http_transport.c | 162 ++++++++++++++++++++++++ src/transports/sentry_http_transport.h | 22 ++++ tests/unit/test_envelopes.c | 2 +- 5 files changed, 185 insertions(+), 201 deletions(-) diff --git a/src/sentry_transport.c b/src/sentry_transport.c index 8ce3b7ff7..6b63c5783 100644 --- a/src/sentry_transport.c +++ b/src/sentry_transport.c @@ -2,22 +2,6 @@ #include "sentry_alloc.h" #include "sentry_envelope.h" #include "sentry_options.h" -#include "sentry_ratelimiter.h" -#include "sentry_string.h" - -#ifdef SENTRY_TRANSPORT_COMPRESSION -# include "zlib.h" -#endif - -#define ENVELOPE_MIME "application/x-sentry-envelope" -#ifdef SENTRY_TRANSPORT_COMPRESSION -// The headers we use are: `x-sentry-auth`, `content-type`, `content-encoding`, -// `content-length` -# define MAX_HTTP_HEADERS 4 -#else -// The headers we use are: `x-sentry-auth`, `content-type`, `content-length` -# define MAX_HTTP_HEADERS 3 -#endif struct sentry_transport_s { void (*send_envelope_func)(sentry_envelope_t *envelope, void *state); @@ -163,154 +147,3 @@ sentry__transport_get_state(sentry_transport_t *transport) { return transport ? transport->state : NULL; } - -#ifdef SENTRY_TRANSPORT_COMPRESSION -static bool -gzipped_with_compression(const char *body, const size_t body_len, - char **compressed_body, size_t *compressed_body_len) -{ - if (!body || body_len == 0) { - return false; - } - - z_stream stream; - memset(&stream, 0, sizeof(stream)); - stream.next_in = (unsigned char *)body; - stream.avail_in = (unsigned int)body_len; - - int err = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, - MAX_WBITS + 16, 9, Z_DEFAULT_STRATEGY); - if (err != Z_OK) { - SENTRY_WARNF("deflateInit2 failed: %d", err); - return false; - } - - size_t len = compressBound((unsigned long)body_len); - char *buffer = sentry_malloc(len); - if (!buffer) { - deflateEnd(&stream); - return false; - } - - while (err == Z_OK) { - stream.next_out = (unsigned char *)(buffer + stream.total_out); - stream.avail_out = (unsigned int)(len - stream.total_out); - err = deflate(&stream, Z_FINISH); - } - - if (err != Z_STREAM_END) { - SENTRY_WARNF("deflate failed: %d", err); - sentry_free(buffer); - buffer = NULL; - deflateEnd(&stream); - return false; - } - - *compressed_body_len = stream.total_out; - *compressed_body = buffer; - - deflateEnd(&stream); - return true; -} -#endif - -sentry_prepared_http_request_t * -sentry__prepare_http_request(sentry_envelope_t *envelope, - const sentry_dsn_t *dsn, const sentry_rate_limiter_t *rl, - const char *user_agent) -{ - if (!dsn || !dsn->is_valid) { - return NULL; - } - - size_t body_len = 0; - bool body_owned = true; - char *body = sentry_envelope_serialize_ratelimited( - envelope, rl, &body_len, &body_owned); - if (!body) { - return NULL; - } - -#ifdef SENTRY_TRANSPORT_COMPRESSION - bool compressed = false; - char *compressed_body = NULL; - size_t compressed_body_len = 0; - compressed = gzipped_with_compression( - body, body_len, &compressed_body, &compressed_body_len); - if (compressed) { - if (body_owned) { - sentry_free(body); - body_owned = false; - } - body = compressed_body; - body_len = compressed_body_len; - body_owned = true; - } -#endif - - sentry_prepared_http_request_t *req - = SENTRY_MAKE(sentry_prepared_http_request_t); - if (!req) { - if (body_owned) { - sentry_free(body); - } - return NULL; - } - req->headers = sentry_malloc( - sizeof(sentry_prepared_http_header_t) * MAX_HTTP_HEADERS); - if (!req->headers) { - sentry_free(req); - if (body_owned) { - sentry_free(body); - } - return NULL; - } - req->headers_len = 0; - - req->method = "POST"; - req->url = sentry__dsn_get_envelope_url(dsn); - - sentry_prepared_http_header_t *h; - h = &req->headers[req->headers_len++]; - h->key = "x-sentry-auth"; - h->value = sentry__dsn_get_auth_header(dsn, user_agent); - - h = &req->headers[req->headers_len++]; - h->key = "content-type"; - h->value = sentry__string_clone(ENVELOPE_MIME); - -#ifdef SENTRY_TRANSPORT_COMPRESSION - if (compressed) { - h = &req->headers[req->headers_len++]; - h->key = "content-encoding"; - h->value = sentry__string_clone("gzip"); - } -#endif - - h = &req->headers[req->headers_len++]; - h->key = "content-length"; - h->value = sentry__int64_to_string((int64_t)body_len); - - req->body = body; - req->body_len = body_len; - req->body_owned = body_owned; - - return req; -} - -void -sentry__prepared_http_request_free(sentry_prepared_http_request_t *req) -{ - if (!req) { - return; - } - sentry_free(req->url); - for (size_t i = 0; i < req->headers_len; i++) { - sentry_free(req->headers[i].value); - } - sentry_free(req->headers); - if (req->body_owned) { - sentry_free(req->body); - } - sentry_free(req); -} diff --git a/src/sentry_transport.h b/src/sentry_transport.h index 0328edc2a..036233284 100644 --- a/src/sentry_transport.h +++ b/src/sentry_transport.h @@ -3,7 +3,6 @@ #include "sentry_boot.h" #include "sentry_database.h" -#include "sentry_ratelimiter.h" #include "sentry_utils.h" /** @@ -58,36 +57,4 @@ size_t sentry__transport_dump_queue( void *sentry__transport_get_state(sentry_transport_t *transport); -typedef struct sentry_prepared_http_header_s { - const char *key; - char *value; -} sentry_prepared_http_header_t; - -/** - * This represents a HTTP request, with method, url, headers and a body. - */ -typedef struct sentry_prepared_http_request_s { - const char *method; - char *url; - sentry_prepared_http_header_t *headers; - size_t headers_len; - char *body; - size_t body_len; - bool body_owned; -} sentry_prepared_http_request_t; - -/** - * Consumes the given envelope and transforms it into into a prepared http - * request. This can return NULL when all the items in the envelope have been - * rate limited. - */ -sentry_prepared_http_request_t *sentry__prepare_http_request( - sentry_envelope_t *envelope, const sentry_dsn_t *dsn, - const sentry_rate_limiter_t *rl, const char *user_agent); - -/** - * Free a previously allocated HTTP request. - */ -void sentry__prepared_http_request_free(sentry_prepared_http_request_t *req); - #endif diff --git a/src/transports/sentry_http_transport.c b/src/transports/sentry_http_transport.c index 225779d42..c4fe1931c 100644 --- a/src/transports/sentry_http_transport.c +++ b/src/transports/sentry_http_transport.c @@ -7,8 +7,19 @@ #include "sentry_string.h" #include "sentry_transport.h" +#ifdef SENTRY_TRANSPORT_COMPRESSION +# include "zlib.h" +#endif + #include +#define ENVELOPE_MIME "application/x-sentry-envelope" +#ifdef SENTRY_TRANSPORT_COMPRESSION +# define MAX_HTTP_HEADERS 4 +#else +# define MAX_HTTP_HEADERS 3 +#endif + typedef struct { sentry_dsn_t *dsn; char *user_agent; @@ -20,6 +31,157 @@ typedef struct { void (*shutdown_client)(void *client); } http_transport_state_t; +#ifdef SENTRY_TRANSPORT_COMPRESSION +static bool +gzipped_with_compression(const char *body, const size_t body_len, + char **compressed_body, size_t *compressed_body_len) +{ + if (!body || body_len == 0) { + return false; + } + + z_stream stream; + memset(&stream, 0, sizeof(stream)); + stream.next_in = (unsigned char *)body; + stream.avail_in = (unsigned int)body_len; + + int err = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + MAX_WBITS + 16, 9, Z_DEFAULT_STRATEGY); + if (err != Z_OK) { + SENTRY_WARNF("deflateInit2 failed: %d", err); + return false; + } + + size_t len = compressBound((unsigned long)body_len); + char *buffer = sentry_malloc(len); + if (!buffer) { + deflateEnd(&stream); + return false; + } + + while (err == Z_OK) { + stream.next_out = (unsigned char *)(buffer + stream.total_out); + stream.avail_out = (unsigned int)(len - stream.total_out); + err = deflate(&stream, Z_FINISH); + } + + if (err != Z_STREAM_END) { + SENTRY_WARNF("deflate failed: %d", err); + sentry_free(buffer); + buffer = NULL; + deflateEnd(&stream); + return false; + } + + *compressed_body_len = stream.total_out; + *compressed_body = buffer; + + deflateEnd(&stream); + return true; +} +#endif + +sentry_prepared_http_request_t * +sentry__prepare_http_request(sentry_envelope_t *envelope, + const sentry_dsn_t *dsn, const sentry_rate_limiter_t *rl, + const char *user_agent) +{ + if (!dsn || !dsn->is_valid) { + return NULL; + } + + size_t body_len = 0; + bool body_owned = true; + char *body = sentry_envelope_serialize_ratelimited( + envelope, rl, &body_len, &body_owned); + if (!body) { + return NULL; + } + +#ifdef SENTRY_TRANSPORT_COMPRESSION + bool compressed = false; + char *compressed_body = NULL; + size_t compressed_body_len = 0; + compressed = gzipped_with_compression( + body, body_len, &compressed_body, &compressed_body_len); + if (compressed) { + if (body_owned) { + sentry_free(body); + body_owned = false; + } + body = compressed_body; + body_len = compressed_body_len; + body_owned = true; + } +#endif + + sentry_prepared_http_request_t *req + = SENTRY_MAKE(sentry_prepared_http_request_t); + if (!req) { + if (body_owned) { + sentry_free(body); + } + return NULL; + } + req->headers = sentry_malloc( + sizeof(sentry_prepared_http_header_t) * MAX_HTTP_HEADERS); + if (!req->headers) { + sentry_free(req); + if (body_owned) { + sentry_free(body); + } + return NULL; + } + req->headers_len = 0; + + req->method = "POST"; + req->url = sentry__dsn_get_envelope_url(dsn); + + sentry_prepared_http_header_t *h; + h = &req->headers[req->headers_len++]; + h->key = "x-sentry-auth"; + h->value = sentry__dsn_get_auth_header(dsn, user_agent); + + h = &req->headers[req->headers_len++]; + h->key = "content-type"; + h->value = sentry__string_clone(ENVELOPE_MIME); + +#ifdef SENTRY_TRANSPORT_COMPRESSION + if (compressed) { + h = &req->headers[req->headers_len++]; + h->key = "content-encoding"; + h->value = sentry__string_clone("gzip"); + } +#endif + + h = &req->headers[req->headers_len++]; + h->key = "content-length"; + h->value = sentry__int64_to_string((int64_t)body_len); + + req->body = body; + req->body_len = body_len; + req->body_owned = body_owned; + + return req; +} + +void +sentry__prepared_http_request_free(sentry_prepared_http_request_t *req) +{ + if (!req) { + return; + } + sentry_free(req->url); + for (size_t i = 0; i < req->headers_len; i++) { + sentry_free(req->headers[i].value); + } + sentry_free(req->headers); + if (req->body_owned) { + sentry_free(req->body); + } + sentry_free(req); +} + static void http_transport_state_free(void *_state) { diff --git a/src/transports/sentry_http_transport.h b/src/transports/sentry_http_transport.h index fddd48426..900b65305 100644 --- a/src/transports/sentry_http_transport.h +++ b/src/transports/sentry_http_transport.h @@ -2,9 +2,31 @@ #define SENTRY_HTTP_TRANSPORT_H_INCLUDED #include "sentry_boot.h" +#include "sentry_ratelimiter.h" #include "sentry_sync.h" #include "sentry_transport.h" +typedef struct sentry_prepared_http_header_s { + const char *key; + char *value; +} sentry_prepared_http_header_t; + +typedef struct sentry_prepared_http_request_s { + const char *method; + char *url; + sentry_prepared_http_header_t *headers; + size_t headers_len; + char *body; + size_t body_len; + bool body_owned; +} sentry_prepared_http_request_t; + +sentry_prepared_http_request_t *sentry__prepare_http_request( + sentry_envelope_t *envelope, const sentry_dsn_t *dsn, + const sentry_rate_limiter_t *rl, const char *user_agent); + +void sentry__prepared_http_request_free(sentry_prepared_http_request_t *req); + typedef struct { long status_code; char *retry_after; diff --git a/tests/unit/test_envelopes.c b/tests/unit/test_envelopes.c index 5acfea790..06e686398 100644 --- a/tests/unit/test_envelopes.c +++ b/tests/unit/test_envelopes.c @@ -2,9 +2,9 @@ #include "sentry_json.h" #include "sentry_path.h" #include "sentry_testsupport.h" -#include "sentry_transport.h" #include "sentry_utils.h" #include "sentry_value.h" +#include "transports/sentry_http_transport.h" static char *const SERIALIZED_ENVELOPE_STR = "{\"dsn\":\"https://foo@sentry.invalid/42\"," From 782db1068fffd668d3befc150d2cb6fc4f1b4f10 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 11 Feb 2026 19:13:33 +0100 Subject: [PATCH 10/15] refactor: set status_code = -1 on network errors in HTTP backends Co-Authored-By: Claude Opus 4.6 --- src/transports/sentry_http_transport_curl.c | 2 ++ src/transports/sentry_http_transport_winhttp.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/transports/sentry_http_transport_curl.c b/src/transports/sentry_http_transport_curl.c index f9a4894a3..ddaacea26 100644 --- a/src/transports/sentry_http_transport_curl.c +++ b/src/transports/sentry_http_transport_curl.c @@ -157,6 +157,7 @@ curl_send_task(void *_client, sentry_prepared_http_request_t *req, #ifdef SENTRY_PLATFORM_NX if (!sentry_nx_curl_connect(client->nx_state)) { + resp->status_code = -1; return; // TODO should we dump the envelope to disk? } #endif @@ -228,6 +229,7 @@ curl_send_task(void *_client, sentry_prepared_http_request_t *req, SENTRY_WARNF("`curl_easy_perform` failed with code `%d`: %s", (int)rv, curl_easy_strerror(rv)); } + resp->status_code = -1; } curl_slist_free_all(headers); diff --git a/src/transports/sentry_http_transport_winhttp.c b/src/transports/sentry_http_transport_winhttp.c index fbf761add..1414811df 100644 --- a/src/transports/sentry_http_transport_winhttp.c +++ b/src/transports/sentry_http_transport_winhttp.c @@ -165,6 +165,7 @@ winhttp_send_task(void *_client, sentry_prepared_http_request_t *req, sentry_http_response_t *resp) { winhttp_client_t *client = (winhttp_client_t *)_client; + resp->status_code = -1; uint64_t started = sentry__monotonic_time(); @@ -293,6 +294,7 @@ winhttp_send_task(void *_client, sentry_prepared_http_request_t *req, } else { SENTRY_WARNF( "`WinHttpSendRequest` failed with code `%d`", GetLastError()); + resp->status_code = -1; } uint64_t now = sentry__monotonic_time(); From 4c9b79c8f9918481e94361d2461c42782effd6fc Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 12 Feb 2026 08:47:47 +0100 Subject: [PATCH 11/15] refactor: make HTTP send functions return bool for success/failure Co-Authored-By: Claude Opus 4.6 --- src/transports/sentry_http_transport.c | 26 +++++++++---------- src/transports/sentry_http_transport.h | 2 +- src/transports/sentry_http_transport_curl.c | 7 +++-- .../sentry_http_transport_winhttp.c | 11 ++++---- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/transports/sentry_http_transport.c b/src/transports/sentry_http_transport.c index c4fe1931c..d67dbe863 100644 --- a/src/transports/sentry_http_transport.c +++ b/src/transports/sentry_http_transport.c @@ -210,20 +210,20 @@ http_send_task(void *_envelope, void *_state) sentry_http_response_t resp; memset(&resp, 0, sizeof(resp)); - state->send_func(state->client, req, &resp); - - if (resp.x_sentry_rate_limits) { - sentry__rate_limiter_update_from_header( - state->ratelimiter, resp.x_sentry_rate_limits); - } else if (resp.retry_after) { - sentry__rate_limiter_update_from_http_retry_after( - state->ratelimiter, resp.retry_after); - } else if (resp.status_code == 429) { - sentry__rate_limiter_update_from_429(state->ratelimiter); - } + if (state->send_func(state->client, req, &resp)) { + if (resp.x_sentry_rate_limits) { + sentry__rate_limiter_update_from_header( + state->ratelimiter, resp.x_sentry_rate_limits); + } else if (resp.retry_after) { + sentry__rate_limiter_update_from_http_retry_after( + state->ratelimiter, resp.retry_after); + } else if (resp.status_code == 429) { + sentry__rate_limiter_update_from_429(state->ratelimiter); + } - sentry_free(resp.retry_after); - sentry_free(resp.x_sentry_rate_limits); + sentry_free(resp.retry_after); + sentry_free(resp.x_sentry_rate_limits); + } sentry__prepared_http_request_free(req); } diff --git a/src/transports/sentry_http_transport.h b/src/transports/sentry_http_transport.h index 900b65305..72fd5dea8 100644 --- a/src/transports/sentry_http_transport.h +++ b/src/transports/sentry_http_transport.h @@ -33,7 +33,7 @@ typedef struct { char *x_sentry_rate_limits; } sentry_http_response_t; -typedef void (*sentry_http_send_func_t)(void *client, +typedef bool (*sentry_http_send_func_t)(void *client, sentry_prepared_http_request_t *req, sentry_http_response_t *resp); /** diff --git a/src/transports/sentry_http_transport_curl.c b/src/transports/sentry_http_transport_curl.c index ddaacea26..608fea9c6 100644 --- a/src/transports/sentry_http_transport_curl.c +++ b/src/transports/sentry_http_transport_curl.c @@ -149,7 +149,7 @@ header_callback(char *buffer, size_t size, size_t nitems, void *userdata) return bytes; } -static void +static bool curl_send_task(void *_client, sentry_prepared_http_request_t *req, sentry_http_response_t *resp) { @@ -157,8 +157,7 @@ curl_send_task(void *_client, sentry_prepared_http_request_t *req, #ifdef SENTRY_PLATFORM_NX if (!sentry_nx_curl_connect(client->nx_state)) { - resp->status_code = -1; - return; // TODO should we dump the envelope to disk? + return false; // TODO should we dump the envelope to disk? } #endif @@ -229,10 +228,10 @@ curl_send_task(void *_client, sentry_prepared_http_request_t *req, SENTRY_WARNF("`curl_easy_perform` failed with code `%d`: %s", (int)rv, curl_easy_strerror(rv)); } - resp->status_code = -1; } curl_slist_free_all(headers); + return rv == CURLE_OK; } sentry_transport_t * diff --git a/src/transports/sentry_http_transport_winhttp.c b/src/transports/sentry_http_transport_winhttp.c index 1414811df..61c85a719 100644 --- a/src/transports/sentry_http_transport_winhttp.c +++ b/src/transports/sentry_http_transport_winhttp.c @@ -160,12 +160,12 @@ winhttp_client_shutdown(void *_client) } } -static void +static bool winhttp_send_task(void *_client, sentry_prepared_http_request_t *req, sentry_http_response_t *resp) { winhttp_client_t *client = (winhttp_client_t *)_client; - resp->status_code = -1; + bool result = false; uint64_t started = sentry__monotonic_time(); @@ -241,8 +241,9 @@ winhttp_send_task(void *_client, sentry_prepared_http_request_t *req, client->proxy_password, 0); } - if (WinHttpSendRequest(client->request, headers, (DWORD)-1, - (LPVOID)req->body, (DWORD)req->body_len, (DWORD)req->body_len, 0)) { + if ((result = WinHttpSendRequest(client->request, headers, (DWORD)-1, + (LPVOID)req->body, (DWORD)req->body_len, (DWORD)req->body_len, + 0))) { WinHttpReceiveResponse(client->request, NULL); if (client->debug) { @@ -294,7 +295,6 @@ winhttp_send_task(void *_client, sentry_prepared_http_request_t *req, } else { SENTRY_WARNF( "`WinHttpSendRequest` failed with code `%d`", GetLastError()); - resp->status_code = -1; } uint64_t now = sentry__monotonic_time(); @@ -308,6 +308,7 @@ winhttp_send_task(void *_client, sentry_prepared_http_request_t *req, } sentry_free(url); sentry_free(headers); + return result; } sentry_transport_t * From 2e905865ee946fab2ec82c0e1a948777c450e94c Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 12 Feb 2026 09:17:11 +0100 Subject: [PATCH 12/15] fix: always compile HTTP transport base for iOS and unit tests Co-Authored-By: Claude Opus 4.6 --- src/CMakeLists.txt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7a2d4865e..4ccb5ddd8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -64,6 +64,8 @@ sentry_target_sources_cwd(sentry transports/sentry_disk_transport.c transports/sentry_disk_transport.h transports/sentry_function_transport.c + transports/sentry_http_transport.c + transports/sentry_http_transport.h unwinder/sentry_unwinder.c ) @@ -117,12 +119,6 @@ elseif(AIX) endif() # transport -if(SENTRY_TRANSPORT_CURL OR SENTRY_TRANSPORT_WINHTTP) - sentry_target_sources_cwd(sentry - transports/sentry_http_transport.c - transports/sentry_http_transport.h - ) -endif() if(SENTRY_TRANSPORT_CURL) sentry_target_sources_cwd(sentry transports/sentry_http_transport_curl.c From f694e804d4b15e955d44b199bb5293ed8c848d46 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 12 Feb 2026 09:43:44 +0100 Subject: [PATCH 13/15] fix: use local variables for platform-specific HTTP status code types curl_easy_getinfo writes a long* and WinHttpQueryHeaders writes a DWORD. Use local variables of the correct native type and assign to the int status_code field to avoid type-size mismatches. Co-Authored-By: Claude Opus 4.6 --- src/transports/sentry_http_transport.h | 2 +- src/transports/sentry_http_transport_curl.c | 4 +++- src/transports/sentry_http_transport_winhttp.c | 6 ++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/transports/sentry_http_transport.h b/src/transports/sentry_http_transport.h index 72fd5dea8..30493d564 100644 --- a/src/transports/sentry_http_transport.h +++ b/src/transports/sentry_http_transport.h @@ -28,7 +28,7 @@ sentry_prepared_http_request_t *sentry__prepare_http_request( void sentry__prepared_http_request_free(sentry_prepared_http_request_t *req); typedef struct { - long status_code; + int status_code; char *retry_after; char *x_sentry_rate_limits; } sentry_http_response_t; diff --git a/src/transports/sentry_http_transport_curl.c b/src/transports/sentry_http_transport_curl.c index 608fea9c6..00c77753d 100644 --- a/src/transports/sentry_http_transport_curl.c +++ b/src/transports/sentry_http_transport_curl.c @@ -215,7 +215,9 @@ curl_send_task(void *_client, sentry_prepared_http_request_t *req, } if (rv == CURLE_OK) { - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &resp->status_code); + long response_code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + resp->status_code = (int)response_code; } else { size_t len = strlen(error_buf); if (len) { diff --git a/src/transports/sentry_http_transport_winhttp.c b/src/transports/sentry_http_transport_winhttp.c index 61c85a719..907f2d095 100644 --- a/src/transports/sentry_http_transport_winhttp.c +++ b/src/transports/sentry_http_transport_winhttp.c @@ -276,12 +276,14 @@ winhttp_send_task(void *_client, sentry_prepared_http_request_t *req, wchar_t buf[2048]; DWORD buf_size = sizeof(buf); - DWORD status_code_size = sizeof(resp->status_code); + DWORD status_code = 0; + DWORD status_code_size = sizeof(status_code); WinHttpQueryHeaders(client->request, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, - WINHTTP_HEADER_NAME_BY_INDEX, &resp->status_code, &status_code_size, + WINHTTP_HEADER_NAME_BY_INDEX, &status_code, &status_code_size, WINHTTP_NO_HEADER_INDEX); + resp->status_code = (int)status_code; if (WinHttpQueryHeaders(client->request, WINHTTP_QUERY_CUSTOM, L"x-sentry-rate-limits", buf, &buf_size, From a50598f162c4d488b4baa4d608c547a817930922 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 12 Feb 2026 09:44:15 +0100 Subject: [PATCH 14/15] fix: free HTTP response strings even when send_func returns false The curl header_callback may populate retry_after/x_sentry_rate_limits before curl_easy_perform fails. Moving the frees outside the success branch avoids leaking these strings on network errors. Co-Authored-By: Claude Opus 4.6 --- src/transports/sentry_http_transport.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/transports/sentry_http_transport.c b/src/transports/sentry_http_transport.c index d67dbe863..24b1ba566 100644 --- a/src/transports/sentry_http_transport.c +++ b/src/transports/sentry_http_transport.c @@ -220,10 +220,9 @@ http_send_task(void *_envelope, void *_state) } else if (resp.status_code == 429) { sentry__rate_limiter_update_from_429(state->ratelimiter); } - - sentry_free(resp.retry_after); - sentry_free(resp.x_sentry_rate_limits); } + sentry_free(resp.retry_after); + sentry_free(resp.x_sentry_rate_limits); sentry__prepared_http_request_free(req); } From 3a741900f9ef2840a72cfcdd25b8776a3e2fbf54 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 12 Feb 2026 10:16:52 +0100 Subject: [PATCH 15/15] fix: add null check after sentry__http_transport_new in default transport If sentry__http_transport_new returns NULL, the subsequent set_* calls would dereference a null pointer. Free the client and return NULL early. Co-Authored-By: Claude Opus 4.6 --- src/transports/sentry_http_transport_curl.c | 4 ++++ src/transports/sentry_http_transport_winhttp.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/transports/sentry_http_transport_curl.c b/src/transports/sentry_http_transport_curl.c index 00c77753d..b0c967f5c 100644 --- a/src/transports/sentry_http_transport_curl.c +++ b/src/transports/sentry_http_transport_curl.c @@ -247,6 +247,10 @@ sentry__transport_new_default(void) sentry_transport_t *transport = sentry__http_transport_new(client, curl_send_task); + if (!transport) { + curl_client_free(client); + return NULL; + } sentry__http_transport_set_free_client(transport, curl_client_free); sentry__http_transport_set_start_client(transport, curl_client_start); return transport; diff --git a/src/transports/sentry_http_transport_winhttp.c b/src/transports/sentry_http_transport_winhttp.c index 907f2d095..0997d3562 100644 --- a/src/transports/sentry_http_transport_winhttp.c +++ b/src/transports/sentry_http_transport_winhttp.c @@ -324,6 +324,10 @@ sentry__transport_new_default(void) sentry_transport_t *transport = sentry__http_transport_new(client, winhttp_send_task); + if (!transport) { + winhttp_client_free(client); + return NULL; + } sentry__http_transport_set_free_client(transport, winhttp_client_free); sentry__http_transport_set_start_client(transport, winhttp_client_start); sentry__http_transport_set_shutdown_client(