Как правильно проверять ответ от API на существование сертификата: по статусу кода или по содержимому
Что проверяют
Понимаете ли вы, что статус и тело отвечают на разные вопросы, и умеете ли различить «сертификата нет» и «мы не знаем, есть ли он». Следом почти всегда спрашивают, что вернёт ваш метод при 500 или таймауте.
Как отвечать
Правильный ответ: сначала статус, потом тело, и опора на контракт API, а не на догадки.
- статус-код: говорит, чем закончился запрос. В REST отсутствие ресурса это
404, наличие это200с телом.401и403означают проблему доступа,5xxи таймаут означают, что ответа по существу нет; - тело: говорит, что именно найдено. Его десериализуют в типизированную модель через
ReadFromJsonAsync<CertificateDto>(), а не ищут подстроку. Ошибки по стандарту приходят какProblemDetails(RFC 9457), их тоже читают моделью; - контракт: если API отвечает
200с{ "exists": false }, это его договорённость, и проверять нужно поле. При этом шлюз или прокси тоже умеет отвечать404с HTML-страницей, поэтому404без ожидаемого тела стоит считать ошибкой, а не «сертификата нет».
Метод проверки лучше возвращать не bool, а три исхода: есть, нет, неизвестно. Например enum CertificateStatus { Exists, NotFound, Unknown } или Result-тип. Тогда 500 не превратится в бизнес-решение «сертификата нет, выпускаем новый».
По инфраструктуре: HttpClient через IHttpClientFactory или типизированный клиент, повторы только для временных ошибок (5xx, 408, таймаут) через Microsoft.Extensions.Http.Resilience или Polly, в лог статус, корреляционный id и обрезанное тело. EnsureSuccessStatusCode() бросает HttpRequestException, у которого с .NET 5 есть свойство StatusCode.
Где спотыкаются
Проверяют content.Contains("error"). Сертификат с полем errorCount или описанием со словом error ломает логику, а реальная ошибка в другом формате проходит как успех.
Сводят всё к bool и любой код, кроме 200, считают отсутствием сертификата. Сбой сервера молча превращается в неверное бизнес-решение.
Создают new HttpClient() на каждый запрос. Под нагрузкой это исчерпание сокетов, а статический клиент без PooledConnectionLifetime не замечает смену DNS.