Initial commit
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Default <see cref="IRudderTransport"/> built on <see cref="HttpClient"/>.
|
||||
/// Serializes bodies with Newtonsoft.Json, attaches the bearer token, maps
|
||||
/// non-success statuses to <see cref="RudderApiException"/> subclasses and
|
||||
/// network failures to <see cref="RudderNetworkException"/>.
|
||||
/// </summary>
|
||||
public sealed class HttpClientTransport : IRudderTransport
|
||||
{
|
||||
private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
private readonly string _baseUrl;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
/// <summary>
|
||||
/// Creates the transport for the given API base URL. When no
|
||||
/// <paramref name="httpClient"/> is supplied, an owned instance with a
|
||||
/// 10-second request timeout is created.
|
||||
/// </summary>
|
||||
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 };
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<TResponse> SendAsync<TRequest, TResponse>(
|
||||
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<TResponse>(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<ErrorResponse>(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),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user