CtrlK
BlogDocsLog inGet started
Tessl Logo

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

Ktor HTTP client core library providing asynchronous HTTP client functionality for multiplatform applications with macOS ARM64 support.

Pending
Overview
Eval results
Files

plugin-system.mddocs/

Plugin System and Extensibility

Plugin framework for extending client functionality with authentication, logging, content negotiation, caching, and custom middleware using a type-safe plugin architecture.

Capabilities

HttpClientPlugin Interface

Core plugin interface for extending HTTP client functionality.

/**
 * HTTP client plugin interface for extending functionality
 * @param TConfig Plugin configuration type
 * @param TPlugin Plugin instance type
 */
interface HttpClientPlugin<TConfig : Any, TPlugin : Any> {
    /** Unique key for this 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 Installation

Installing plugins in HTTP client configuration.

/**
 * Install plugin in HTTP client
 * @param plugin Plugin to install
 * @param configure Plugin configuration block
 */
fun <TConfig : Any, TPlugin : Any> HttpClientConfig<*>.install(
    plugin: HttpClientPlugin<TConfig, TPlugin>,
    configure: TConfig.() -> Unit = {}
)

/**
 * Get installed plugin instance
 * @param plugin Plugin to retrieve
 * @return Plugin instance
 */
fun <TConfig : Any, TPlugin : Any> HttpClient.plugin(
    plugin: HttpClientPlugin<TConfig, TPlugin>
): TPlugin

/**
 * Get installed plugin instance or null if not installed
 * @param plugin Plugin to retrieve
 * @return Plugin instance or null
 */
fun <TConfig : Any, TPlugin : Any> HttpClient.pluginOrNull(
    plugin: HttpClientPlugin<TConfig, TPlugin>
): TPlugin?

Usage Examples:

import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*

// Install plugins with configuration
val client = HttpClient {
    install(Auth) {
        bearer {
            loadTokens {
                BearerTokens("access_token", "refresh_token")
            }
        }
    }
    
    install(Logging) {
        logger = Logger.DEFAULT
        level = LogLevel.HEADERS
    }
    
    install(HttpTimeout) {
        requestTimeoutMillis = 30000
        connectTimeoutMillis = 10000
    }
}

// Access installed plugin
val authPlugin = client.plugin(Auth)
val loggingPlugin = client.pluginOrNull(Logging)

if (loggingPlugin != null) {
    println("Logging is enabled")
}

ClientPluginBuilder

Builder for creating custom plugins with hooks and transformations.

/**
 * Builder for creating custom HTTP client plugins
 * @param TConfig Plugin configuration type
 */
class ClientPluginBuilder<TConfig : Any>(private val name: String) {
    /** Register hook for client events */
    fun on(event: ClientHook<*>, block: suspend ClientHookHandler<*>.() -> Unit)
    
    /** Register request hook */
    fun onRequest(block: suspend OnRequestContext.() -> Unit)
    
    /** Register response hook */
    fun onResponse(block: suspend OnResponseContext.() -> Unit)
    
    /** Register request body transformation */
    fun transformRequestBody(block: suspend TransformRequestBodyContext.() -> Unit)
    
    /** Register response body transformation */
    fun transformResponseBody(block: suspend TransformResponseBodyContext.() -> Unit)
}

/**
 * Create custom HTTP client plugin
 * @param name Plugin name
 * @param createConfiguration Configuration factory
 * @param body Plugin builder block
 * @return Plugin instance
 */
fun <TConfig : Any> createClientPlugin(
    name: String,
    createConfiguration: () -> TConfig,
    body: ClientPluginBuilder<TConfig>.() -> Unit
): HttpClientPlugin<TConfig, *>

Usage Examples:

import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*

// Custom plugin configuration
data class CustomLoggingConfig(
    var logRequests: Boolean = true,
    var logResponses: Boolean = true,
    var logLevel: String = "INFO"
)

// Create custom plugin
val CustomLogging = createClientPlugin("CustomLogging", ::CustomLoggingConfig) {
    onRequest { request, _ ->
        if (pluginConfig.logRequests) {
            println("[${pluginConfig.logLevel}] Request: ${request.method.value} ${request.url}")
        }
    }
    
    onResponse { response ->
        if (pluginConfig.logResponses) {
            println("[${pluginConfig.logLevel}] Response: ${response.status} from ${response.call.request.url}")
        }
    }
}

// Install custom plugin
val client = HttpClient {
    install(CustomLogging) {
        logRequests = true
        logResponses = true
        logLevel = "DEBUG"
    }
}

// Complex custom plugin with transformations
val RequestIdPlugin = createClientPlugin("RequestId", { Unit }) {
    onRequest { request, _ ->
        val requestId = generateRequestId()
        request.headers.append("X-Request-ID", requestId)
        request.attributes.put(RequestIdKey, requestId)
    }
    
    onResponse { response ->
        val requestId = response.call.request.attributes[RequestIdKey]
        println("Response for request $requestId: ${response.status}")
    }
}

Plugin Hooks and Events

System for intercepting and modifying HTTP client behavior.

/**
 * Request context for plugin hooks
 */
class OnRequestContext(
    val request: HttpRequestBuilder,
    val content: OutgoingContent
)

/**
 * Response context for plugin hooks  
 */
class OnResponseContext(
    val response: HttpResponse
)

/**
 * Request body transformation context
 */
class TransformRequestBodyContext(
    val contentType: ContentType?,
    val body: Any,
    val bodyType: TypeInfo
)

/**
 * Response body transformation context
 */
class TransformResponseBodyContext(
    val contentType: ContentType?,
    val body: Any,
    val requestedType: TypeInfo
)

/**
 * Client hook types for different events
 */
sealed class ClientHook<T>
object OnRequest : ClientHook<OnRequestContext>()
object OnResponse : ClientHook<OnResponseContext>()
object TransformRequestBody : ClientHook<TransformRequestBodyContext>()
object TransformResponseBody : ClientHook<TransformResponseBodyContext>()

Usage Examples:

import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.http.*

// Plugin with multiple hooks
val ComprehensivePlugin = createClientPlugin("Comprehensive", { Unit }) {
    // Request processing
    onRequest { request, content ->
        // Add custom headers
        request.headers.append("X-Client", "Ktor")
        request.headers.append("X-Timestamp", System.currentTimeMillis().toString())
        
        // Log request details
        println("Sending ${request.method.value} to ${request.url}")
    }
    
    // Response processing
    onResponse { response ->
        // Log response details
        println("Received ${response.status} from ${response.call.request.url}")
        
        // Custom response validation
        if (response.status.value >= 400) {
            println("Error response received: ${response.status}")
        }
    }
    
    // Request body transformation
    transformRequestBody { contentType, body, bodyType ->
        if (contentType?.match(ContentType.Application.Json) == true) {
            // Modify JSON requests
            when (body) {
                is String -> {
                    val jsonObject = parseJson(body)
                    jsonObject["timestamp"] = System.currentTimeMillis()
                    transformBody(jsonObject.toString())
                }
            }
        }
    }
    
    // Response body transformation
    transformResponseBody { contentType, body, requestedType ->
        if (contentType?.match(ContentType.Application.Json) == true) {
            // Modify JSON responses
            when (body) {
                is String -> {
                    val jsonObject = parseJson(body)
                    jsonObject["processed"] = true
                    transformBody(jsonObject)
                }
            }
        }
    }
}

Plugin Configuration Management

Managing plugin configurations and accessing plugin state.

/**
 * Plugin configuration access within plugin context
 */
val <T> ClientPluginBuilder<T>.pluginConfig: T

/**
 * Attribute key for storing plugin data
 */
class AttributeKey<T>(val name: String)

/**
 * Attributes container for storing custom data
 */
interface Attributes {
    /** Get attribute value */
    operator fun <T : Any> get(key: AttributeKey<T>): T
    
    /** Get attribute value or null */
    fun <T : Any> getOrNull(key: AttributeKey<T>): T?
    
    /** Set attribute value */
    fun <T : Any> put(key: AttributeKey<T>, value: T)
    
    /** Check if attribute exists */
    fun <T : Any> contains(key: AttributeKey<T>): Boolean
    
    /** Remove attribute */
    fun <T : Any> remove(key: AttributeKey<T>)
    
    /** Get all attribute keys */
    fun allKeys(): List<AttributeKey<*>>
}

Usage Examples:

import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.util.*

// Plugin with stateful configuration
data class StatefulConfig(
    var counter: Int = 0,
    var enabled: Boolean = true
)

val RequestIdKey = AttributeKey<String>("RequestId")
val CounterKey = AttributeKey<Int>("Counter")

val StatefulPlugin = createClientPlugin("Stateful", ::StatefulConfig) {
    onRequest { request, _ ->
        if (pluginConfig.enabled) {
            // Increment counter
            pluginConfig.counter++
            
            // Store in request attributes
            request.attributes.put(CounterKey, pluginConfig.counter)
            request.attributes.put(RequestIdKey, "req-${pluginConfig.counter}")
            
            // Add header
            request.headers.append("X-Request-Counter", pluginConfig.counter.toString())
        }
    }
    
    onResponse { response ->
        val counter = response.call.request.attributes.getOrNull(CounterKey)
        val requestId = response.call.request.attributes.getOrNull(RequestIdKey)
        
        println("Request $requestId (#$counter) completed with ${response.status}")
    }
}

// Install and configure stateful plugin
val client = HttpClient {
    install(StatefulPlugin) {
        counter = 0
        enabled = true
    }
}

// Access plugin instance to modify state
val plugin = client.plugin(StatefulPlugin)
// Note: Plugin configuration is immutable after installation
// Use attributes or custom plugin state management for runtime changes

Pipeline Integration

Integration with HTTP client pipelines for advanced request/response processing.

/**
 * Pipeline phases for plugin integration
 */
class HttpRequestPipeline {
    companion object {
        val Before = PipelinePhase("Before")
        val State = PipelinePhase("State")
        val Transform = PipelinePhase("Transform")
        val Render = PipelinePhase("Render")
        val Send = PipelinePhase("Send")
    }
}

class HttpResponsePipeline {
    companion object {
        val Receive = PipelinePhase("Receive")
        val Parse = PipelinePhase("Parse")
        val Transform = PipelinePhase("Transform")
    }
}

/**
 * Advanced plugin with pipeline integration
 */
fun <TConfig : Any> createClientPlugin(
    name: String,
    createConfiguration: () -> TConfig,
    body: ClientPluginBuilder<TConfig>.() -> Unit
): HttpClientPlugin<TConfig, *>

Usage Examples:

import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*

// Advanced plugin with pipeline integration
val PipelinePlugin = createClientPlugin("Pipeline", { Unit }) {
    // Early request processing
    on(HttpRequestPipeline.Before) { (request, _) ->
        println("Before: Processing request to ${request.url}")
        request.headers.append("X-Pipeline-Stage", "before")
    }
    
    // Request state processing
    on(HttpRequestPipeline.State) { (request, _) ->
        println("State: Request state processing for ${request.url}")
        request.attributes.put(ProcessingKey, "state-processed")
    }
    
    // Request transformation
    on(HttpRequestPipeline.Transform) { (request, content) ->
        println("Transform: Transforming request content")
        // Transform content if needed
    }
    
    // Response processing
    on(HttpResponsePipeline.Receive) { container ->
        println("Receive: Processing response")
        // Process response container
    }
}

// Error handling plugin
val ErrorHandlingPlugin = createClientPlugin("ErrorHandling", { Unit }) {
    onResponse { response ->
        when (response.status.value) {
            in 400..499 -> {
                val error = response.bodyAsText()
                throw ClientRequestException(response, error)
            }
            in 500..599 -> {
                val error = response.bodyAsText()
                throw ServerResponseException(response, error)
            }
        }
    }
}

Types

Plugin Types

data class PluginData<T>(
    val plugin: HttpClientPlugin<*, T>,
    val instance: T
)

class PluginAlreadyInstalledException(
    val plugin: HttpClientPlugin<*, *>
) : IllegalStateException("Plugin $plugin is already installed")

abstract class ClientHookHandler<T> {
    abstract suspend fun handle(context: T)
}

interface ClientPluginConfig {
    fun <T : Any> getAttribute(key: AttributeKey<T>): T?
    fun <T : Any> setAttribute(key: AttributeKey<T>, value: T)
}

Exception Types

class ClientRequestException(
    val response: HttpResponse,
    val cachedResponseText: String
) : ResponseException(response, cachedResponseText)

class ServerResponseException(
    val response: HttpResponse,
    val cachedResponseText: String
) : ResponseException(response, cachedResponseText)

open class ResponseException(
    val response: HttpResponse,
    val cachedResponseText: String
) : IllegalStateException("Bad response: ${response.status}")

Install with Tessl CLI

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

docs

built-in-plugins.md

engine-configuration.md

form-data-content.md

http-client.md

index.md

plugin-system.md

request-building.md

response-handling.md

tile.json