CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-ktor--ktor-client-core

Ktor HTTP Client Core - a multiplatform asynchronous HTTP client library for Kotlin providing comprehensive HTTP request/response handling with plugin architecture.

Pending
Overview
Eval results
Files

plugin-system.mddocs/

Plugin System

Comprehensive plugin architecture with built-in plugins for common functionality and custom plugin creation capabilities.

Capabilities

Core Plugin Interface

Base plugin interface and plugin management system.

/**
 * Core plugin interface for HttpClient
 */
interface HttpClientPlugin<TBuilder : Any, TPlugin : Any> {
    /** Unique key for this plugin type */
    val key: AttributeKey<TPlugin>
    
    /**
     * Prepare plugin configuration
     * @param block Configuration block
     * @returns Configured plugin instance
     */
    fun prepare(block: TBuilder.() -> Unit): TPlugin
    
    /**
     * Install plugin into HttpClient
     * @param plugin Prepared plugin instance
     * @param scope Target HttpClient
     */
    fun install(plugin: TPlugin, scope: HttpClient)
}

/**
 * Create custom client plugin
 * @param name Plugin name
 * @param createConfiguration Configuration factory
 * @param body Plugin implementation
 * @returns HttpClientPlugin instance
 */
fun <TConfig : Any, TPlugin : Any> createClientPlugin(
    name: String,
    createConfiguration: () -> TConfig,
    body: ClientPluginBuilder<TConfig>.() -> TPlugin
): HttpClientPlugin<TConfig, TPlugin>

Built-in Plugins

Core plugins provided by Ktor for common HTTP client functionality.

/**
 * HTTP timeout configuration plugin
 */
object HttpTimeout : HttpClientPlugin<HttpTimeoutConfig, HttpTimeoutConfig> {
    override val key: AttributeKey<HttpTimeoutConfig>
    
    /**
     * Timeout configuration
     */
    class HttpTimeoutConfig {
        /** Request timeout in milliseconds */
        var requestTimeoutMillis: Long? = null
        
        /** Connection timeout in milliseconds */  
        var connectTimeoutMillis: Long? = null
        
        /** Socket timeout in milliseconds */
        var socketTimeoutMillis: Long? = null
    }
}

/**
 * HTTP redirect handling plugin
 */
object HttpRedirect : HttpClientPlugin<HttpRedirectConfig, HttpRedirectConfig> {
    override val key: AttributeKey<HttpRedirectConfig>
    
    /**
     * Redirect configuration
     */
    class HttpRedirectConfig {
        /** Check HTTP method on redirect (default: true) */
        var checkHttpMethod: Boolean = true
        
        /** Allow HTTPS to HTTP downgrade (default: false) */
        var allowHttpsDowngrade: Boolean = false
        
        /** Maximum number of redirects (default: 20) */
        var maxJumps: Int = 20
    }
}

/**
 * Cookie management plugin
 */
object HttpCookies : HttpClientPlugin<HttpCookiesConfig, HttpCookiesConfig> {
    override val key: AttributeKey<HttpCookiesConfig>
    
    /**
     * Cookies configuration
     */
    class HttpCookiesConfig {
        /** Cookie storage implementation */
        var storage: CookiesStorage = AcceptAllCookiesStorage()
    }
}

/**
 * Response validation plugin
 */
object HttpCallValidator : HttpClientPlugin<HttpCallValidatorConfig, HttpCallValidatorConfig> {
    override val key: AttributeKey<HttpCallValidatorConfig>
    
    /**
     * Validation configuration
     */
    class HttpCallValidatorConfig {
        /** Validate response status codes */
        var validateResponse: (suspend (HttpResponse) -> Unit)? = null
        
        /** Handle response exceptions */
        var handleResponseException: (suspend (exception: Throwable) -> Unit)? = null
    }
}

/**
 * Default request configuration plugin
 */
object DefaultRequest : HttpClientPlugin<DefaultRequestConfig, DefaultRequestConfig> {
    override val key: AttributeKey<DefaultRequestConfig>
    
    /**
     * Default request configuration
     */
    class DefaultRequestConfig {
        /** Default request builder configuration */
        var block: HttpRequestBuilder.() -> Unit = {}
    }
}

/**
 * User agent header plugin
 */
object UserAgent : HttpClientPlugin<UserAgentConfig, UserAgentConfig> {
    override val key: AttributeKey<UserAgentConfig>
    
    /**
     * User agent configuration
     */
    class UserAgentConfig {
        /** User agent string */
        var agent: String = ""
    }
}

/**
 * Request retry plugin
 */
object HttpRequestRetry : HttpClientPlugin<HttpRequestRetryConfig, HttpRequestRetryConfig> {
    override val key: AttributeKey<HttpRequestRetryConfig>
    
    /**
     * Retry configuration
     */
    class HttpRequestRetryConfig {
        /** Maximum retry attempts */
        var maxRetryAttempts: Int = 3
        
        /** Retry condition */
        var retryIf: (suspend (request: HttpRequestBuilder, response: HttpResponse) -> Boolean)? = null
        
        /** Delay between retries */
        var delayMillis: (retry: Int) -> Long = { retry -> retry * 1000L }
    }
}

Usage Examples:

val client = HttpClient {
    // Install timeout plugin
    install(HttpTimeout) {
        requestTimeoutMillis = 30000
        connectTimeoutMillis = 10000
        socketTimeoutMillis = 15000
    }
    
    // Install redirect plugin
    install(HttpRedirect) {
        checkHttpMethod = true
        allowHttpsDowngrade = false
        maxJumps = 10
    }
    
    // Install cookies plugin
    install(HttpCookies) {
        storage = AcceptAllCookiesStorage()
    }
    
    // Install response validation
    install(HttpCallValidator) {
        validateResponse { response ->
            when (response.status.value) {
                in 300..399 -> throw RedirectResponseException(response, "Redirects not allowed")
                in 400..499 -> throw ClientRequestException(response, "Client error")
                in 500..599 -> throw ServerResponseException(response, "Server error")
            }
        }
        
        handleResponseException { exception ->
            println("Request failed: ${exception.message}")
        }
    }
    
    // Install default request configuration
    install(DefaultRequest) {
        header("User-Agent", "MyApp/1.0")
        header("Accept", "application/json")
        url("https://api.example.com/")
    }
    
    // Install user agent
    install(UserAgent) {
        agent = "MyApp/1.0 (Kotlin HTTP Client)"
    }
    
    // Install retry plugin
    install(HttpRequestRetry) {
        maxRetryAttempts = 3
        retryIf { _, response ->
            response.status.value >= 500
        }
        delayMillis { retry ->
            retry * 2000L // Exponential backoff
        }
    }
}

Custom Plugin Creation

Create custom plugins using the plugin builder DSL.

/**
 * Plugin builder for creating custom plugins
 */
class ClientPluginBuilder<TConfig : Any> {
    /**
     * Hook into request pipeline phase
     * @param phase Pipeline phase to intercept
     * @param block Interceptor implementation
     */
    fun onRequest(phase: PipelinePhase, block: suspend (HttpRequestBuilder, TConfig) -> Unit)
    
    /**
     * Hook into response pipeline phase
     * @param phase Pipeline phase to intercept  
     * @param block Interceptor implementation
     */
    fun onResponse(phase: PipelinePhase, block: suspend (HttpResponse, TConfig) -> Unit)
    
    /**
     * Hook into call processing
     * @param block Call interceptor implementation
     */
    fun onCall(block: suspend (HttpClientCall, TConfig) -> Unit)
    
    /**
     * Add cleanup logic when client is closed
     * @param block Cleanup implementation
     */
    fun onClose(block: (TConfig) -> Unit)
}

/**
 * Plugin configuration attribute key
 */
class AttributeKey<T>(val name: String) {
    companion object {
        fun <T> create(name: String): AttributeKey<T>
    }
}

Usage Examples:

// Custom logging plugin
val CustomLogging = createClientPlugin("CustomLogging", ::LoggingConfig) {
    val config = pluginConfig
    
    onRequest(HttpRequestPipeline.Before) { request, _ ->
        if (config.logRequests) {
            println("→ ${request.method.value} ${request.url}")
            request.headers.forEach { name, values ->
                println("  $name: ${values.joinToString()}")
            }
        }
    }
    
    onResponse(HttpResponsePipeline.Receive) { response, _ ->
        if (config.logResponses) {
            println("← ${response.status}")
            response.headers.forEach { name, values ->
                println("  $name: ${values.joinToString()}")
            }
        }
    }
    
    onClose { config ->
        if (config.logCleanup) {
            println("Logging plugin closed")
        }
    }
}

data class LoggingConfig(
    var logRequests: Boolean = true,
    var logResponses: Boolean = true,
    var logCleanup: Boolean = false
)

// Use custom plugin
val client = HttpClient {
    install(CustomLogging) {
        logRequests = true
        logResponses = true
        logCleanup = true
    }
}

// Custom authentication plugin
val BearerAuth = createClientPlugin("BearerAuth", ::BearerAuthConfig) {
    val config = pluginConfig
    
    onRequest(HttpRequestPipeline.State) { request, _ ->
        val token = config.tokenProvider()
        if (token != null) {
            request.header("Authorization", "Bearer $token")
        }
    }
    
    onResponse(HttpResponsePipeline.Receive) { response, _ ->
        if (response.status == HttpStatusCode.Unauthorized) {
            config.onUnauthorized?.invoke()
        }
    }
}

data class BearerAuthConfig(
    var tokenProvider: () -> String? = { null },
    var onUnauthorized: (() -> Unit)? = null
)

// Use authentication plugin
val client = HttpClient {
    install(BearerAuth) {
        tokenProvider = { getStoredToken() }
        onUnauthorized = { refreshToken() }
    }
}

Plugin Hooks and Lifecycle

Plugin hooks for intercepting various stages of request/response processing.

/**
 * Common plugin hooks
 */
object CommonHooks {
    /** Before request is sent */
    val BeforeRequest: ClientHook<suspend (HttpRequestBuilder) -> Unit>
    
    /** After response is received */
    val AfterResponse: ClientHook<suspend (HttpResponse) -> Unit>
    
    /** On request exception */
    val OnRequestException: ClientHook<suspend (Throwable) -> Unit>
    
    /** On response exception */
    val OnResponseException: ClientHook<suspend (Throwable) -> Unit>
}

/**
 * Plugin hook interface
 */
interface ClientHook<T> {
    val name: String
    fun install(client: HttpClient, handler: T)
}

/**
 * Plugin instance wrapper
 */
class ClientPluginInstance<TConfig : Any> {
    val config: TConfig
    val plugin: HttpClientPlugin<*, *>
    
    fun close()
}

Usage Examples:

// Using common hooks
val client = HttpClient {
    install("RequestLogger") {
        CommonHooks.BeforeRequest.install(this) { request ->
            println("Sending request to: ${request.url}")
        }
        
        CommonHooks.AfterResponse.install(this) { response ->
            println("Received response: ${response.status}")
        }
        
        CommonHooks.OnRequestException.install(this) { exception ->
            println("Request failed: ${exception.message}")
        }
    }
}

// Advanced plugin with multiple hooks
val AdvancedMetrics = createClientPlugin("AdvancedMetrics", ::MetricsConfig) {
    val config = pluginConfig
    val requestTimes = mutableMapOf<HttpClientCall, Long>()
    
    // Track request start time
    onRequest(HttpRequestPipeline.Before) { request, _ ->
        requestTimes[request.executionContext] = System.currentTimeMillis()
    }
    
    // Calculate and log request duration
    onResponse(HttpResponsePipeline.Receive) { response, _ ->
        val startTime = requestTimes.remove(response.call)
        if (startTime != null) {
            val duration = System.currentTimeMillis() - startTime
            config.onRequestComplete(response.call.request.url.toString(), duration, response.status)
        }
    }
    
    // Handle request failures
    onCall { call, _ ->
        try {
            proceed()
        } catch (e: Exception) {
            val startTime = requestTimes.remove(call)
            if (startTime != null) {
                val duration = System.currentTimeMillis() - startTime
                config.onRequestFailed(call.request.url.toString(), duration, e)
            }
            throw e
        }
    }
}

data class MetricsConfig(
    var onRequestComplete: (url: String, duration: Long, status: HttpStatusCode) -> Unit = { _, _, _ -> },
    var onRequestFailed: (url: String, duration: Long, exception: Throwable) -> Unit = { _, _, _ -> }
)

Plugin Configuration and Management

Managing plugin configurations and accessing installed plugins.

/**
 * Access installed plugin configuration
 * @param plugin Plugin to get configuration for
 * @returns Plugin configuration instance
 */
fun <TBuilder : Any, TPlugin : Any> HttpClient.plugin(
    plugin: HttpClientPlugin<TBuilder, TPlugin>
): TPlugin

/**
 * Check if plugin is installed
 * @param plugin Plugin to check
 * @returns True if plugin is installed
 */
fun HttpClient.isPluginInstalled(plugin: HttpClientPlugin<*, *>): Boolean

/**
 * Get all installed plugins
 * @returns Map of plugin keys to plugin instances
 */
fun HttpClient.installedPlugins(): Map<AttributeKey<*>, Any>

Usage Examples:

val client = HttpClient {
    install(HttpTimeout) {
        requestTimeoutMillis = 30000
    }
    
    install(HttpCookies) {
        storage = AcceptAllCookiesStorage()
    }
}

// Access plugin configuration
val timeoutConfig = client.plugin(HttpTimeout)
println("Request timeout: ${timeoutConfig.requestTimeoutMillis}")

val cookiesConfig = client.plugin(HttpCookies)
val cookieStorage = cookiesConfig.storage

// Check if plugin is installed
if (client.isPluginInstalled(HttpTimeout)) {
    println("Timeout plugin is installed")
}

// Get all installed plugins
val allPlugins = client.installedPlugins()
allPlugins.forEach { (key, instance) ->
    println("Plugin: ${key.name}")
}

Types

Plugin System Types

/**
 * Pipeline phase for request/response processing
 */
class PipelinePhase(val name: String) {
    companion object {
        fun create(name: String): PipelinePhase
    }
}

/**
 * Request pipeline phases
 */
object HttpRequestPipeline {
    val Before: PipelinePhase
    val State: PipelinePhase
    val Transform: PipelinePhase
    val Render: PipelinePhase
    val Send: PipelinePhase
}

/**
 * Response pipeline phases
 */
object HttpResponsePipeline {
    val Receive: PipelinePhase
    val Parse: PipelinePhase
    val Transform: PipelinePhase
}

/**
 * Send pipeline phases
 */
object HttpSendPipeline {
    val Before: PipelinePhase
    val State: PipelinePhase
    val Monitoring: PipelinePhase
    val Engine: PipelinePhase
    val Receive: PipelinePhase
}

/**
 * Attributes collection for plugin data
 */
interface Attributes {
    fun <T : Any> get(key: AttributeKey<T>): T
    fun <T : Any> getOrNull(key: AttributeKey<T>): T?
    fun <T : Any> put(key: AttributeKey<T>, value: T)
    fun <T : Any> remove(key: AttributeKey<T>): T?
    fun <T : Any> computeIfAbsent(key: AttributeKey<T>, block: () -> T): T
    
    fun allKeys(): List<AttributeKey<*>>
    
    companion object {
        fun concurrent(): Attributes
    }
}

Exception Types

/**
 * Plugin installation exception
 */
class PluginInstallationException(
    message: String,
    cause: Throwable? = null
) : Exception(message, cause)

/**
 * Plugin configuration exception
 */
class PluginConfigurationException(
    message: String,
    cause: Throwable? = null
) : Exception(message, cause)

Install with Tessl CLI

npx tessl i tessl/maven-io-ktor--ktor-client-core

docs

client-configuration.md

cookie-management.md

forms-and-uploads.md

http-caching.md

http-requests.md

index.md

plugin-system.md

response-handling.md

server-sent-events.md

websockets.md

tile.json