0
# Plugin System
1
2
Comprehensive plugin architecture with built-in plugins for common functionality and custom plugin creation capabilities.
3
4
## Capabilities
5
6
### Core Plugin Interface
7
8
Base plugin interface and plugin management system.
9
10
```kotlin { .api }
11
/**
12
* Core plugin interface for HttpClient
13
*/
14
interface HttpClientPlugin<TBuilder : Any, TPlugin : Any> {
15
/** Unique key for this plugin type */
16
val key: AttributeKey<TPlugin>
17
18
/**
19
* Prepare plugin configuration
20
* @param block Configuration block
21
* @returns Configured plugin instance
22
*/
23
fun prepare(block: TBuilder.() -> Unit): TPlugin
24
25
/**
26
* Install plugin into HttpClient
27
* @param plugin Prepared plugin instance
28
* @param scope Target HttpClient
29
*/
30
fun install(plugin: TPlugin, scope: HttpClient)
31
}
32
33
/**
34
* Create custom client plugin
35
* @param name Plugin name
36
* @param createConfiguration Configuration factory
37
* @param body Plugin implementation
38
* @returns HttpClientPlugin instance
39
*/
40
fun <TConfig : Any, TPlugin : Any> createClientPlugin(
41
name: String,
42
createConfiguration: () -> TConfig,
43
body: ClientPluginBuilder<TConfig>.() -> TPlugin
44
): HttpClientPlugin<TConfig, TPlugin>
45
```
46
47
### Built-in Plugins
48
49
Core plugins provided by Ktor for common HTTP client functionality.
50
51
```kotlin { .api }
52
/**
53
* HTTP timeout configuration plugin
54
*/
55
object HttpTimeout : HttpClientPlugin<HttpTimeoutConfig, HttpTimeoutConfig> {
56
override val key: AttributeKey<HttpTimeoutConfig>
57
58
/**
59
* Timeout configuration
60
*/
61
class HttpTimeoutConfig {
62
/** Request timeout in milliseconds */
63
var requestTimeoutMillis: Long? = null
64
65
/** Connection timeout in milliseconds */
66
var connectTimeoutMillis: Long? = null
67
68
/** Socket timeout in milliseconds */
69
var socketTimeoutMillis: Long? = null
70
}
71
}
72
73
/**
74
* HTTP redirect handling plugin
75
*/
76
object HttpRedirect : HttpClientPlugin<HttpRedirectConfig, HttpRedirectConfig> {
77
override val key: AttributeKey<HttpRedirectConfig>
78
79
/**
80
* Redirect configuration
81
*/
82
class HttpRedirectConfig {
83
/** Check HTTP method on redirect (default: true) */
84
var checkHttpMethod: Boolean = true
85
86
/** Allow HTTPS to HTTP downgrade (default: false) */
87
var allowHttpsDowngrade: Boolean = false
88
89
/** Maximum number of redirects (default: 20) */
90
var maxJumps: Int = 20
91
}
92
}
93
94
/**
95
* Cookie management plugin
96
*/
97
object HttpCookies : HttpClientPlugin<HttpCookiesConfig, HttpCookiesConfig> {
98
override val key: AttributeKey<HttpCookiesConfig>
99
100
/**
101
* Cookies configuration
102
*/
103
class HttpCookiesConfig {
104
/** Cookie storage implementation */
105
var storage: CookiesStorage = AcceptAllCookiesStorage()
106
}
107
}
108
109
/**
110
* Response validation plugin
111
*/
112
object HttpCallValidator : HttpClientPlugin<HttpCallValidatorConfig, HttpCallValidatorConfig> {
113
override val key: AttributeKey<HttpCallValidatorConfig>
114
115
/**
116
* Validation configuration
117
*/
118
class HttpCallValidatorConfig {
119
/** Validate response status codes */
120
var validateResponse: (suspend (HttpResponse) -> Unit)? = null
121
122
/** Handle response exceptions */
123
var handleResponseException: (suspend (exception: Throwable) -> Unit)? = null
124
}
125
}
126
127
/**
128
* Default request configuration plugin
129
*/
130
object DefaultRequest : HttpClientPlugin<DefaultRequestConfig, DefaultRequestConfig> {
131
override val key: AttributeKey<DefaultRequestConfig>
132
133
/**
134
* Default request configuration
135
*/
136
class DefaultRequestConfig {
137
/** Default request builder configuration */
138
var block: HttpRequestBuilder.() -> Unit = {}
139
}
140
}
141
142
/**
143
* User agent header plugin
144
*/
145
object UserAgent : HttpClientPlugin<UserAgentConfig, UserAgentConfig> {
146
override val key: AttributeKey<UserAgentConfig>
147
148
/**
149
* User agent configuration
150
*/
151
class UserAgentConfig {
152
/** User agent string */
153
var agent: String = ""
154
}
155
}
156
157
/**
158
* Request retry plugin
159
*/
160
object HttpRequestRetry : HttpClientPlugin<HttpRequestRetryConfig, HttpRequestRetryConfig> {
161
override val key: AttributeKey<HttpRequestRetryConfig>
162
163
/**
164
* Retry configuration
165
*/
166
class HttpRequestRetryConfig {
167
/** Maximum retry attempts */
168
var maxRetryAttempts: Int = 3
169
170
/** Retry condition */
171
var retryIf: (suspend (request: HttpRequestBuilder, response: HttpResponse) -> Boolean)? = null
172
173
/** Delay between retries */
174
var delayMillis: (retry: Int) -> Long = { retry -> retry * 1000L }
175
}
176
}
177
```
178
179
**Usage Examples:**
180
181
```kotlin
182
val client = HttpClient {
183
// Install timeout plugin
184
install(HttpTimeout) {
185
requestTimeoutMillis = 30000
186
connectTimeoutMillis = 10000
187
socketTimeoutMillis = 15000
188
}
189
190
// Install redirect plugin
191
install(HttpRedirect) {
192
checkHttpMethod = true
193
allowHttpsDowngrade = false
194
maxJumps = 10
195
}
196
197
// Install cookies plugin
198
install(HttpCookies) {
199
storage = AcceptAllCookiesStorage()
200
}
201
202
// Install response validation
203
install(HttpCallValidator) {
204
validateResponse { response ->
205
when (response.status.value) {
206
in 300..399 -> throw RedirectResponseException(response, "Redirects not allowed")
207
in 400..499 -> throw ClientRequestException(response, "Client error")
208
in 500..599 -> throw ServerResponseException(response, "Server error")
209
}
210
}
211
212
handleResponseException { exception ->
213
println("Request failed: ${exception.message}")
214
}
215
}
216
217
// Install default request configuration
218
install(DefaultRequest) {
219
header("User-Agent", "MyApp/1.0")
220
header("Accept", "application/json")
221
url("https://api.example.com/")
222
}
223
224
// Install user agent
225
install(UserAgent) {
226
agent = "MyApp/1.0 (Kotlin HTTP Client)"
227
}
228
229
// Install retry plugin
230
install(HttpRequestRetry) {
231
maxRetryAttempts = 3
232
retryIf { _, response ->
233
response.status.value >= 500
234
}
235
delayMillis { retry ->
236
retry * 2000L // Exponential backoff
237
}
238
}
239
}
240
```
241
242
### Custom Plugin Creation
243
244
Create custom plugins using the plugin builder DSL.
245
246
```kotlin { .api }
247
/**
248
* Plugin builder for creating custom plugins
249
*/
250
class ClientPluginBuilder<TConfig : Any> {
251
/**
252
* Hook into request pipeline phase
253
* @param phase Pipeline phase to intercept
254
* @param block Interceptor implementation
255
*/
256
fun onRequest(phase: PipelinePhase, block: suspend (HttpRequestBuilder, TConfig) -> Unit)
257
258
/**
259
* Hook into response pipeline phase
260
* @param phase Pipeline phase to intercept
261
* @param block Interceptor implementation
262
*/
263
fun onResponse(phase: PipelinePhase, block: suspend (HttpResponse, TConfig) -> Unit)
264
265
/**
266
* Hook into call processing
267
* @param block Call interceptor implementation
268
*/
269
fun onCall(block: suspend (HttpClientCall, TConfig) -> Unit)
270
271
/**
272
* Add cleanup logic when client is closed
273
* @param block Cleanup implementation
274
*/
275
fun onClose(block: (TConfig) -> Unit)
276
}
277
278
/**
279
* Plugin configuration attribute key
280
*/
281
class AttributeKey<T>(val name: String) {
282
companion object {
283
fun <T> create(name: String): AttributeKey<T>
284
}
285
}
286
```
287
288
**Usage Examples:**
289
290
```kotlin
291
// Custom logging plugin
292
val CustomLogging = createClientPlugin("CustomLogging", ::LoggingConfig) {
293
val config = pluginConfig
294
295
onRequest(HttpRequestPipeline.Before) { request, _ ->
296
if (config.logRequests) {
297
println("→ ${request.method.value} ${request.url}")
298
request.headers.forEach { name, values ->
299
println(" $name: ${values.joinToString()}")
300
}
301
}
302
}
303
304
onResponse(HttpResponsePipeline.Receive) { response, _ ->
305
if (config.logResponses) {
306
println("← ${response.status}")
307
response.headers.forEach { name, values ->
308
println(" $name: ${values.joinToString()}")
309
}
310
}
311
}
312
313
onClose { config ->
314
if (config.logCleanup) {
315
println("Logging plugin closed")
316
}
317
}
318
}
319
320
data class LoggingConfig(
321
var logRequests: Boolean = true,
322
var logResponses: Boolean = true,
323
var logCleanup: Boolean = false
324
)
325
326
// Use custom plugin
327
val client = HttpClient {
328
install(CustomLogging) {
329
logRequests = true
330
logResponses = true
331
logCleanup = true
332
}
333
}
334
335
// Custom authentication plugin
336
val BearerAuth = createClientPlugin("BearerAuth", ::BearerAuthConfig) {
337
val config = pluginConfig
338
339
onRequest(HttpRequestPipeline.State) { request, _ ->
340
val token = config.tokenProvider()
341
if (token != null) {
342
request.header("Authorization", "Bearer $token")
343
}
344
}
345
346
onResponse(HttpResponsePipeline.Receive) { response, _ ->
347
if (response.status == HttpStatusCode.Unauthorized) {
348
config.onUnauthorized?.invoke()
349
}
350
}
351
}
352
353
data class BearerAuthConfig(
354
var tokenProvider: () -> String? = { null },
355
var onUnauthorized: (() -> Unit)? = null
356
)
357
358
// Use authentication plugin
359
val client = HttpClient {
360
install(BearerAuth) {
361
tokenProvider = { getStoredToken() }
362
onUnauthorized = { refreshToken() }
363
}
364
}
365
```
366
367
### Plugin Hooks and Lifecycle
368
369
Plugin hooks for intercepting various stages of request/response processing.
370
371
```kotlin { .api }
372
/**
373
* Common plugin hooks
374
*/
375
object CommonHooks {
376
/** Before request is sent */
377
val BeforeRequest: ClientHook<suspend (HttpRequestBuilder) -> Unit>
378
379
/** After response is received */
380
val AfterResponse: ClientHook<suspend (HttpResponse) -> Unit>
381
382
/** On request exception */
383
val OnRequestException: ClientHook<suspend (Throwable) -> Unit>
384
385
/** On response exception */
386
val OnResponseException: ClientHook<suspend (Throwable) -> Unit>
387
}
388
389
/**
390
* Plugin hook interface
391
*/
392
interface ClientHook<T> {
393
val name: String
394
fun install(client: HttpClient, handler: T)
395
}
396
397
/**
398
* Plugin instance wrapper
399
*/
400
class ClientPluginInstance<TConfig : Any> {
401
val config: TConfig
402
val plugin: HttpClientPlugin<*, *>
403
404
fun close()
405
}
406
```
407
408
**Usage Examples:**
409
410
```kotlin
411
// Using common hooks
412
val client = HttpClient {
413
install("RequestLogger") {
414
CommonHooks.BeforeRequest.install(this) { request ->
415
println("Sending request to: ${request.url}")
416
}
417
418
CommonHooks.AfterResponse.install(this) { response ->
419
println("Received response: ${response.status}")
420
}
421
422
CommonHooks.OnRequestException.install(this) { exception ->
423
println("Request failed: ${exception.message}")
424
}
425
}
426
}
427
428
// Advanced plugin with multiple hooks
429
val AdvancedMetrics = createClientPlugin("AdvancedMetrics", ::MetricsConfig) {
430
val config = pluginConfig
431
val requestTimes = mutableMapOf<HttpClientCall, Long>()
432
433
// Track request start time
434
onRequest(HttpRequestPipeline.Before) { request, _ ->
435
requestTimes[request.executionContext] = System.currentTimeMillis()
436
}
437
438
// Calculate and log request duration
439
onResponse(HttpResponsePipeline.Receive) { response, _ ->
440
val startTime = requestTimes.remove(response.call)
441
if (startTime != null) {
442
val duration = System.currentTimeMillis() - startTime
443
config.onRequestComplete(response.call.request.url.toString(), duration, response.status)
444
}
445
}
446
447
// Handle request failures
448
onCall { call, _ ->
449
try {
450
proceed()
451
} catch (e: Exception) {
452
val startTime = requestTimes.remove(call)
453
if (startTime != null) {
454
val duration = System.currentTimeMillis() - startTime
455
config.onRequestFailed(call.request.url.toString(), duration, e)
456
}
457
throw e
458
}
459
}
460
}
461
462
data class MetricsConfig(
463
var onRequestComplete: (url: String, duration: Long, status: HttpStatusCode) -> Unit = { _, _, _ -> },
464
var onRequestFailed: (url: String, duration: Long, exception: Throwable) -> Unit = { _, _, _ -> }
465
)
466
```
467
468
### Plugin Configuration and Management
469
470
Managing plugin configurations and accessing installed plugins.
471
472
```kotlin { .api }
473
/**
474
* Access installed plugin configuration
475
* @param plugin Plugin to get configuration for
476
* @returns Plugin configuration instance
477
*/
478
fun <TBuilder : Any, TPlugin : Any> HttpClient.plugin(
479
plugin: HttpClientPlugin<TBuilder, TPlugin>
480
): TPlugin
481
482
/**
483
* Check if plugin is installed
484
* @param plugin Plugin to check
485
* @returns True if plugin is installed
486
*/
487
fun HttpClient.isPluginInstalled(plugin: HttpClientPlugin<*, *>): Boolean
488
489
/**
490
* Get all installed plugins
491
* @returns Map of plugin keys to plugin instances
492
*/
493
fun HttpClient.installedPlugins(): Map<AttributeKey<*>, Any>
494
```
495
496
**Usage Examples:**
497
498
```kotlin
499
val client = HttpClient {
500
install(HttpTimeout) {
501
requestTimeoutMillis = 30000
502
}
503
504
install(HttpCookies) {
505
storage = AcceptAllCookiesStorage()
506
}
507
}
508
509
// Access plugin configuration
510
val timeoutConfig = client.plugin(HttpTimeout)
511
println("Request timeout: ${timeoutConfig.requestTimeoutMillis}")
512
513
val cookiesConfig = client.plugin(HttpCookies)
514
val cookieStorage = cookiesConfig.storage
515
516
// Check if plugin is installed
517
if (client.isPluginInstalled(HttpTimeout)) {
518
println("Timeout plugin is installed")
519
}
520
521
// Get all installed plugins
522
val allPlugins = client.installedPlugins()
523
allPlugins.forEach { (key, instance) ->
524
println("Plugin: ${key.name}")
525
}
526
```
527
528
## Types
529
530
### Plugin System Types
531
532
```kotlin { .api }
533
/**
534
* Pipeline phase for request/response processing
535
*/
536
class PipelinePhase(val name: String) {
537
companion object {
538
fun create(name: String): PipelinePhase
539
}
540
}
541
542
/**
543
* Request pipeline phases
544
*/
545
object HttpRequestPipeline {
546
val Before: PipelinePhase
547
val State: PipelinePhase
548
val Transform: PipelinePhase
549
val Render: PipelinePhase
550
val Send: PipelinePhase
551
}
552
553
/**
554
* Response pipeline phases
555
*/
556
object HttpResponsePipeline {
557
val Receive: PipelinePhase
558
val Parse: PipelinePhase
559
val Transform: PipelinePhase
560
}
561
562
/**
563
* Send pipeline phases
564
*/
565
object HttpSendPipeline {
566
val Before: PipelinePhase
567
val State: PipelinePhase
568
val Monitoring: PipelinePhase
569
val Engine: PipelinePhase
570
val Receive: PipelinePhase
571
}
572
573
/**
574
* Attributes collection for plugin data
575
*/
576
interface Attributes {
577
fun <T : Any> get(key: AttributeKey<T>): T
578
fun <T : Any> getOrNull(key: AttributeKey<T>): T?
579
fun <T : Any> put(key: AttributeKey<T>, value: T)
580
fun <T : Any> remove(key: AttributeKey<T>): T?
581
fun <T : Any> computeIfAbsent(key: AttributeKey<T>, block: () -> T): T
582
583
fun allKeys(): List<AttributeKey<*>>
584
585
companion object {
586
fun concurrent(): Attributes
587
}
588
}
589
```
590
591
### Exception Types
592
593
```kotlin { .api }
594
/**
595
* Plugin installation exception
596
*/
597
class PluginInstallationException(
598
message: String,
599
cause: Throwable? = null
600
) : Exception(message, cause)
601
602
/**
603
* Plugin configuration exception
604
*/
605
class PluginConfigurationException(
606
message: String,
607
cause: Throwable? = null
608
) : Exception(message, cause)
609
```