Ktor HTTP client core library providing asynchronous HTTP client capabilities for Kotlin multiplatform applications
—
Extensible plugin architecture for adding cross-cutting concerns like authentication, logging, caching, content negotiation, and custom request/response processing to the HTTP client.
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)
}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
}
}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}")
}
}
}
}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)
}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")
}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) }
}
}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
}// 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