moko-network
moko-network
Библиотека moko-network - позволяет генерировать сущности и API классы из OpenAPI (Swagger) файлов.
moko-network-errors
Библиотека moko-network
содержит внутри себя модуль moko-network-errors
- интеграцию с moko-errors
, чтобы удобно обрабатывать ошибки сети.
Для начала, ознакомьтесь с этим модулем по README.
Для использования этого модуля, проделайте следующие действия:
- Подключить его в project.build.gradle:
commonMainApi("dev.icerock.moko:network-errors:$mokoNetworkVersion")
- Вызвать метод для регистрации основных ошибок сети:
ExceptionMappersStorage.registerAllNetworkMappers()
, его реализацию можете посмотреть тут.- Если вы хотите изменить текст при основных ошибках сети - переопределите параметр
errorsTexts
методаregisterAllNetworkMappers
. - Если вы хотите добавить обработку другого класса ошибок, используйте
ExceptionMappersStorage.register
- метод изmoko-errors
.
- Если вы хотите изменить текст при основных ошибках сети - переопределите параметр
- Используйте
exceptionHandler
для автоматической обработки ошибок сети:viewModelScope.launch {
exceptionHandler.handle {
api.mareTestRequest()
// ...
}.execute()
}
Features
Библиотека moko-network
содержит в себе фичи - классы, реализующие интерфейс HttpClientFeature
из Ktor.
В версии Ktor 2.0.0
интерфейс HttpClientFeature
переименовали в HttpClientPlugin
.
Ktor
содержит уже готовые плагины, вот, например, для чего их можно использовать:
- Cache - включить кеширование для каждого запроса, чтобы они отрабатывали быстрее
- DefaultRequest добавлять для всех запросов какие-нибудь хидеры по умолчанию
- Logging - плагин для логгирования запросов
- BodyProgress - плагин для получения
observable
прогресса загрузки и скачивания - HttpTimeout - плагин для настройки таймаутов
С полным списком плагинов, доступных в Ktor вы можете ознакомиться по ссылке.
Также, примеры использования стандартных плагинов можно посмотреть в статье Kotlin Multiplatform Mobile: Intercepting Network Request and Response.
Плагин выполняет свою задачу, как правило, для каждого запроса или ответа сервера. Чтобы понять, что делает каждый плагин, смотрите реализацию метода
handle
, для плагинов из Ktorinstall
вcompanion object
, для плагинов изmoko-network
Подключение
Пример создания httpClient
, в котором происходит подключение и настройка плагинов.
Теперь рассмотрим те плагины, которые есть в moko-network
.
ExceptionFeature
Эта фича просто кидает ошибку, если status
ответа сервера неудачный.
override fun install(feature: ExceptionFeature, scope: HttpClient) {
scope.responsePipeline.intercept(HttpResponsePipeline.Receive) { (_, body) ->
if (body !is ByteReadChannel) return@intercept
val response = context.response
if (!response.status.isSuccess()) {
val packet = body.readRemaining()
val responseString = packet.readText(charset = Charset.forName("UTF-8"))
throw feature.exceptionFactory.createException(
request = context.request,
response = context.response,
responseBody = responseString
)
}
proceedWith(subject)
}
}
LanguageFeature
Эта фича позволяет добавить язык к каждому запросу, чтобы уведомить сервер, на каком языке мы хотим получить ответ.
override fun install(feature: LanguageFeature, scope: HttpClient) {
scope.requestPipeline.intercept(HttpRequestPipeline.State) {
feature.languageProvider.getLanguageCode()?.apply {
context.header(feature.languageHeaderName, this)
}
}
}
TokenFeature
Эта фича к каждому запросу добавляет токен, например для авторизации, по ключу, которое вы укажите в tokenHeaderName
, при настройке фичи. (обычно - authorization)
Для использования фичи необходимо реализовать метод получения токена - getToken()
.
override fun install(feature: TokenFeature, scope: HttpClient) {
scope.requestPipeline.intercept(HttpRequestPipeline.State) {
feature.tokenProvider.getToken()?.apply {
context.headers.remove(feature.tokenHeaderName)
context.header(feature.tokenHeaderName, this)
}
}
}
RefreshTokenFeature
Бывают ситуации, когда у токена есть время жизни, по истечении которого токен становится недействителен. В этом случае необходимо как-то его обновить.
За правила обновления и сохранения нового токена отвечает метод feature.updateTokenHandler.invoke()
.
Этот блок кода отвечает за создание реквеста, подставления туда текущего использующегося токена и выполнения запроса.
val requestBuilder = HttpRequestBuilder().takeFrom(context.request)
val result: HttpResponse = context.client!!.request(requestBuilder)
proceedWith(result)
Что может произойти?
Отправили запрос - получили ответ - 401 ошибка авторизации. После этого мы обновили токен и повторили запрос - все ок.
Но, может получиться так, что мы успели отправить несколько запросов с неправильным токеном, и каждому из них придет ответ - ошибка авторизации.
Первое, что нам нужно сделать в этом случае - проверить, отличается ли тот токен, который мы отправили от того, который находится у нас в хранилище. За эту проверку отвечает метод feature.isCredentialsActual()
.
Если токены отличаются, значит какой-то запрос до нас его уже обновил, и нам нужно просто повторно отправить наш запрос, но уже с новым токеном.
if (!feature.isCredentialsActual(context.request)) {
refreshTokenHttpFeatureMutex.unlock()
val requestBuilder = HttpRequestBuilder().takeFrom(context.request)
val result: HttpResponse = context.client!!.request(requestBuilder)
proceedWith(result)
return@intercept
}
В случае, если мы получили ошибку 401, но токен который мы отправили не отличается от того, который находится у нас в хранилище - просто обновляем токен методом feature.updateTokenHandler.invoke()
.
Если обновление токена прошло успешно - повторяем запрос с новым токеном. Если обновить не удалось - отправляем результат дальше, чтобы показать проблему юзеру.
if (feature.updateTokenHandler.invoke()) {
// Если обновление токена прошел успешно, пробуем повторить запрос
refreshTokenHttpFeatureMutex.unlock()
val requestBuilder = HttpRequestBuilder().takeFrom(context.request)
val result: HttpResponse = context.client!!.request(requestBuilder)
proceedWith(result)
} else {
// Если не удалось обновить токен -
refreshTokenHttpFeatureMutex.unlock()
proceedWith(subject)
}