Extensible plugin architecture for adding functionality like authentication, caching, logging, and custom interceptors. The plugin system provides a standardized way to extend HttpClient capabilities with both built-in and custom plugins.
Base interface for all client plugins providing installation and configuration mechanisms.
/**
* Base interface for client plugins
*/
interface HttpClientPlugin<TConfig : Any, TPlugin : Any> {
/** Unique key for identifying the plugin */
val key: AttributeKey<TPlugin>
/**
* Prepare a plugin instance with the given configuration
*/
fun prepare(block: TConfig.() -> Unit = {}): TPlugin
/**
* Install the plugin into the specified client scope
*/
fun install(plugin: TPlugin, scope: HttpClient)
}Functions for installing and managing plugins in HttpClient instances.
/**
* Install a plugin with configuration
*/
fun <TConfig : Any, TPlugin : Any> HttpClientConfig<*>.install(
plugin: HttpClientPlugin<TConfig, TPlugin>,
configure: TConfig.() -> Unit = {}
): TPlugin
/**
* Install a custom interceptor with a string key
*/
fun HttpClientConfig<*>.install(
key: String,
block: HttpClient.() -> Unit
)
/**
* Get an installed plugin instance (nullable)
*/
fun <TConfig : Any, TPlugin : Any> HttpClient.pluginOrNull(
plugin: HttpClientPlugin<TConfig, TPlugin>
): TPlugin?
/**
* Get an installed plugin instance (throws if not found)
*/
fun <TConfig : Any, TPlugin : Any> HttpClient.plugin(
plugin: HttpClientPlugin<TConfig, TPlugin>
): TPluginRequest, connect, and socket timeout configuration.
/**
* Plugin for configuring request timeouts
*/
object HttpTimeout : HttpClientPlugin<HttpTimeoutConfig, HttpTimeoutConfig> {
override val key: AttributeKey<HttpTimeoutConfig>
}
/**
* Configuration for HttpTimeout plugin
*/
class HttpTimeoutConfig {
/** Request timeout in milliseconds */
var requestTimeoutMillis: Long?
/** Connection timeout in milliseconds */
var connectTimeoutMillis: Long?
/** Socket timeout in milliseconds */
var socketTimeoutMillis: Long?
}
/**
* Configure timeout for a specific request
*/
fun HttpRequestBuilder.timeout(block: HttpTimeoutConfig.() -> Unit)Automatic redirect handling with customizable behavior.
/**
* Plugin for handling HTTP redirects
*/
object HttpRedirect : HttpClientPlugin<HttpRedirectConfig, HttpRedirectConfig> {
override val key: AttributeKey<HttpRedirectConfig>
}
/**
* Configuration for HttpRedirect plugin
*/
class HttpRedirectConfig {
/** Check HTTP method before following redirects */
var checkHttpMethod: Boolean
/** Allow HTTPS to HTTP downgrade during redirects */
var allowHttpsDowngrade: Boolean
/** Maximum number of redirects to follow */
var maxJumps: Int
}Response validation and error handling.
/**
* Plugin for validating HTTP responses
*/
object HttpCallValidator : HttpClientPlugin<HttpCallValidatorConfig, HttpCallValidatorConfig> {
override val key: AttributeKey<HttpCallValidatorConfig>
}
/**
* Configuration for HttpCallValidator plugin
*/
class HttpCallValidatorConfig {
/**
* Add a response validator
*/
fun validateResponse(block: suspend (response: HttpResponse) -> Unit)
/**
* Add an exception handler
*/
fun handleResponseExceptionWithRequest(
block: suspend (exception: Throwable, request: HttpRequest) -> Unit
)
/** Enable/disable automatic status code validation */
var expectSuccess: Boolean
}User-Agent header management.
/**
* Plugin for managing User-Agent header
*/
object UserAgent : HttpClientPlugin<UserAgentConfig, UserAgentConfig> {
override val key: AttributeKey<UserAgentConfig>
}
/**
* Configuration for UserAgent plugin
*/
class UserAgentConfig {
/** User agent string */
var agent: String
}Default request configuration applied to all requests.
/**
* Plugin for applying default request configuration
*/
object DefaultRequest : HttpClientPlugin<DefaultRequestBuilder, DefaultRequestBuilder> {
override val key: AttributeKey<DefaultRequestBuilder>
}
/**
* Builder for default request configuration
*/
class DefaultRequestBuilder : HttpRequestBuilder() {
// Inherits all HttpRequestBuilder functionality
// Configuration applied to every request
}Request sending pipeline management.
/**
* Plugin for managing request sending pipeline
*/
object HttpSend : HttpClientPlugin<HttpSendConfig, HttpSendInterceptor> {
override val key: AttributeKey<HttpSendInterceptor>
}
/**
* Configuration for HttpSend plugin
*/
class HttpSendConfig {
/** Maximum number of send attempts */
var maxSendCount: Int
}Response body saving and replay functionality.
/**
* Plugin for saving and replaying response bodies
*/
object SaveBody : HttpClientPlugin<SaveBodyConfig, SaveBodyConfig> {
override val key: AttributeKey<SaveBodyConfig>
}
/**
* Configuration for SaveBody plugin
*/
class SaveBodyConfig {
/** Enable/disable body saving */
var enable: Boolean
/** Maximum body size to save */
var maxBodySize: Long
}Upload/download progress monitoring.
/**
* Plugin for monitoring upload/download progress
*/
object BodyProgress : HttpClientPlugin<BodyProgressConfig, BodyProgressConfig> {
override val key: AttributeKey<BodyProgressConfig>
}
/**
* Configuration for BodyProgress plugin
*/
class BodyProgressConfig {
/**
* Set upload progress listener
*/
fun upload(listener: (bytesSentTotal: Long, contentLength: Long) -> Unit)
/**
* Set download progress listener
*/
fun download(listener: (bytesReceivedTotal: Long, contentLength: Long) -> Unit)
}Plain text content handling and charset support.
/**
* Plugin for handling plain text content
*/
object HttpPlainText : HttpClientPlugin<HttpPlainTextConfig, HttpPlainTextConfig> {
override val key: AttributeKey<HttpPlainTextConfig>
}
/**
* Configuration for HttpPlainText plugin
*/
class HttpPlainTextConfig {
/** Default charset for text content */
var defaultCharset: Charset
/** Accepted charsets */
val acceptedCharsets: MutableSet<Charset>
/**
* Send text with specific charset
*/
fun send(charset: Charset)
/**
* Accept text with specific charset
*/
fun accept(charset: Charset)
}Creating custom plugins for extending HttpClient functionality.
/**
* Create a custom plugin using the plugin builder DSL
*/
fun <TConfig : Any> createClientPlugin(
name: String,
createConfiguration: () -> TConfig,
body: PluginBuilder<TConfig>.() -> Unit
): HttpClientPlugin<TConfig, *>
/**
* Builder for creating custom plugins
*/
class PluginBuilder<TConfig : Any> {
/**
* Configure what happens when the plugin is installed
*/
fun onInstall(block: (plugin: TConfig, scope: HttpClient) -> Unit)
/**
* Add request transformation
*/
fun transformRequestBody(
block: suspend (context: HttpRequestBuilder, content: Any, bodyType: TypeInfo?) -> Any
)
/**
* Add response transformation
*/
fun transformResponseBody(
block: suspend (context: HttpClientCall, content: Any, requestedType: TypeInfo) -> Any
)
/**
* Intercept request pipeline
*/
fun onRequest(block: suspend (request: HttpRequestBuilder, content: Any) -> Unit)
/**
* Intercept response pipeline
*/
fun onResponse(block: suspend (response: HttpResponse) -> Unit)
/**
* Handle call lifecycle
*/
fun onCallRequest(block: suspend (request: HttpRequest) -> Unit)
fun onCallResponse(block: suspend (call: HttpClientCall) -> Unit)
}Usage Examples:
import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.cache.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.cookies.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import kotlinx.serialization.json.Json
// Create client with built-in plugins
val client = HttpClient {
// Timeout configuration
install(HttpTimeout) {
requestTimeoutMillis = 30000
connectTimeoutMillis = 10000
socketTimeoutMillis = 15000
}
// Redirect handling
install(HttpRedirect) {
checkHttpMethod = true
allowHttpsDowngrade = false
maxJumps = 5
}
// Response validation
install(HttpCallValidator) {
expectSuccess = true
validateResponse { response ->
if (response.status.value >= 400) {
val errorBody = response.bodyAsText()
throw Exception("HTTP ${response.status.value}: $errorBody")
}
}
handleResponseExceptionWithRequest { exception, request ->
println("Request to ${request.url} failed: ${exception.message}")
}
}
// User agent
install(UserAgent) {
agent = "MyApp/1.0.0 (Kotlin Ktor Client)"
}
// Default request configuration
install(DefaultRequest) {
url {
protocol = URLProtocol.HTTPS
host = "api.example.com"
}
headers {
append("X-API-Version", "v1")
}
}
// Progress monitoring
install(BodyProgress) {
upload { bytesSent, totalBytes ->
val progress = (bytesSent.toDouble() / totalBytes * 100).toInt()
println("Upload progress: $progress%")
}
download { bytesReceived, totalBytes ->
val progress = (bytesReceived.toDouble() / totalBytes * 100).toInt()
println("Download progress: $progress%")
}
}
}
// Access installed plugins
val timeoutConfig = client.plugin(HttpTimeout)
println("Request timeout: ${timeoutConfig.requestTimeoutMillis}ms")
// Check if plugin is installed
val redirectConfig = client.pluginOrNull(HttpRedirect)
if (redirectConfig != null) {
println("Redirect plugin is installed")
}
// Create custom plugin
val CustomLoggingPlugin = createClientPlugin("CustomLogging", { CustomLoggingConfig() }) {
onRequest { request, _ ->
println("Making request to: ${request.url}")
}
onResponse { response ->
println("Received response: ${response.status}")
}
onInstall { config, scope ->
println("Custom logging plugin installed with level: ${config.level}")
}
}
data class CustomLoggingConfig(
var level: String = "INFO"
)
// Install custom plugin
val customClient = HttpClient {
install(CustomLoggingPlugin) {
level = "DEBUG"
}
}
// Request-specific timeout override
val response = client.get("/users") {
timeout {
requestTimeoutMillis = 5000
}
}
client.close()
customClient.close()Understanding plugin configuration and lifecycle management.
/**
* Plugin configuration scope during client creation
*/
interface HttpClientConfig<T : HttpClientEngineConfig> {
/** Engine-specific configuration */
val engineConfig: T
/** Enable/disable automatic redirects */
var followRedirects: Boolean
/** Enable/disable default transformers */
var useDefaultTransformers: Boolean
/** Enable/disable response validation */
var expectSuccess: Boolean
/**
* Configure engine settings
*/
fun engine(block: T.() -> Unit)
/**
* Install plugin with configuration
*/
fun <TBuilder : Any, TPlugin : Any> install(
plugin: HttpClientPlugin<TBuilder, TPlugin>,
configure: TBuilder.() -> Unit = {}
): TPlugin
/**
* Copy configuration from another config
*/
operator fun plusAssign(other: HttpClientConfig<out HttpClientEngineConfig>)
}