Ktor HTTP client core library providing asynchronous HTTP client functionality for multiplatform applications with macOS ARM64 support.
—
Plugin framework for extending client functionality with authentication, logging, content negotiation, caching, and custom middleware using a type-safe plugin architecture.
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)
}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")
}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}")
}
}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)
}
}
}
}
}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 changesIntegration 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)
}
}
}
}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)
}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