using BS.Shared.Core; using BS.Shared.DataContracts; using BS.Shared.Exceptions; using BS.Shared.Interface; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Diagnostics.Eventing.Reader; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Security; using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; namespace BeWo.ServiceUtils.ApiFacade { public class HttpClientFacade { public HttpClientFacade(string base_url, string api_key, bool directConntect = true) { if (string.IsNullOrEmpty(base_url)) throw new ArgumentNullException("HttpClient: base_url is missing"); if (string.IsNullOrWhiteSpace(api_key)) throw new ArgumentNullException("HttpClient: token is missing"); Base_Url = base_url; SetToken(api_key); DirectConnect = directConntect; } public bool DirectConnect { get; private set; } public string Base_Url { get; private set; } public HttpMethod Method { get; set; } = HttpMethod.Get; public Dictionary Headers { get; set; } = new Dictionary(); public Dictionary Parameters { get; set; } = new Dictionary(); public string ContentType { get; set; } = "application/x-www-form-urlencoded"; public Encoding Encoding { get; set; } = Encoding.UTF8; public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30); /// /// Führt einen Request aus, erwartet einen komplexen Datentyp: Schaue Verweise für Beispiele. /// /// /// /// /// /// public virtual async Task> GetAnonymousTypeAsync(string method, T anonymousType, IApiErrorExtractor extractor) { var http_response = await getResponseAsync(method).ConfigureAwait(false); if (!http_response.IsSuccessStatusCode) { return ApiResponse.FailureResponse(AppError.ApiResponseFailed, http_response.ReasonPhrase, (int)http_response.StatusCode); } var json = await http_response.Content.ReadAsStringAsync().ConfigureAwait(false); return parse(() => JsonConvert.DeserializeAnonymousType(json, anonymousType), json, extractor); } /// /// Führt einen Request aus, erwartet einen einfachen Datentyp wie: string, int, decimal, byte[] - /// Liste kann gerne erweitert werden /// /// /// /// /// /// public virtual async Task> GetAsync(string method, IApiErrorExtractor extractor) { var http_response = await getResponseAsync(method).ConfigureAwait(false); if (!http_response.IsSuccessStatusCode) { return ApiResponse.FailureResponse(AppError.ApiResponseFailed, http_response.ReasonPhrase, (int)http_response.StatusCode); } var content = await http_response.Content.ReadAsStringAsync().ConfigureAwait(false); object result; var type = typeof(T); if (type == typeof(string)) { result = content; } else if (type == typeof(int)) { result = int.Parse(content); } else if (type == typeof(decimal)) { result = decimal.Parse(content); } else if (type == typeof(byte[])) { result = await http_response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); } else { throw new BeWoInvalidOperationException(AppError.UnsupportedType(typeof(T))); } return parse(() => (T)result, content, extractor); } private static bool ServerCertificateCustomValidation(HttpRequestMessage requestMessage, X509Certificate2 certificate, X509Chain chain, SslPolicyErrors sslErrors) { // It is possible to inspect the certificate provided by the server. Console.WriteLine($"Requested URI: {requestMessage.RequestUri}"); Console.WriteLine($"Effective date: {certificate?.GetEffectiveDateString()}"); Console.WriteLine($"Exp date: {certificate?.GetExpirationDateString()}"); Console.WriteLine($"Issuer: {certificate?.Issuer}"); Console.WriteLine($"Subject: {certificate?.Subject}"); // Based on the custom logic it is possible to decide whether the client considers certificate valid or not Console.WriteLine($"Errors: {sslErrors}"); return sslErrors == SslPolicyErrors.None; } private protected virtual async Task getResponseAsync(string method) { var handler = new HttpClientHandler { ServerCertificateCustomValidationCallback = ServerCertificateCustomValidation, SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, }; using (var client = new HttpClient(handler) { Timeout = Timeout }) { var requestUri = BS.Shared.Core.Utilities.WebUtils.CombineUrl(Base_Url, method); if (Method == HttpMethod.Get && Parameters.Count > 0) { var query = string.Join("&", Parameters.Select(p => $"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value)}")); requestUri += (Base_Url.Contains("?") ? "&" : "?") + query; } using (var request = new HttpRequestMessage(Method, requestUri)) { await checkForDirect(request).ConfigureAwait(false); // Add headers foreach (var header in Headers) request.Headers.TryAddWithoutValidation(header.Key, header.Value); // Add body if POST or PUT if (Method == HttpMethod.Post || Method == HttpMethod.Put) { var content = new FormUrlEncodedContent(Parameters); var bytes = await content.ReadAsByteArrayAsync().ConfigureAwait(false); request.Content = new ByteArrayContent(bytes); request.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(ContentType); } return await client.SendAsync(request).ConfigureAwait(false); } } } private protected async Task checkForDirect(HttpRequestMessage request) { if (!DirectConnect || request.RequestUri is null) return; var host = request.RequestUri.DnsSafeHost; var isSslSession = request.RequestUri.ToString().StartsWith("https://"); try { // DNS-Auflösung für den Hostnamen durchführen var ipAddresses = await Dns.GetHostAddressesAsync(host).ConfigureAwait(false); // Erste verfügbare IP-Adresse verwenden (IPv4 bevorzugen) var targetIp = ipAddresses.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork) ?? ipAddresses.FirstOrDefault(); if (targetIp != null) { request.RequestUri = new Uri($"{(isSslSession ? "https://" : "http://")}{targetIp}{request.RequestUri.PathAndQuery}"); request.Headers.Host = host; } } catch (Exception ex) { // Fallback: Original URI beibehalten bei DNS-Fehlern // Optional: Logging des Fehlers Console.WriteLine($"DNS resolution failed for {host}: {ex.Message}"); } } private void SetToken(string api_key) { var key = nameof(HttpRequestHeader.Authorization); var token = $"Bearer {api_key}"; if (Headers.ContainsKey(key)) { Headers[key] = token; } else { Headers.Add(key, token); } } private ApiResponse parse(Func func, string json, IApiErrorExtractor errorExtractor) { try { // Versuch, die erwartete Struktur zu deserialisieren var data = func(); return ApiResponse.SuccessResponse(data); } catch (JsonException) { // Falls normale Deserialisierung fehlschlägt, versuche Fehler zu extrahieren return ApiResponse.FailureResponse(AppError.JsonParsingError, errorExtractor.ExtractError(json).ToList()); } } } }