From 4fb2b87aa119890d315236080a4519e5f3e61969 Mon Sep 17 00:00:00 2001 From: dmolinari Date: Wed, 20 Aug 2025 18:02:17 -0300 Subject: [PATCH] Retry Bancas --- .../Services/ElectoralApiService.cs | 175 +++++++----------- 1 file changed, 69 insertions(+), 106 deletions(-) diff --git a/Elecciones-Web/src/Elecciones.Infrastructure/Services/ElectoralApiService.cs b/Elecciones-Web/src/Elecciones.Infrastructure/Services/ElectoralApiService.cs index 748aef8..0cce3de 100644 --- a/Elecciones-Web/src/Elecciones.Infrastructure/Services/ElectoralApiService.cs +++ b/Elecciones-Web/src/Elecciones.Infrastructure/Services/ElectoralApiService.cs @@ -1,13 +1,12 @@ using Elecciones.Core.DTOs; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; -using Microsoft.Extensions.Logging; +using System.Threading.RateLimiting; using System.Threading.Tasks; -using static Elecciones.Core.DTOs.BancaDto; -//using System.Threading.RateLimiting; namespace Elecciones.Infrastructure.Services; @@ -16,46 +15,40 @@ public class ElectoralApiService : IElectoralApiService private readonly IHttpClientFactory _httpClientFactory; private readonly IConfiguration _configuration; private readonly ILogger _logger; - //private readonly RateLimiter _rateLimiter; + private readonly RateLimiter _rateLimiter; - public ElectoralApiService(IHttpClientFactory httpClientFactory, + public ElectoralApiService( + IHttpClientFactory httpClientFactory, IConfiguration configuration, - ILogger logger) - //RateLimiter rateLimiter) + ILogger logger, + RateLimiter rateLimiter) { _httpClientFactory = httpClientFactory; _configuration = configuration; _logger = logger; - //_rateLimiter = rateLimiter; + _rateLimiter = rateLimiter; } public async Task GetAuthTokenAsync() { - // "Pedir una ficha". Este método ahora devuelve un "lease" (permiso). - // Si no hay fichas, esperará aquí automáticamente hasta que se rellene el cubo. - /* - using RateLimitLease lease = await _rateLimiter.AcquireAsync(1); + using RateLimitLease lease = await _rateLimiter.AcquireAsync(1); + if (lease.IsAcquired) + { + var client = _httpClientFactory.CreateClient("ElectoralApiClient"); + var username = _configuration["ElectoralApi:Username"]; + var password = _configuration["ElectoralApi:Password"]; + if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) return null; - // Si se nos concede el permiso para proceder... - if (lease.IsAcquired) - {*/ - var client = _httpClientFactory.CreateClient("ElectoralApiClient"); - var username = _configuration["ElectoralApi:Username"]; - var password = _configuration["ElectoralApi:Password"]; - if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) return null; + var request = new HttpRequestMessage(HttpMethod.Get, "/api/createtoken"); + request.Headers.Add("username", username); + request.Headers.Add("password", password); - var request = new HttpRequestMessage(HttpMethod.Get, "/api/createtoken"); - request.Headers.Add("username", username); - request.Headers.Add("password", password); - - var response = await client.SendAsync(request); - if (!response.IsSuccessStatusCode) return null; - - // Ahora esto es válido, porque el método devuelve Task - return await response.Content.ReadFromJsonAsync(); - /* } - // Si no se pudo obtener un permiso (ej. la cola está llena), devolvemos null. - return null;*/ + var response = await client.SendAsync(request); + if (!response.IsSuccessStatusCode) return null; + + return await response.Content.ReadFromJsonAsync(); + } + return null; } public async Task GetCatalogoAmbitosAsync(string authToken, int categoriaId) @@ -131,17 +124,11 @@ public class ElectoralApiService : IElectoralApiService public async Task GetBancasAsync(string authToken, string distritoId, string? seccionProvincialId, int categoriaId) { - // "Pedir una ficha". Este método ahora devuelve un "lease" (permiso). - // Si no hay fichas, esperará aquí automáticamente hasta que se rellene el cubo. - /* - using RateLimitLease lease = await _rateLimiter.AcquireAsync(1); + using RateLimitLease lease = await _rateLimiter.AcquireAsync(1); + if (!lease.IsAcquired) return null; - // Si se nos concede el permiso para proceder... - if (lease.IsAcquired) - {*/ var client = _httpClientFactory.CreateClient("ElectoralApiClient"); var requestUri = $"/api/resultados/getBancas?distritoId={distritoId}&categoriaId={categoriaId}"; - if (!string.IsNullOrEmpty(seccionProvincialId)) { requestUri += $"&seccionProvincialId={seccionProvincialId}"; @@ -149,45 +136,35 @@ public class ElectoralApiService : IElectoralApiService var request = new HttpRequestMessage(HttpMethod.Get, requestUri); request.Headers.Add("Authorization", $"Bearer {authToken}"); - - HttpResponseMessage response; + try { - response = await client.SendAsync(request); + var response = await client.SendAsync(request); + + // --- CORRECCIÓN FINAL --- + // Eliminamos la comprobación de ContentLength. Confiamos en el try-catch. + if (response.IsSuccessStatusCode) + { + try + { + return await response.Content.ReadFromJsonAsync(); + } + catch (JsonException ex) + { + // Esto se activará si el cuerpo está vacío o no es un JSON válido. + _logger.LogWarning(ex, "La API devolvió una respuesta no-JSON para getBancas. URI: {uri}", requestUri); + return null; + } + } + + _logger.LogWarning("La API devolvió un código de error {statusCode} para getBancas. URI: {uri}", response.StatusCode, requestUri); + return null; } catch (Exception ex) { - // Captura errores de red (ej. la API se cae momentáneamente) - _logger.LogError(ex, "La petición HTTP a getBancas falló. URI: {requestUri}", requestUri); + _logger.LogError(ex, "La petición HTTP a getBancas falló. URI: {uri}", requestUri); return null; } - - // Comprobamos que la respuesta fue exitosa Y que contiene datos antes de intentar leerla. - if (response.IsSuccessStatusCode && response.Content?.Headers.ContentLength > 0) - { - try - { - // Solo si hay contenido, intentamos deserializar. - return await response.Content.ReadFromJsonAsync(); - } - catch (JsonException ex) - { - // Si el contenido no es un JSON válido, lo registramos y devolvemos null. - _logger.LogWarning(ex, "La API devolvió una respuesta no-JSON para getBancas. URI: {requestUri}, Status: {statusCode}", requestUri, response.StatusCode); - return null; - } - } - else if (!response.IsSuccessStatusCode) - { - // Si la API devolvió un error HTTP, lo registramos. - _logger.LogWarning("La API devolvió un código de error {statusCode} para getBancas. URI: {requestUri}", response.StatusCode, requestUri); - } - - // Si la respuesta fue 200 OK pero con cuerpo vacío, o si fue un error HTTP, devolvemos null. - return null; - /* } - // Si no se pudo obtener un permiso (ej. la cola está llena), devolvemos null. - return null;*/ } public async Task?> GetTelegramasTotalizadosAsync(string authToken, string distritoId, string seccionId, int? categoriaId = null) @@ -221,54 +198,40 @@ public class ElectoralApiService : IElectoralApiService public async Task GetTelegramaFileAsync(string authToken, string mesaId) { - // "Pedir una ficha". Este método ahora devuelve un "lease" (permiso). - // Si no hay fichas, esperará aquí automáticamente hasta que se rellene el cubo. - /* - using RateLimitLease lease = await _rateLimiter.AcquireAsync(1); + using RateLimitLease lease = await _rateLimiter.AcquireAsync(1); + if (!lease.IsAcquired) return null; - // Si se nos concede el permiso para proceder... - if (lease.IsAcquired) - {*/ var client = _httpClientFactory.CreateClient("ElectoralApiClient"); var requestUri = $"/api/resultados/getFile?mesaId={mesaId}"; var request = new HttpRequestMessage(HttpMethod.Get, requestUri); request.Headers.Add("Authorization", $"Bearer {authToken}"); - - HttpResponseMessage response; + try { - response = await client.SendAsync(request); + var response = await client.SendAsync(request); + + // --- APLICAMOS LA MISMA CORRECCIÓN AQUÍ POR CONSISTENCIA --- + if (response.IsSuccessStatusCode) + { + try + { + return await response.Content.ReadFromJsonAsync(); + } + catch (JsonException ex) + { + _logger.LogWarning(ex, "La API devolvió una respuesta no-JSON para getFile para la mesa {mesaId}", mesaId); + return null; + } + } + + _logger.LogWarning("La API devolvió un código de error {statusCode} para la mesa {mesaId}", response.StatusCode, mesaId); + return null; } catch (Exception ex) { _logger.LogError(ex, "La petición HTTP a getFile falló para la mesa {mesaId}", mesaId); return null; } - - if (response.IsSuccessStatusCode && response.Content?.Headers.ContentLength > 0) - { - try - { - return await response.Content.ReadFromJsonAsync(); - } - catch (JsonException ex) - { - // Si la deserialización falla, ahora lo sabremos exactamente. - _logger.LogWarning(ex, "La API devolvió una respuesta no-JSON para la mesa {mesaId}. Status: {statusCode}", mesaId, response.StatusCode); - return null; - } - } - else if (!response.IsSuccessStatusCode) - { - _logger.LogWarning("La API devolvió un código de error {statusCode} para la mesa {mesaId}", response.StatusCode, mesaId); - } - // Si la respuesta fue exitosa pero sin contenido, no es necesario loguearlo como un error, - // simplemente devolvemos null y el Worker lo ignorará. - - return null; - /* } - // Si no se pudo obtener un permiso (ej. la cola está llena), devolvemos null. - return null;*/ } public async Task GetResumenAsync(string authToken, string distritoId)