0
# Plugin System and Extensibility
1
2
Plugin framework for extending client functionality with authentication, logging, content negotiation, caching, and custom middleware using a type-safe plugin architecture.
3
4
## Capabilities
5
6
### HttpClientPlugin Interface
7
8
Core plugin interface for extending HTTP client functionality.
9
10
```kotlin { .api }
11
/**
12
* HTTP client plugin interface for extending functionality
13
* @param TConfig Plugin configuration type
14
* @param TPlugin Plugin instance type
15
*/
16
interface HttpClientPlugin<TConfig : Any, TPlugin : Any> {
17
/** Unique key for this plugin */
18
val key: AttributeKey<TPlugin>
19
20
/** Prepare plugin instance from configuration */
21
fun prepare(block: TConfig.() -> Unit): TPlugin
22
23
/** Install plugin into HTTP client */
24
fun install(plugin: TPlugin, scope: HttpClient)
25
}
26
```
27
28
### Plugin Installation
29
30
Installing plugins in HTTP client configuration.
31
32
```kotlin { .api }
33
/**
34
* Install plugin in HTTP client
35
* @param plugin Plugin to install
36
* @param configure Plugin configuration block
37
*/
38
fun <TConfig : Any, TPlugin : Any> HttpClientConfig<*>.install(
39
plugin: HttpClientPlugin<TConfig, TPlugin>,
40
configure: TConfig.() -> Unit = {}
41
)
42
43
/**
44
* Get installed plugin instance
45
* @param plugin Plugin to retrieve
46
* @return Plugin instance
47
*/
48
fun <TConfig : Any, TPlugin : Any> HttpClient.plugin(
49
plugin: HttpClientPlugin<TConfig, TPlugin>
50
): TPlugin
51
52
/**
53
* Get installed plugin instance or null if not installed
54
* @param plugin Plugin to retrieve
55
* @return Plugin instance or null
56
*/
57
fun <TConfig : Any, TPlugin : Any> HttpClient.pluginOrNull(
58
plugin: HttpClientPlugin<TConfig, TPlugin>
59
): TPlugin?
60
```
61
62
**Usage Examples:**
63
64
```kotlin
65
import io.ktor.client.*
66
import io.ktor.client.plugins.*
67
import io.ktor.client.plugins.auth.*
68
import io.ktor.client.plugins.auth.providers.*
69
70
// Install plugins with configuration
71
val client = HttpClient {
72
install(Auth) {
73
bearer {
74
loadTokens {
75
BearerTokens("access_token", "refresh_token")
76
}
77
}
78
}
79
80
install(Logging) {
81
logger = Logger.DEFAULT
82
level = LogLevel.HEADERS
83
}
84
85
install(HttpTimeout) {
86
requestTimeoutMillis = 30000
87
connectTimeoutMillis = 10000
88
}
89
}
90
91
// Access installed plugin
92
val authPlugin = client.plugin(Auth)
93
val loggingPlugin = client.pluginOrNull(Logging)
94
95
if (loggingPlugin != null) {
96
println("Logging is enabled")
97
}
98
```
99
100
### ClientPluginBuilder
101
102
Builder for creating custom plugins with hooks and transformations.
103
104
```kotlin { .api }
105
/**
106
* Builder for creating custom HTTP client plugins
107
* @param TConfig Plugin configuration type
108
*/
109
class ClientPluginBuilder<TConfig : Any>(private val name: String) {
110
/** Register hook for client events */
111
fun on(event: ClientHook<*>, block: suspend ClientHookHandler<*>.() -> Unit)
112
113
/** Register request hook */
114
fun onRequest(block: suspend OnRequestContext.() -> Unit)
115
116
/** Register response hook */
117
fun onResponse(block: suspend OnResponseContext.() -> Unit)
118
119
/** Register request body transformation */
120
fun transformRequestBody(block: suspend TransformRequestBodyContext.() -> Unit)
121
122
/** Register response body transformation */
123
fun transformResponseBody(block: suspend TransformResponseBodyContext.() -> Unit)
124
}
125
126
/**
127
* Create custom HTTP client plugin
128
* @param name Plugin name
129
* @param createConfiguration Configuration factory
130
* @param body Plugin builder block
131
* @return Plugin instance
132
*/
133
fun <TConfig : Any> createClientPlugin(
134
name: String,
135
createConfiguration: () -> TConfig,
136
body: ClientPluginBuilder<TConfig>.() -> Unit
137
): HttpClientPlugin<TConfig, *>
138
```
139
140
**Usage Examples:**
141
142
```kotlin
143
import io.ktor.client.*
144
import io.ktor.client.plugins.*
145
import io.ktor.client.request.*
146
147
// Custom plugin configuration
148
data class CustomLoggingConfig(
149
var logRequests: Boolean = true,
150
var logResponses: Boolean = true,
151
var logLevel: String = "INFO"
152
)
153
154
// Create custom plugin
155
val CustomLogging = createClientPlugin("CustomLogging", ::CustomLoggingConfig) {
156
onRequest { request, _ ->
157
if (pluginConfig.logRequests) {
158
println("[${pluginConfig.logLevel}] Request: ${request.method.value} ${request.url}")
159
}
160
}
161
162
onResponse { response ->
163
if (pluginConfig.logResponses) {
164
println("[${pluginConfig.logLevel}] Response: ${response.status} from ${response.call.request.url}")
165
}
166
}
167
}
168
169
// Install custom plugin
170
val client = HttpClient {
171
install(CustomLogging) {
172
logRequests = true
173
logResponses = true
174
logLevel = "DEBUG"
175
}
176
}
177
178
// Complex custom plugin with transformations
179
val RequestIdPlugin = createClientPlugin("RequestId", { Unit }) {
180
onRequest { request, _ ->
181
val requestId = generateRequestId()
182
request.headers.append("X-Request-ID", requestId)
183
request.attributes.put(RequestIdKey, requestId)
184
}
185
186
onResponse { response ->
187
val requestId = response.call.request.attributes[RequestIdKey]
188
println("Response for request $requestId: ${response.status}")
189
}
190
}
191
```
192
193
### Plugin Hooks and Events
194
195
System for intercepting and modifying HTTP client behavior.
196
197
```kotlin { .api }
198
/**
199
* Request context for plugin hooks
200
*/
201
class OnRequestContext(
202
val request: HttpRequestBuilder,
203
val content: OutgoingContent
204
)
205
206
/**
207
* Response context for plugin hooks
208
*/
209
class OnResponseContext(
210
val response: HttpResponse
211
)
212
213
/**
214
* Request body transformation context
215
*/
216
class TransformRequestBodyContext(
217
val contentType: ContentType?,
218
val body: Any,
219
val bodyType: TypeInfo
220
)
221
222
/**
223
* Response body transformation context
224
*/
225
class TransformResponseBodyContext(
226
val contentType: ContentType?,
227
val body: Any,
228
val requestedType: TypeInfo
229
)
230
231
/**
232
* Client hook types for different events
233
*/
234
sealed class ClientHook<T>
235
object OnRequest : ClientHook<OnRequestContext>()
236
object OnResponse : ClientHook<OnResponseContext>()
237
object TransformRequestBody : ClientHook<TransformRequestBodyContext>()
238
object TransformResponseBody : ClientHook<TransformResponseBodyContext>()
239
```
240
241
**Usage Examples:**
242
243
```kotlin
244
import io.ktor.client.*
245
import io.ktor.client.plugins.*
246
import io.ktor.client.request.*
247
import io.ktor.http.*
248
249
// Plugin with multiple hooks
250
val ComprehensivePlugin = createClientPlugin("Comprehensive", { Unit }) {
251
// Request processing
252
onRequest { request, content ->
253
// Add custom headers
254
request.headers.append("X-Client", "Ktor")
255
request.headers.append("X-Timestamp", System.currentTimeMillis().toString())
256
257
// Log request details
258
println("Sending ${request.method.value} to ${request.url}")
259
}
260
261
// Response processing
262
onResponse { response ->
263
// Log response details
264
println("Received ${response.status} from ${response.call.request.url}")
265
266
// Custom response validation
267
if (response.status.value >= 400) {
268
println("Error response received: ${response.status}")
269
}
270
}
271
272
// Request body transformation
273
transformRequestBody { contentType, body, bodyType ->
274
if (contentType?.match(ContentType.Application.Json) == true) {
275
// Modify JSON requests
276
when (body) {
277
is String -> {
278
val jsonObject = parseJson(body)
279
jsonObject["timestamp"] = System.currentTimeMillis()
280
transformBody(jsonObject.toString())
281
}
282
}
283
}
284
}
285
286
// Response body transformation
287
transformResponseBody { contentType, body, requestedType ->
288
if (contentType?.match(ContentType.Application.Json) == true) {
289
// Modify JSON responses
290
when (body) {
291
is String -> {
292
val jsonObject = parseJson(body)
293
jsonObject["processed"] = true
294
transformBody(jsonObject)
295
}
296
}
297
}
298
}
299
}
300
```
301
302
### Plugin Configuration Management
303
304
Managing plugin configurations and accessing plugin state.
305
306
```kotlin { .api }
307
/**
308
* Plugin configuration access within plugin context
309
*/
310
val <T> ClientPluginBuilder<T>.pluginConfig: T
311
312
/**
313
* Attribute key for storing plugin data
314
*/
315
class AttributeKey<T>(val name: String)
316
317
/**
318
* Attributes container for storing custom data
319
*/
320
interface Attributes {
321
/** Get attribute value */
322
operator fun <T : Any> get(key: AttributeKey<T>): T
323
324
/** Get attribute value or null */
325
fun <T : Any> getOrNull(key: AttributeKey<T>): T?
326
327
/** Set attribute value */
328
fun <T : Any> put(key: AttributeKey<T>, value: T)
329
330
/** Check if attribute exists */
331
fun <T : Any> contains(key: AttributeKey<T>): Boolean
332
333
/** Remove attribute */
334
fun <T : Any> remove(key: AttributeKey<T>)
335
336
/** Get all attribute keys */
337
fun allKeys(): List<AttributeKey<*>>
338
}
339
```
340
341
**Usage Examples:**
342
343
```kotlin
344
import io.ktor.client.*
345
import io.ktor.client.plugins.*
346
import io.ktor.util.*
347
348
// Plugin with stateful configuration
349
data class StatefulConfig(
350
var counter: Int = 0,
351
var enabled: Boolean = true
352
)
353
354
val RequestIdKey = AttributeKey<String>("RequestId")
355
val CounterKey = AttributeKey<Int>("Counter")
356
357
val StatefulPlugin = createClientPlugin("Stateful", ::StatefulConfig) {
358
onRequest { request, _ ->
359
if (pluginConfig.enabled) {
360
// Increment counter
361
pluginConfig.counter++
362
363
// Store in request attributes
364
request.attributes.put(CounterKey, pluginConfig.counter)
365
request.attributes.put(RequestIdKey, "req-${pluginConfig.counter}")
366
367
// Add header
368
request.headers.append("X-Request-Counter", pluginConfig.counter.toString())
369
}
370
}
371
372
onResponse { response ->
373
val counter = response.call.request.attributes.getOrNull(CounterKey)
374
val requestId = response.call.request.attributes.getOrNull(RequestIdKey)
375
376
println("Request $requestId (#$counter) completed with ${response.status}")
377
}
378
}
379
380
// Install and configure stateful plugin
381
val client = HttpClient {
382
install(StatefulPlugin) {
383
counter = 0
384
enabled = true
385
}
386
}
387
388
// Access plugin instance to modify state
389
val plugin = client.plugin(StatefulPlugin)
390
// Note: Plugin configuration is immutable after installation
391
// Use attributes or custom plugin state management for runtime changes
392
```
393
394
### Pipeline Integration
395
396
Integration with HTTP client pipelines for advanced request/response processing.
397
398
```kotlin { .api }
399
/**
400
* Pipeline phases for plugin integration
401
*/
402
class HttpRequestPipeline {
403
companion object {
404
val Before = PipelinePhase("Before")
405
val State = PipelinePhase("State")
406
val Transform = PipelinePhase("Transform")
407
val Render = PipelinePhase("Render")
408
val Send = PipelinePhase("Send")
409
}
410
}
411
412
class HttpResponsePipeline {
413
companion object {
414
val Receive = PipelinePhase("Receive")
415
val Parse = PipelinePhase("Parse")
416
val Transform = PipelinePhase("Transform")
417
}
418
}
419
420
/**
421
* Advanced plugin with pipeline integration
422
*/
423
fun <TConfig : Any> createClientPlugin(
424
name: String,
425
createConfiguration: () -> TConfig,
426
body: ClientPluginBuilder<TConfig>.() -> Unit
427
): HttpClientPlugin<TConfig, *>
428
```
429
430
**Usage Examples:**
431
432
```kotlin
433
import io.ktor.client.*
434
import io.ktor.client.plugins.*
435
import io.ktor.client.request.*
436
437
// Advanced plugin with pipeline integration
438
val PipelinePlugin = createClientPlugin("Pipeline", { Unit }) {
439
// Early request processing
440
on(HttpRequestPipeline.Before) { (request, _) ->
441
println("Before: Processing request to ${request.url}")
442
request.headers.append("X-Pipeline-Stage", "before")
443
}
444
445
// Request state processing
446
on(HttpRequestPipeline.State) { (request, _) ->
447
println("State: Request state processing for ${request.url}")
448
request.attributes.put(ProcessingKey, "state-processed")
449
}
450
451
// Request transformation
452
on(HttpRequestPipeline.Transform) { (request, content) ->
453
println("Transform: Transforming request content")
454
// Transform content if needed
455
}
456
457
// Response processing
458
on(HttpResponsePipeline.Receive) { container ->
459
println("Receive: Processing response")
460
// Process response container
461
}
462
}
463
464
// Error handling plugin
465
val ErrorHandlingPlugin = createClientPlugin("ErrorHandling", { Unit }) {
466
onResponse { response ->
467
when (response.status.value) {
468
in 400..499 -> {
469
val error = response.bodyAsText()
470
throw ClientRequestException(response, error)
471
}
472
in 500..599 -> {
473
val error = response.bodyAsText()
474
throw ServerResponseException(response, error)
475
}
476
}
477
}
478
}
479
```
480
481
## Types
482
483
### Plugin Types
484
485
```kotlin { .api }
486
data class PluginData<T>(
487
val plugin: HttpClientPlugin<*, T>,
488
val instance: T
489
)
490
491
class PluginAlreadyInstalledException(
492
val plugin: HttpClientPlugin<*, *>
493
) : IllegalStateException("Plugin $plugin is already installed")
494
495
abstract class ClientHookHandler<T> {
496
abstract suspend fun handle(context: T)
497
}
498
499
interface ClientPluginConfig {
500
fun <T : Any> getAttribute(key: AttributeKey<T>): T?
501
fun <T : Any> setAttribute(key: AttributeKey<T>, value: T)
502
}
503
```
504
505
### Exception Types
506
507
```kotlin { .api }
508
class ClientRequestException(
509
val response: HttpResponse,
510
val cachedResponseText: String
511
) : ResponseException(response, cachedResponseText)
512
513
class ServerResponseException(
514
val response: HttpResponse,
515
val cachedResponseText: String
516
) : ResponseException(response, cachedResponseText)
517
518
open class ResponseException(
519
val response: HttpResponse,
520
val cachedResponseText: String
521
) : IllegalStateException("Bad response: ${response.status}")
522
```