Ktor HTTP Client Core - a multiplatform asynchronous HTTP client library for Kotlin providing comprehensive HTTP request/response handling with plugin architecture.
—
Comprehensive plugin architecture with built-in plugins for common functionality and custom plugin creation capabilities.
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>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
}
}
}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 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 = { _, _, _ -> }
)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}")
}/**
* 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
}
}/**
* 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