HttpUtils.kt
/*
* This file is part of GNU Taler
* (C) 2024 Taler Systems S.A.
*
* GNU Taler is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3, or (at your option) any later version.
*
* GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
package net.taler.common
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.HttpRedirect
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.HttpTimeoutConfig.Companion.INFINITE_TIMEOUT_MS
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.logging.ANDROID
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.http.HttpMethod
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import okhttp3.ConnectionPool
import okhttp3.Protocol
import java.util.concurrent.TimeUnit
private val clientCache = mutableMapOf<String, HttpClient>()
/** Returns a process-wide shared client. Callers must not close it or install owner-specific callbacks. */
fun getDefaultHttpClient(
withJson: Boolean = true,
followRedirect: Boolean = false,
logging: Boolean = true,
): HttpClient {
val key = "$withJson-$followRedirect-$logging"
return clientCache.getOrPut(key) { createDefaultHttpClient(withJson, followRedirect, logging) }
}
/** Creates an independent client that its owner must close when no longer needed. */
fun createDefaultHttpClient(
withJson: Boolean = true,
followRedirect: Boolean = false,
logging: Boolean = true,
): HttpClient = HttpClient(OkHttp) {
expectSuccess = true
followRedirects = followRedirect
engine {
config {
retryOnConnectionFailure(true)
connectionPool(ConnectionPool(5, 30, TimeUnit.SECONDS))
protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1))
}
}
install(ContentNegotiation) {
if (withJson) {
json(Json {
encodeDefaults = false
ignoreUnknownKeys = true
})
}
}
install(HttpTimeout) {
requestTimeoutMillis = INFINITE_TIMEOUT_MS
socketTimeoutMillis = INFINITE_TIMEOUT_MS
connectTimeoutMillis = INFINITE_TIMEOUT_MS
}
install(HttpRedirect) {
checkHttpMethod = !followRedirect
}
install(Logging) {
logger = Logger.ANDROID
level = if (logging) LogLevel.INFO else LogLevel.NONE
}
}
fun String.toHttpMethod(): HttpMethod? = when(this) {
"GET" -> HttpMethod.Get
"POST" -> HttpMethod.Post
"PUT" -> HttpMethod.Put
"PATCH" -> HttpMethod.Patch
"DELETE" -> HttpMethod.Delete
"HEAD" -> HttpMethod.Head
"OPTIONS" -> HttpMethod.Options
else -> null
}