All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.io.github.jan.supabase.network.KtorSupabaseHttpClient.kt Maven / Gradle / Ivy

There is a newer version: 3.0.2
Show newest version
@file:Suppress("UndocumentedPublicFunction")
package io.github.jan.supabase.network

import io.github.jan.supabase.BuildConfig
import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.annotations.SupabaseInternal
import io.github.jan.supabase.exceptions.HttpRequestException
import io.github.jan.supabase.logging.d
import io.github.jan.supabase.logging.e
import io.github.jan.supabase.supabaseJson
import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
import io.ktor.client.engine.HttpClientEngine
import io.ktor.client.plugins.DefaultRequest
import io.ktor.client.plugins.HttpRequestTimeoutException
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.headers
import io.ktor.client.request.prepareRequest
import io.ktor.client.request.request
import io.ktor.client.request.url
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.HttpStatement
import io.ktor.http.encodedPath
import io.ktor.serialization.kotlinx.json.json
import kotlin.coroutines.cancellation.CancellationException
import kotlin.time.Duration.Companion.milliseconds

private const val HTTPS_PORT = 443

/**
 * A function that can be used to override the default request configuration
 */
typealias HttpRequestOverride = HttpRequestBuilder.() -> Unit

/**
 * A [SupabaseHttpClient] that uses ktor to send requests
 */
@OptIn(SupabaseInternal::class)
class KtorSupabaseHttpClient @SupabaseInternal constructor(
    private val supabaseKey: String,
    modifiers: List.() -> Unit> = listOf(),
    private val requestTimeout: Long,
    engine: HttpClientEngine? = null
): SupabaseHttpClient() {

    @SupabaseInternal
    val httpClient =
        if(engine != null) HttpClient(engine) { applyDefaultConfiguration(modifiers) }
        else HttpClient { applyDefaultConfiguration(modifiers) }

    override suspend fun request(url: String, builder: HttpRequestBuilder.() -> Unit): HttpResponse {
        val request = HttpRequestBuilder().apply {
            url(url)
            builder()
        }
        val endPoint = request.url.encodedPath
        SupabaseClient.LOGGER.d { "Starting ${request.method.value} request to endpoint $endPoint" }

        val response = try {
            httpClient.request(url, builder)
        } catch(e: HttpRequestTimeoutException) {
            SupabaseClient.LOGGER.e { "${request.method.value} request to endpoint $endPoint timed out after $requestTimeout ms" }
            throw e
        } catch(e: CancellationException) {
            SupabaseClient.LOGGER.e { "${request.method.value} request to endpoint $endPoint was cancelled"}
            throw e
        } catch(e: Exception) {
            SupabaseClient.LOGGER.e(e) { "${request.method.value} request to endpoint $endPoint failed with exception ${e.message}" }
            throw HttpRequestException(e.message ?: "", request)
        }
        val responseTime = (response.responseTime.timestamp - response.requestTime.timestamp).milliseconds
        SupabaseClient.LOGGER.d { "${request.method.value} request to endpoint $endPoint successfully finished in $responseTime" }
        return response
    }

    override suspend fun prepareRequest(
        url: String,
        builder: HttpRequestBuilder.() -> Unit
    ): HttpStatement {
        val request = HttpRequestBuilder().apply {
            url(url)
            builder()
        }
        val response = try {
            httpClient.prepareRequest(url, builder)
        } catch(e: HttpRequestTimeoutException) {
            SupabaseClient.LOGGER.e { "Request timed out after $requestTimeout ms on url $url" }
            throw e
        } catch(e: CancellationException) {
            SupabaseClient.LOGGER.e { "Request was cancelled on url $url" }
            throw e
        } catch(e: Exception) {
            SupabaseClient.LOGGER.e(e) { "Request failed with ${e.message} on url $url" }
            throw HttpRequestException(e.message ?: "", request)
        }
        return response
    }

    fun close() = httpClient.close()

    private fun HttpClientConfig<*>.applyDefaultConfiguration(modifiers: List.() -> Unit>) {
        install(DefaultRequest) {
            headers {
                if(supabaseKey.isNotBlank()) {
                    append("apikey", supabaseKey)
                }
                append("X-Client-Info", "supabase-kt/${BuildConfig.PROJECT_VERSION}")
            }
            port = HTTPS_PORT
        }
        install(ContentNegotiation) {
            json(supabaseJson)
        }
        install(HttpTimeout) {
            requestTimeoutMillis = requestTimeout
        }
        modifiers.forEach { it.invoke(this) }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy