CtrlK
BlogDocsLog inGet started
Tessl Logo

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

Ktor HTTP client core library providing asynchronous HTTP client capabilities for Kotlin multiplatform applications

Pending
Overview
Eval results
Files

plugin-system.mddocs/

Plugin System

Extensible plugin architecture for adding cross-cutting concerns like authentication, logging, caching, content negotiation, and custom request/response processing to the HTTP client.

Capabilities

HttpClientPlugin Interface

Core plugin interface for extending client functionality.

/**
 * Interface for HTTP client plugins
 * @param TConfig - Type of plugin configuration
 * @param TPlugin - Type of plugin instance
 */
interface HttpClientPlugin<out TConfig : Any, TPlugin : Any> {
    /** Unique key for the plugin */
    val key: AttributeKey<TPlugin>
    
    /** Prepare plugin instance from configuration */
    fun prepare(block: TConfig.() -> Unit): TPlugin
    
    /** Install plugin into HTTP client */
    fun install(plugin: TPlugin, scope: HttpClient)
}

Plugin Creation

Create custom plugins using the plugin builder DSL.

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

/**
 * Create a plugin with default configuration
 */
fun <TPlugin : Any> createClientPlugin(
    name: String,
    body: ClientPluginBuilder<Unit>.() -> TPlugin
): HttpClientPlugin<Unit, TPlugin>

/**
 * Plugin builder for creating custom plugins
 */
class ClientPluginBuilder<TConfig : Any> {
    /** Plugin configuration */
    val pluginConfig: TConfig
    
    /** Setup hook for plugin initialization */
    fun onSetup(block: suspend () -> Unit)
    
    /** Hook for transforming requests */
    fun transformRequest(block: suspend (HttpRequestBuilder) -> Unit)
    
    /** Hook for transforming responses */
    fun transformResponse(block: suspend (HttpResponse) -> Unit)
    
    /** Hook for request processing */
    fun onRequest(block: suspend (HttpRequestBuilder, content: OutgoingContent) -> Unit)
    
    /** Hook for response processing */
    fun onResponse(block: suspend (HttpResponse) -> Unit)
    
    /** Hook for call completion */
    fun onClose(block: suspend () -> Unit)
}

Usage Examples:

// Simple logging plugin
val RequestLoggingPlugin = createClientPlugin("RequestLogging") {
    onRequest { request, _ ->
        println("Making request to: ${request.url}")
    }
    
    onResponse { response ->
        println("Received response: ${response.status}")
    }
}

// Plugin with configuration
data class RetryConfig(
    var maxRetries: Int = 3,
    var delayMillis: Long = 1000
)

val RetryPlugin = createClientPlugin("Retry", ::RetryConfig) { config ->
    onRequest { request, content ->
        repeat(config.maxRetries) { attempt ->
            try {
                // Attempt request
                return@onRequest
            } catch (e: Exception) {
                if (attempt == config.maxRetries - 1) throw e
                delay(config.delayMillis)
            }
        }
    }
}

// Install plugins
val client = HttpClient {
    install(RequestLoggingPlugin)
    install(RetryPlugin) {
        maxRetries = 5
        delayMillis = 2000
    }
}

Built-in Plugins

Core plugins provided by Ktor.

/**
 * Default request configuration plugin
 */
object DefaultRequest : HttpClientPlugin<DefaultRequest.Config, DefaultRequest> {
    class Config {
        var host: String? = null
        var port: Int? = null
        val headers: HeadersBuilder = HeadersBuilder()
        val url: URLBuilder = URLBuilder()
    }
}

/**
 * HTTP redirect handling plugin
 */
object HttpRedirect : HttpClientPlugin<HttpRedirect.Config, HttpRedirect> {
    class Config {
        var checkHttpMethod: Boolean = true
        var allowHttpsRedirect: Boolean = false
        var maxJumps: Int = 20
    }
}

/**
 * Request retry plugin
 */
object HttpRequestRetry : HttpClientPlugin<HttpRequestRetry.Config, HttpRequestRetry> {
    class Config {
        var maxRetries: Int = 0
        var retryOnServerErrors: Boolean = true
        var retryOnTimeout: Boolean = true
        var exponentialDelay: Boolean = false
        var constantDelay: Duration? = null
    }
}

/**
 * HTTP timeout configuration plugin
 */
object HttpTimeout : HttpClientPlugin<HttpTimeout.Config, HttpTimeout> {
    class Config {
        var requestTimeoutMillis: Long? = null
        var connectTimeoutMillis: Long? = null
        var socketTimeoutMillis: Long? = null
    }
}

/**
 * User-Agent header plugin
 */
object UserAgent : HttpClientPlugin<UserAgent.Config, UserAgent> {
    class Config {
        var agent: String? = null
    }
}

/**
 * Response observer plugin
 */
object ResponseObserver : HttpClientPlugin<ResponseObserver.Config, ResponseObserver> {
    class Config {
        val responseHandlers: MutableList<suspend (HttpResponse) -> Unit> = mutableListOf()
    }
}

Usage Examples:

val client = HttpClient {
    // Configure default request parameters
    install(DefaultRequest) {
        host = "api.example.com"
        port = 443
        header("User-Agent", "MyApp/1.0")
        url {
            protocol = URLProtocol.HTTPS
            path("v1/")
        }
    }
    
    // Configure redirects
    install(HttpRedirect) {
        checkHttpMethod = false
        allowHttpsRedirect = true
        maxJumps = 10
    }
    
    // Configure retries
    install(HttpRequestRetry) {
        maxRetries = 3
        retryOnServerErrors = true
        exponentialDelay = true
    }
    
    // Configure timeouts
    install(HttpTimeout) {
        requestTimeoutMillis = 30000
        connectTimeoutMillis = 5000
        socketTimeoutMillis = 10000
    }
    
    // Set user agent
    install(UserAgent) {
        agent = "MyApp/2.0 (Kotlin)"
    }
    
    // Observe responses
    install(ResponseObserver) {
        onResponse { response ->
            if (!response.status.isSuccess()) {
                println("Request failed: ${response.status}")
            }
        }
    }
}

Plugin Hooks

Available hooks for plugin development.

/**
 * Setup hook - called during plugin installation
 */
class SetupHook {
    suspend fun proceed()
}

/**
 * Transform request hook - modify outgoing requests
 */
class TransformRequestHook {
    suspend fun transformRequest(transform: suspend (HttpRequestBuilder) -> Unit)
}

/**
 * Transform response hook - modify incoming responses
 */
class TransformResponseHook {
    suspend fun transformResponse(transform: suspend (HttpResponse) -> HttpResponse)
}

/**
 * Request hook - intercept request sending
 */
class OnRequestHook {
    suspend fun onRequest(
        handler: suspend (request: HttpRequestBuilder, content: OutgoingContent) -> Unit
    )
}

/**
 * Response hook - intercept response receiving
 */
class OnResponseHook {
    suspend fun onResponse(
        handler: suspend (response: HttpResponse) -> Unit
    )
}

/**
 * Close hook - cleanup when client closes
 */
class OnCloseHook {
    suspend fun onClose(handler: suspend () -> Unit)
}

Plugin Installation

Install and configure plugins in the client.

/**
 * Install plugin with configuration
 */
fun <TConfig : Any, TPlugin : Any> HttpClientConfig<*>.install(
    plugin: HttpClientPlugin<TConfig, TPlugin>,
    configure: TConfig.() -> Unit = {}
)

/**
 * Install plugin by key
 */
fun <T : Any> HttpClientConfig<*>.install(
    key: AttributeKey<T>,
    block: () -> T
)

/**
 * Get installed plugin
 */
fun <T : Any> HttpClient.plugin(plugin: HttpClientPlugin<*, T>): T
fun <T : Any> HttpClient.plugin(key: AttributeKey<T>): T

/**
 * Check if plugin is installed
 */
fun <T : Any> HttpClient.pluginOrNull(plugin: HttpClientPlugin<*, T>): T?
fun <T : Any> HttpClient.pluginOrNull(key: AttributeKey<T>): T?

Usage Examples:

// Install with configuration
val client = HttpClient {
    install(HttpTimeout) {
        requestTimeoutMillis = 15000
        connectTimeoutMillis = 3000
    }
}

// Access installed plugin
val timeoutPlugin = client.plugin(HttpTimeout)

// Check if plugin is installed
val retryPlugin = client.pluginOrNull(HttpRequestRetry)
if (retryPlugin != null) {
    println("Retry plugin is installed")
}

Custom Plugin Examples

Examples of creating custom plugins for specific use cases.

// Request ID plugin
data class RequestIdConfig(
    var headerName: String = "X-Request-ID",
    var generateId: () -> String = { UUID.randomUUID().toString() }
)

val RequestIdPlugin = createClientPlugin("RequestId", ::RequestIdConfig) { config ->
    onRequest { request, _ ->
        if (!request.headers.contains(config.headerName)) {
            request.header(config.headerName, config.generateId())
        }
    }
}

// Response time measurement plugin
class ResponseTimeConfig {
    val handlers: MutableList<(Long) -> Unit> = mutableListOf()
    
    fun onResponseTime(handler: (Long) -> Unit) {
        handlers.add(handler)
    }
}

val ResponseTimePlugin = createClientPlugin("ResponseTime", ::ResponseTimeConfig) { config ->
    var startTime: Long = 0
    
    onRequest { _, _ ->
        startTime = System.currentTimeMillis()
    }
    
    onResponse { _ ->
        val responseTime = System.currentTimeMillis() - startTime
        config.handlers.forEach { it(responseTime) }
    }
}

Plugin Phases

Understanding plugin execution phases and order.

/**
 * Plugin phases determine execution order
 */
enum class PipelinePhase {
    Before,      // Execute before built-in processing
    Transform,   // Transform request/response
    State,       // Modify client state
    Monitoring,  // Monitor and observe
    Send,        // Send request
    Receive,     // Receive response
    After        // Execute after built-in processing
}

Types

// Plugin system types
class AttributeKey<T>(val name: String)

class Attributes {
    fun <T : Any> put(key: AttributeKey<T>, value: T)
    fun <T : Any> get(key: AttributeKey<T>): T
    fun <T : Any> getOrNull(key: AttributeKey<T>): T?
    fun <T : Any> remove(key: AttributeKey<T>): T?
    fun <T : Any> computeIfAbsent(key: AttributeKey<T>, block: () -> T): T
}

// Plugin configuration types
interface ClientPluginConfig

// Hook types for pipeline processing
interface PipelineContext<TSubject : Any, TContext : Any> {
    val context: TContext
    val subject: TSubject
    suspend fun proceed(): TSubject
    suspend fun proceedWith(subject: TSubject): TSubject
}

Install with Tessl CLI

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

docs

client-configuration.md

content-handling.md

engine-architecture.md

events-monitoring.md

http-statement.md

index.md

plugin-system.md

request-building.md

response-handling.md

websocket-support.md

tile.json