using System.Collections.Concurrent; using System.Runtime.CompilerServices; namespace ZymonicServices; internal static class ZymonicHttpClients { private static readonly ConcurrentDictionary> Clients = new(); public static HttpClient Anonymous(IZymonicLogger logger, string baseUri) { return GetOrCreate( $"anonymous:{NormalizeBaseUri(baseUri)}", () => new HttpClient(new HttpLoggingHandler(logger, new HttpClientHandler() { UseCookies = false })) { BaseAddress = new Uri(NormalizeBaseUri(baseUri)), // Shared clients cannot safely vary HttpClient.Timeout per request; callers use TimeoutToken instead. Timeout = Timeout.InfiniteTimeSpan }); } public static HttpClient Authenticated(IZymonicLogger logger, IZymonicAA authService, string baseUri) { return GetOrCreate( $"authenticated:{RuntimeHelpers.GetHashCode(authService)}:{NormalizeBaseUri(baseUri)}", () => new HttpClient(new ZymonicAPIHandler(logger, authService, new HttpLoggingHandler(logger))) { BaseAddress = new Uri(NormalizeBaseUri(baseUri)), // Shared clients cannot safely vary HttpClient.Timeout per request; callers use TimeoutToken instead. Timeout = Timeout.InfiniteTimeSpan }); } public static CancellationTokenSource TimeoutToken(CancellationToken ct, int timeoutSeconds) { var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct); timeoutCts.CancelAfter(TimeSpan.FromSeconds(timeoutSeconds)); return timeoutCts; } private static HttpClient GetOrCreate(string key, Func createClient) { return Clients.GetOrAdd(key, _ => new Lazy(createClient, LazyThreadSafetyMode.ExecutionAndPublication)).Value; } private static string NormalizeBaseUri(string baseUri) { return baseUri.EndsWith("/", StringComparison.Ordinal) ? baseUri : $"{baseUri}/"; } }