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),
};
}
}