Выполнение запросов и обработка ответов
Клиент выполняет методы синхронно. Вызов блокирует текущий поток до получения
ответа, истечения тайм-аута или ошибки транспорта. Возвращать retrofit2.Call<T>
из интерфейса нельзя: SimpleCallAdapterFactory завершит создание метода с
BGException.
Порядок обработки
Вызов проходит через адаптеры в таком порядке:
метод API
-> Rate Limiter
-> Circuit Breaker
-> SimpleCallAdapter: Call.execute()
-> OkHttp
Rate Limiter является внешним декоратором, поэтому разрешение расходуется и тогда, когда Circuit Breaker уже открыт и не отправляет HTTP-запрос.
Application-interceptor OkHttp выполняются в порядке регистрации:
-
TimingInterceptor— измерение и событие телеметрии; -
HttpLoggingInterceptor— HTTP-лог; -
TaggingInterceptor— перенос методных аннотаций в теги запроса; -
interceptor провайдера авторизации;
-
дополнительные interceptors, зарегистрированные приложением.
Из-за такого порядка HTTP-логирование выполняется до добавления заголовков авторизации и дополнительных перехватчиков. Это уменьшает риск вывода токена, но дополнительный interceptor всё равно может записать его самостоятельно.
Преобразование тела
Основной ObjectMapper выбирается параметром @Api.responseType:
-
JSON— обычныйObjectMapper; -
XML—XmlMapper.
Оба маппера подключают Kotlin module со следующими настройками:
-
nullпреобразуется в пустую коллекцию или map; -
nullразрешено подменять значением Kotlin-параметра по умолчанию; -
неизвестные поля ответа игнорируются;
-
включены void-valued properties.
Если интерфейс помечен @ConverterFactory, собственная фабрика добавляется
перед Jackson и может обработать запрос или ответ первой. Подробности приведены в
разделе расширения.
Успешный ответ и ошибки
Для HTTP 2xx возвращается десериализованное тело. Пустое тело даёт null, даже
если метод интерфейса объявлен с non-null типом Kotlin; это нужно учитывать в
контракте внешнего API.
Ошибки обрабатываются так:
| Ситуация | Результат |
|---|---|
|
Логируется предупреждение и выбрасывается |
HTTP не 2xx без тела ошибки |
|
HTTP не 2xx, стандартный |
|
HTTP не 2xx, собственный |
Тело всегда читается JSON-маппером; в сообщение попадает |
Собственный |
Логируется предупреждение, затем |
Rate Limiter не дал разрешение |
|
Circuit Breaker открыт |
|
Если Circuit Breaker включён, любое исключение выполненного действия, кроме
CallNotPermittedException, дополнительно оборачивается в BGException.
Resilience4j при этом учитывает только TimeoutException, IOException,
ApiException и ResourceNotFoundException; BGException игнорируется в
статистике. При выключенном Circuit Breaker исходное исключение проходит без
этой обёртки.
Модель ошибки
Собственная модель должна наследовать ru.ufanet.retrofit.Response и отдавать
пользовательский текст через errorMessage:
@Api(
name = "catalog",
errorType = CatalogError::class
)
interface CatalogApi
Даже у XML-клиента ошибка читается JSON-маппером. Если внешний сервис возвращает
XML-ошибку, текущий встроенный обработчик не сможет преобразовать её в
errorType и покажет исходное тело.
SOAP 1.1
Встроенный SoapConverterFactory оборачивает только методы с @SoapRequest.
Интерфейс настраивается так:
import retrofit2.http.Body
import retrofit2.http.POST
import ru.bgcrm.util.api.annotation.Api
import ru.bgcrm.util.api.converter.ConverterFactory
import ru.bgcrm.util.api.converter.soap.SoapConverterFactory
import ru.bgcrm.util.api.tag.SoapRequest
@Api(name = "billing-soap", responseType = Api.ResponseType.XML)
@ConverterFactory(
factoryClass = SoapConverterFactory.SoapConverterFactoryCreator::class
)
interface BillingSoapApi {
@POST("services/Billing")
@SoapRequest(
namespace = "urn:billing",
method = "getBalance"
)
fun getBalance(@Body request: BalanceRequest): BalanceResponse
}
Запрос получает SOAP 1.1 envelope, пространство имён записывается с префиксом
bg, а полезная нагрузка — в bg:<method>. Ответ ищется по ключу
<method>Response, после чего возвращается вложенное поле return.
Конвертер не реализует SOAP Fault, WS-Security, несколько значений return или
поиск ответа по namespace. Для такого протокола нужен собственный конвертер либо
специализированный клиент.
Телеметрия
TimingInterceptor публикует RetrofitRequestEndEvent после каждого завершения,
включая транспортную ошибку. Событие содержит:
-
apiName—@Api.name; -
method— HTTP-метод; -
endpoint— имя Java/Kotlin-метода интерфейса; -
status— HTTP-код илиfailure; -
duration— время в миллисекундах.
TelemetryListener преобразует события в метрики
http_retrofit_request_count_total и
http_retrofit_request_duration_milliseconds с теми же
тегами. Экспортируемые Prometheus-имена и назначение тегов приведены в
каталоге телеметрии.
JMX
Пул соединений, Dispatcher, Rate Limiter и Circuit Breaker публикуются как MBean с именами вида:
BGCRM:type=<Component>,name=api-<Api.name>,description=<api.name или Api.name>
Состав атрибутов и особенности регистрации описаны в каталоге MBean.