using System.Net.Http.Headers; namespace ZymonicServices; // HACK: request/response logger - remove after diagnosis static class RequestLogger { private static readonly string _dir = Path.Combine(AppContext.BaseDirectory, "requests"); private static int _seq = 0; public static async Task Log(HttpRequestMessage request, HttpResponseMessage? response, Exception? ex) { Directory.CreateDirectory(_dir); var n = System.Threading.Interlocked.Increment(ref _seq); var file = Path.Combine(_dir, $"{n:D4}_{DateTime.Now:HHmmss_fff}.txt"); var sb = new System.Text.StringBuilder(); sb.AppendLine($"=== REQUEST {n} {DateTime.Now:O} ==="); sb.AppendLine($"{request.Method} {request.RequestUri}"); foreach (var h in request.Headers) sb.AppendLine($" {h.Key}: {string.Join(", ", h.Value)}"); if (request.Content != null) { foreach (var h in request.Content.Headers) sb.AppendLine($" {h.Key}: {string.Join(", ", h.Value)}"); sb.AppendLine(await request.Content.ReadAsStringAsync()); } sb.AppendLine(); if (response != null) { sb.AppendLine($"=== RESPONSE {(int)response.StatusCode} {response.StatusCode} ==="); foreach (var h in response.Headers) sb.AppendLine($" {h.Key}: {string.Join(", ", h.Value)}"); sb.AppendLine(await response.Content.ReadAsStringAsync()); } if (ex != null) sb.AppendLine($"=== EXCEPTION ===\n{ex}"); await File.WriteAllTextAsync(file, sb.ToString()); } } // https://github.com/reactiveui/refit?tab=readme-ov-file#reducing-header-boilerplate-with-delegatinghandlers-authorization-headers-worked-example class ZymonicAPIHandler : DelegatingHandler { private readonly IZymonicAA authTokenStore; private readonly IZymonicLogger _logger; public ZymonicAPIHandler(IZymonicLogger logger, IZymonicAA authTokenStore, HttpMessageHandler? innerHandler = null) : base(innerHandler ?? new HttpClientHandler() { UseCookies = false }) { this.authTokenStore = authTokenStore ?? throw new ArgumentNullException(nameof(authTokenStore)); _logger = logger; } protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var token = await authTokenStore.GetToken(); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); try { var result = await base.SendAsync(request, cancellationToken).ConfigureAwait(true); // Buffer content so the logger can read it without consuming the stream // UNCOMMENT THE TWO LINES BELOW TO GET INDIVIDUAL API requests logged to disk. // await result.Content.LoadIntoBufferAsync(); // await RequestLogger.Log(request, result, null); result.EnsureSuccessStatusCode(); return result; } catch (Exception e) { await RequestLogger.Log(request, null, e); debugInnerException(e); throw; } } private void debugInnerException(Exception exception) { _logger.LogError(exception.ToString()); if (exception.InnerException != null) { debugInnerException(exception.InnerException); } } }