using System.Net.Http; using System.Text.Json; namespace LazyBear.Confluence; public static class ConfluenceClientFactory { public static HttpClient CreateClient(string baseUrl, HttpClientHandler? handler = null) { handler ??= new HttpClientHandler(); return new HttpClient(handler) { BaseAddress = new Uri(baseUrl.TrimEnd('/')), DefaultRequestHeaders = { HttpHeaders { ["User-Agent"] = "LazyBear-Confluence-MCP/1.0", ["Accept"] = "application/json" } } }; } public static ConfluenceHttpClientProvider CreateProvider(string baseUrl, string? token = null, HttpClientHandler? handler = null) { var httpHandler = handler ?? new HttpClientHandler(); var httpClient = CreateClient(baseUrl, httpHandler); return new ConfluenceHttpClientProvider(httpClient, baseUrl, token); } } public sealed class ConfluenceHttpClientProvider { public HttpClient Client { get; } public string BaseUrl => HttpBaseAddress?.Root ?? string.Empty; public string? AccessToken { get; } public string? InitializationError { get; } private Uri? HttpBaseAddress; public ConfluenceHttpClientProvider(HttpClient client, string baseUrl, string? token = null) { Client = client; HttpBaseAddress = new Uri(baseUrl.TrimEnd('/')); AccessToken = token; } public ConcurrentDictionary Cache { get; } = new(); public async Task GetJsonAsync(HttpRequestMessage request) where T : class { using var response = await Client.SendAsync(request).ConfigureAwait(false); if (!response.IsSuccessStatusCode) { if (response.StatusCode == System.Net.HttpStatusCode.NotFound) { return default; } var errorBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); throw new HttpRequestException($"[{request.RequestMethod}] {request.RequestUri}: {response.StatusCode} {errorBody}"); } return await JsonSerializer.DeserializeAsync(response.Content, request, JsonDefaults.DefaultSerializerOptions) .ConfigureAwait(false); } public async Task GetJsonAsync(HttpRequestMessage request) { if (Cache.TryGetValue(request.RequestUri!.ToString(), out var cached)) { return cached; } using var response = await Client.SendAsync(request).ConfigureAwait(false); if (!response.IsSuccessStatusCode) { if (response.StatusCode == System.Net.HttpStatusCode.NotFound) { return null; } var errorBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); throw new HttpRequestException($"[{request.RequestMethod}] {request.RequestUri}: {response.StatusCode} {errorBody}"); } var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var element = JsonDocument.Parse(json).RootElement; Cache[request.RequestUri!.ToString()] = element; return element; } public HttpRequestMessage CreateGetRequest(string resource) { return new HttpRequestMessage(HttpMethod.Get, HttpBaseAddress + resource); } public HttpRequestMessage CreatePostRequest(string resource, object? body = null) { var request = new HttpRequestMessage(HttpMethod.Post, HttpBaseAddress + resource); request.Content = new StringContent(JsonSerializer.Serialize(body), System.Text.Encoding.UTF8, "application/json"); return request; } public HttpRequestMessage CreatePutRequest(string resource, object? body = null) { var request = new HttpRequestMessage(HttpMethod.Put, HttpBaseAddress + resource); request.Content = new StringContent(JsonSerializer.Serialize(body), System.Text.Encoding.UTF8, "application/json"); return request; } public HttpRequestMessage CreateDeleteRequest(string resource) { return new HttpRequestMessage(HttpMethod.Delete, HttpBaseAddress + resource); } public void SetAccessToken(string token) { AccessToken = token; } public void SetRequestHeaders(HttpHeaders headers) { var cloned = Client.DefaultRequestHeaders.Clone(); foreach (var header in headers) { cloned.Add(header.Key, header.Value); } Client.DefaultRequestHeaders = cloned; } public void AddRequestHeader(string name, params string[] values) { foreach (var value in values) { Client.DefaultRequestHeaders.Add(name, value); } } private class JsonDefaults { public static readonly JsonSerializerOptions DefaultSerializerOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Converters = { new JsonStringEnumConverter() } }; } }