using System; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using RudderSdk.Core.Abstractions; using RudderSdk.Core.Models; namespace RudderSdk.Core; /// /// Default built on . /// Serializes bodies with Newtonsoft.Json, attaches the bearer token, maps /// non-success statuses to subclasses and /// network failures to . /// public sealed class HttpClientTransport : IRudderTransport { private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10); private readonly string _baseUrl; private readonly HttpClient _httpClient; /// /// Creates the transport for the given API base URL. When no /// is supplied, an owned instance with a /// 10-second request timeout is created. /// public HttpClientTransport(string baseUrl, HttpClient? httpClient = null) { if (string.IsNullOrEmpty(baseUrl)) throw new ArgumentException("Base URL is required.", nameof(baseUrl)); _baseUrl = baseUrl.TrimEnd('/'); _httpClient = httpClient ?? new HttpClient { Timeout = DefaultTimeout }; } /// public async Task SendAsync( string method, string path, TRequest? request, string? accessToken, CancellationToken cancellationToken = default) { using var httpRequest = new HttpRequestMessage(new HttpMethod(method), _baseUrl + path); if (!string.IsNullOrEmpty(accessToken)) httpRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken); if (request is not null) httpRequest.Content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json"); HttpResponseMessage response; try { response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested) { throw new RudderNetworkException("The request timed out.", ex); } catch (HttpRequestException ex) { throw new RudderNetworkException(ex.Message, ex); } using (response) { var body = response.Content is null ? null : await response.Content.ReadAsStringAsync().ConfigureAwait(false); if (!response.IsSuccessStatusCode) throw MapError((int)response.StatusCode, response.ReasonPhrase, body); if (string.IsNullOrEmpty(body)) return default!; return JsonConvert.DeserializeObject(body)!; } } private static RudderApiException MapError(int statusCode, string? reason, string? body) { string? code = null; string? message = null; string? requestId = null; if (!string.IsNullOrEmpty(body)) { try { var error = JsonConvert.DeserializeObject(body); code = error?.Code; message = error?.Error; requestId = error?.RequestId; } catch (JsonException) { // Non-JSON error body — fall back to the status line. } } if (string.IsNullOrEmpty(message)) message = string.IsNullOrEmpty(reason) ? "HTTP " + statusCode : reason; return statusCode switch { 401 => new RudderAuthException(statusCode, code, message, requestId), 404 => new RudderNotFoundException(statusCode, code, message, requestId), 429 => new RudderRateLimitException(statusCode, code, message, requestId), _ => new RudderApiException(statusCode, code, message, requestId), }; } }