0
# Plugin System
1
2
Extensible plugin architecture for adding cross-cutting concerns like authentication, caching, content negotiation, and logging to HTTP clients.
3
4
## Capabilities
5
6
### HttpClientPlugin Interface
7
8
Base interface for all HTTP client plugins defining lifecycle and configuration.
9
10
```kotlin { .api }
11
/**
12
* Base plugin interface for HTTP client functionality
13
*/
14
interface HttpClientPlugin<out TConfig : Any, TPlugin : Any> {
15
/** Unique plugin identifier */
16
val key: AttributeKey<TPlugin>
17
18
/**
19
* Prepare plugin instance with configuration
20
*/
21
fun prepare(block: TConfig.() -> Unit = {}): TPlugin
22
23
/**
24
* Install plugin into HTTP client scope
25
*/
26
fun install(plugin: TPlugin, scope: HttpClient)
27
}
28
```
29
30
**Usage Examples:**
31
32
```kotlin
33
// Example plugin implementation
34
object CustomLogging : HttpClientPlugin<CustomLogging.Config, CustomLogging> {
35
override val key: AttributeKey<CustomLogging> = AttributeKey("CustomLogging")
36
37
class Config {
38
var logLevel: LogLevel = LogLevel.INFO
39
var includeHeaders: Boolean = false
40
}
41
42
override fun prepare(block: Config.() -> Unit): CustomLogging {
43
val config = Config().apply(block)
44
return CustomLogging()
45
}
46
47
override fun install(plugin: CustomLogging, scope: HttpClient) {
48
// Install interceptors and setup plugin logic
49
}
50
}
51
```
52
53
### Plugin Installation
54
55
Methods for installing and configuring plugins in HTTP client configuration.
56
57
```kotlin { .api }
58
/**
59
* Install plugin with configuration in HttpClientConfig
60
*/
61
fun <TBuilder : Any, TPlugin : Any> HttpClientConfig<*>.install(
62
plugin: HttpClientPlugin<TBuilder, TPlugin>,
63
configure: TBuilder.() -> Unit = {}
64
)
65
66
/**
67
* Install custom interceptor with string key
68
*/
69
fun HttpClientConfig<*>.install(key: String, block: HttpClient.() -> Unit)
70
```
71
72
**Usage Examples:**
73
74
```kotlin
75
import io.ktor.client.*
76
import io.ktor.client.plugins.contentnegotiation.*
77
import io.ktor.client.plugins.logging.*
78
import io.ktor.serialization.kotlinx.json.*
79
80
val client = HttpClient {
81
// Install plugin with configuration
82
install(Logging) {
83
logger = Logger.DEFAULT
84
level = LogLevel.INFO
85
filter { request ->
86
request.url.host.contains("api.example.com")
87
}
88
}
89
90
// Install content negotiation
91
install(ContentNegotiation) {
92
json(Json {
93
prettyPrint = true
94
isLenient = true
95
ignoreUnknownKeys = true
96
})
97
}
98
99
// Install custom plugin
100
install(CustomLogging) {
101
logLevel = LogLevel.DEBUG
102
includeHeaders = true
103
}
104
105
// Install custom interceptor
106
install("RequestIdGenerator") {
107
requestPipeline.intercept(HttpRequestPipeline.Before) {
108
context.headers.append("X-Request-ID", generateRequestId())
109
}
110
}
111
}
112
```
113
114
### Plugin Access
115
116
Functions for accessing installed plugins from client instances.
117
118
```kotlin { .api }
119
/**
120
* Get installed plugin instance (nullable)
121
*/
122
fun <B : Any, F : Any> HttpClient.pluginOrNull(plugin: HttpClientPlugin<B, F>): F?
123
124
/**
125
* Get installed plugin instance (throws if not found)
126
*/
127
fun <B : Any, F : Any> HttpClient.plugin(plugin: HttpClientPlugin<B, F>): F
128
```
129
130
**Usage Examples:**
131
132
```kotlin
133
val client = HttpClient {
134
install(Logging) {
135
level = LogLevel.INFO
136
}
137
}
138
139
// Access plugin safely
140
val loggingPlugin = client.pluginOrNull(Logging)
141
if (loggingPlugin != null) {
142
// Use plugin instance
143
println("Logging plugin is installed")
144
}
145
146
// Access plugin (throws if not installed)
147
try {
148
val logging = client.plugin(Logging)
149
// Use plugin instance
150
} catch (e: IllegalStateException) {
151
println("Logging plugin not installed")
152
}
153
```
154
155
### Built-in Core Plugins
156
157
Essential plugins that are automatically installed or commonly used.
158
159
```kotlin { .api }
160
// Always installed plugins
161
object HttpSend : HttpClientPlugin<Unit, HttpSend>
162
object HttpCallValidator : HttpClientPlugin<HttpCallValidator.Config, HttpCallValidator>
163
object HttpRequestLifecycle : HttpClientPlugin<Unit, HttpRequestLifecycle>
164
object BodyProgress : HttpClientPlugin<Unit, BodyProgress>
165
166
// Optional core plugins
167
object HttpPlainText : HttpClientPlugin<HttpPlainText.Config, HttpPlainText>
168
object DefaultRequest : HttpClientPlugin<DefaultRequest.DefaultRequestBuilder, DefaultRequest>
169
object UserAgent : HttpClientPlugin<UserAgent.Config, UserAgent>
170
```
171
172
**Usage Examples:**
173
174
```kotlin
175
// These plugins are automatically installed:
176
// - HttpSend: Request sending pipeline
177
// - HttpCallValidator: Response validation
178
// - HttpRequestLifecycle: Request lifecycle management
179
// - BodyProgress: Progress tracking
180
181
// Optional plugins you can install:
182
val client = HttpClient {
183
install(HttpPlainText) {
184
// Plain text content handling configuration
185
charset = Charsets.UTF_8
186
sendCharset = Charsets.UTF_8
187
}
188
189
install(DefaultRequest) {
190
// Default request configuration
191
host = "api.example.com"
192
port = 443
193
url {
194
protocol = URLProtocol.HTTPS
195
}
196
headers {
197
append(HttpHeaders.UserAgent, "MyApp/1.0")
198
}
199
}
200
201
install(UserAgent) {
202
agent = "MyApp/1.0 (Ktor Client)"
203
}
204
}
205
```
206
207
### Timeout Plugin
208
209
Comprehensive timeout management for requests, connections, and sockets.
210
211
```kotlin { .api }
212
/**
213
* HTTP timeout management plugin
214
*/
215
object HttpTimeout : HttpClientPlugin<HttpTimeoutCapabilityConfiguration, HttpTimeout> {
216
override val key: AttributeKey<HttpTimeout> = AttributeKey("HttpTimeout")
217
218
/** Infinite timeout constant */
219
const val INFINITE_TIMEOUT_MS: Long = Long.MAX_VALUE
220
}
221
222
/**
223
* Timeout configuration class
224
*/
225
class HttpTimeoutCapabilityConfiguration {
226
/** Request timeout in milliseconds */
227
var requestTimeoutMillis: Long? = null
228
229
/** Connection timeout in milliseconds */
230
var connectTimeoutMillis: Long? = null
231
232
/** Socket timeout in milliseconds */
233
var socketTimeoutMillis: Long? = null
234
}
235
236
/**
237
* Configure timeout for specific request
238
*/
239
fun HttpRequestBuilder.timeout(block: HttpTimeoutCapabilityConfiguration.() -> Unit)
240
```
241
242
**Usage Examples:**
243
244
```kotlin
245
import io.ktor.client.plugins.*
246
247
// Install timeout plugin globally
248
val client = HttpClient {
249
install(HttpTimeout) {
250
requestTimeoutMillis = 30_000
251
connectTimeoutMillis = 10_000
252
socketTimeoutMillis = 15_000
253
}
254
}
255
256
// Configure timeout per request
257
val response = client.get("https://api.example.com/slow-endpoint") {
258
timeout {
259
requestTimeoutMillis = 60_000 // 1 minute for slow endpoint
260
socketTimeoutMillis = HttpTimeout.INFINITE_TIMEOUT_MS
261
}
262
}
263
264
// Handle timeout exceptions
265
try {
266
val response = client.get("https://api.example.com/data")
267
} catch (e: HttpRequestTimeoutException) {
268
println("Request timed out: ${e.message}")
269
} catch (e: ConnectTimeoutException) {
270
println("Connection timed out: ${e.message}")
271
} catch (e: SocketTimeoutException) {
272
println("Socket timed out: ${e.message}")
273
}
274
```
275
276
### Request Retry Plugin
277
278
Automatic retry mechanism for failed HTTP requests with configurable retry policies, delays, and request modification.
279
280
```kotlin { .api }
281
/**
282
* HTTP request retry plugin
283
*/
284
object HttpRequestRetry : HttpClientPlugin<HttpRequestRetry.Configuration, HttpRequestRetry> {
285
override val key: AttributeKey<HttpRequestRetry> = AttributeKey("HttpRequestRetry")
286
287
/**
288
* Retry configuration class
289
*/
290
class Configuration {
291
/** Maximum number of retries (default: 3) */
292
var maxRetries: Int = 3
293
294
/** Delay function for calculating retry delay */
295
var delayMillis: DelayContext.(Int) -> Long = { retry -> 1000L * (retry - 1) }
296
297
/** Custom delay function (default: kotlinx.coroutines.delay) */
298
var delay: suspend (Long) -> Unit = { kotlinx.coroutines.delay(it) }
299
300
/** Predicate to determine if response should trigger retry */
301
var shouldRetry: ShouldRetryContext.(HttpRequest, HttpResponse) -> Boolean = { _, _ -> false }
302
303
/** Predicate to determine if exception should trigger retry */
304
var shouldRetryOnException: ShouldRetryContext.(HttpRequestBuilder, Throwable) -> Boolean = { _, _ -> false }
305
306
/** Request modification before retry */
307
var modifyRequest: ModifyRequestContext.(HttpRequestBuilder) -> Unit = {}
308
309
/** Predefined policy: retry on server errors (5xx) */
310
fun retryOnServerErrors(maxRetries: Int = 3)
311
312
/** Predefined policy: retry on connection failures */
313
fun retryOnConnectionFailure(maxRetries: Int = 3)
314
315
/** Predefined delay policy: exponential backoff */
316
fun exponentialDelay(
317
base: Double = 2.0,
318
maxDelayMs: Long = 60000,
319
randomizationMs: Long = 1000
320
)
321
322
/** Predefined delay policy: constant delay */
323
fun constantDelay(delayMs: Long = 1000)
324
325
/** Custom retry condition based on response */
326
fun retryIf(block: ShouldRetryContext.(HttpRequest, HttpResponse) -> Boolean)
327
328
/** Custom retry condition based on exception */
329
fun retryOnExceptionIf(block: ShouldRetryContext.(HttpRequestBuilder, Throwable) -> Boolean)
330
331
/** Custom request modification */
332
fun modifyRequest(block: ModifyRequestContext.(HttpRequestBuilder) -> Unit)
333
}
334
335
/** Context classes */
336
class ShouldRetryContext(val retryCount: Int)
337
class DelayContext(val request: HttpRequestBuilder, val response: HttpResponse?, val cause: Throwable?)
338
class ModifyRequestContext(
339
val request: HttpRequestBuilder,
340
val response: HttpResponse?,
341
val cause: Throwable?,
342
val retryCount: Int
343
)
344
class RetryEventData(
345
val request: HttpRequestBuilder,
346
val retryCount: Int,
347
val response: HttpResponse?,
348
val cause: Throwable?
349
)
350
}
351
352
/** Event fired when request is being retried */
353
val HttpRequestRetryEvent: EventDefinition<HttpRequestRetry.RetryEventData>
354
355
/** Configure retry for specific request */
356
fun HttpRequestBuilder.retry(block: HttpRequestRetry.Configuration.() -> Unit)
357
```
358
359
**Usage Examples:**
360
361
```kotlin
362
import io.ktor.client.plugins.*
363
364
// Basic retry with server errors and exponential backoff
365
val client = HttpClient {
366
install(HttpRequestRetry) {
367
retryOnServerErrors(maxRetries = 3)
368
exponentialDelay()
369
}
370
}
371
372
// Advanced custom retry configuration
373
val client = HttpClient {
374
install(HttpRequestRetry) {
375
maxRetries = 5
376
377
// Retry on specific status codes
378
retryIf { request, response ->
379
response.status.value in 500..599 || response.status.value == 429
380
}
381
382
// Retry on network errors
383
retryOnExceptionIf { request, cause ->
384
cause is ConnectTimeoutException || cause is SocketTimeoutException
385
}
386
387
// Custom delay with jitter
388
delayMillis { retry ->
389
(retry * 1000L) + Random.nextLong(0, 500)
390
}
391
392
// Modify request before retry (e.g., add retry headers)
393
modifyRequest { request ->
394
request.headers.append("X-Retry-Count", retryCount.toString())
395
}
396
}
397
}
398
399
// Per-request retry configuration
400
val response = client.get("https://api.example.com/data") {
401
retry {
402
maxRetries = 2
403
constantDelay(2000) // 2 second delay between retries
404
}
405
}
406
407
// Listen to retry events
408
client.monitor.subscribe(HttpRequestRetryEvent) { retryData ->
409
println("Retrying request ${retryData.retryCount} time(s)")
410
}
411
412
// Handle retry exhaustion
413
try {
414
val response = client.get("https://unreliable-api.example.com/data")
415
} catch (e: SendCountExceedException) {
416
println("Max retries exceeded: ${e.message}")
417
}
418
```
419
420
### Redirect Plugin
421
422
HTTP redirect handling with configurable redirect policies.
423
424
```kotlin { .api }
425
/**
426
* HTTP redirect handling plugin
427
*/
428
object HttpRedirect : HttpClientPlugin<HttpRedirect.Config, HttpRedirect> {
429
override val key: AttributeKey<HttpRedirect> = AttributeKey("HttpRedirect")
430
431
/** Event fired when redirect occurs */
432
val HttpResponseRedirect: EventDefinition<HttpResponse>
433
434
/**
435
* Redirect configuration
436
*/
437
class Config {
438
/** Check HTTP method for redirects (default: true) */
439
var checkHttpMethod: Boolean = true
440
441
/** Allow HTTPS to HTTP downgrade (default: false) */
442
var allowHttpsDowngrade: Boolean = false
443
}
444
}
445
```
446
447
**Usage Examples:**
448
449
```kotlin
450
val client = HttpClient {
451
install(HttpRedirect) {
452
checkHttpMethod = true // Only redirect GET/HEAD by default
453
allowHttpsDowngrade = false // Prevent HTTPS -> HTTP redirects
454
}
455
456
// Monitor redirect events
457
monitor.subscribe(HttpRedirect.HttpResponseRedirect) { response ->
458
println("Redirected to: ${response.request.url}")
459
}
460
}
461
462
// Client automatically follows redirects
463
val response = client.get("https://example.com/redirect-me")
464
println("Final URL: ${response.request.url}")
465
```
466
467
### Content Negotiation Plugin
468
469
Automatic serialization and deserialization of request/response bodies.
470
471
```kotlin { .api }
472
/**
473
* Content negotiation plugin for automatic serialization
474
* Note: This is typically provided by ktor-client-content-negotiation artifact
475
*/
476
object ContentNegotiation : HttpClientPlugin<ContentNegotiation.Config, ContentNegotiation> {
477
class Config {
478
fun json(json: Json = Json)
479
fun xml()
480
fun cbor()
481
// Additional serialization formats
482
}
483
}
484
```
485
486
**Usage Examples:**
487
488
```kotlin
489
import io.ktor.client.plugins.contentnegotiation.*
490
import io.ktor.serialization.kotlinx.json.*
491
import kotlinx.serialization.Serializable
492
493
@Serializable
494
data class User(val id: Int, val name: String, val email: String)
495
496
val client = HttpClient {
497
install(ContentNegotiation) {
498
json(Json {
499
prettyPrint = true
500
ignoreUnknownKeys = true
501
})
502
}
503
}
504
505
// Automatic serialization
506
val newUser = User(0, "John Doe", "john@example.com")
507
val response = client.post("https://api.example.com/users") {
508
contentType(ContentType.Application.Json)
509
setBody(newUser) // Automatically serialized to JSON
510
}
511
512
// Automatic deserialization
513
val users: List<User> = client.get("https://api.example.com/users").body()
514
```
515
516
### Custom Plugin Development
517
518
Guidelines and patterns for developing custom HTTP client plugins.
519
520
```kotlin { .api }
521
/**
522
* Example custom plugin structure
523
*/
524
object CustomPlugin : HttpClientPlugin<CustomPlugin.Config, CustomPlugin> {
525
override val key: AttributeKey<CustomPlugin> = AttributeKey("CustomPlugin")
526
527
class Config {
528
// Configuration properties
529
var enabled: Boolean = true
530
var customProperty: String = "default"
531
}
532
533
override fun prepare(block: Config.() -> Unit): CustomPlugin {
534
val config = Config().apply(block)
535
return CustomPlugin(config)
536
}
537
538
override fun install(plugin: CustomPlugin, scope: HttpClient) {
539
// Install request interceptors
540
scope.requestPipeline.intercept(HttpRequestPipeline.Before) {
541
// Modify request
542
}
543
544
// Install response interceptors
545
scope.responsePipeline.intercept(HttpResponsePipeline.Receive) {
546
// Process response
547
}
548
549
// Setup cleanup
550
scope.monitor.subscribe(HttpClientEvents.Closed) {
551
// Cleanup resources
552
}
553
}
554
}
555
556
class CustomPlugin(private val config: Config) {
557
// Plugin implementation
558
}
559
```
560
561
**Usage Examples:**
562
563
```kotlin
564
// Example: Request/Response logging plugin
565
object RequestResponseLogger : HttpClientPlugin<RequestResponseLogger.Config, RequestResponseLogger> {
566
override val key = AttributeKey<RequestResponseLogger>("RequestResponseLogger")
567
568
class Config {
569
var logRequests: Boolean = true
570
var logResponses: Boolean = true
571
var logger: (String) -> Unit = ::println
572
}
573
574
override fun prepare(block: Config.() -> Unit) = RequestResponseLogger(Config().apply(block))
575
576
override fun install(plugin: RequestResponseLogger, scope: HttpClient) {
577
if (plugin.config.logRequests) {
578
scope.requestPipeline.intercept(HttpRequestPipeline.Before) {
579
plugin.config.logger("Request: ${context.method} ${context.url}")
580
}
581
}
582
583
if (plugin.config.logResponses) {
584
scope.responsePipeline.intercept(HttpResponsePipeline.Receive) {
585
plugin.config.logger("Response: ${context.status}")
586
}
587
}
588
}
589
}
590
591
class RequestResponseLogger(val config: Config)
592
593
// Usage
594
val client = HttpClient {
595
install(RequestResponseLogger) {
596
logRequests = true
597
logResponses = true
598
logger = { message ->
599
println("[HTTP] $message")
600
}
601
}
602
}
603
```
604
605
### Plugin Pipeline Integration
606
607
Understanding how plugins integrate with request/response pipelines.
608
609
```kotlin { .api }
610
/**
611
* Request pipeline phases where plugins can intercept
612
*/
613
object HttpRequestPipeline {
614
val Before: PipelinePhase
615
val State: PipelinePhase
616
val Transform: PipelinePhase
617
val Render: PipelinePhase
618
val Send: PipelinePhase
619
}
620
621
/**
622
* Response pipeline phases where plugins can intercept
623
*/
624
object HttpResponsePipeline {
625
val Receive: PipelinePhase
626
val Parse: PipelinePhase
627
val Transform: PipelinePhase
628
val State: PipelinePhase
629
val After: PipelinePhase
630
}
631
```
632
633
**Usage Examples:**
634
635
```kotlin
636
// Plugin intercepting at different pipeline phases
637
scope.requestPipeline.intercept(HttpRequestPipeline.Before) {
638
// Early request modification
639
context.headers.append("X-Early-Header", "value")
640
}
641
642
scope.requestPipeline.intercept(HttpRequestPipeline.State) {
643
// Request state management
644
context.attributes.put(StateKey, "some-state")
645
}
646
647
scope.responsePipeline.intercept(HttpResponsePipeline.Receive) {
648
// Early response processing
649
if (context.status == HttpStatusCode.Unauthorized) {
650
// Handle auth refresh
651
}
652
}
653
654
scope.responsePipeline.intercept(HttpResponsePipeline.After) {
655
// Final response processing
656
logResponseMetrics(context)
657
}
658
```
659
660
### Progress Tracking Plugin
661
662
Upload and download progress monitoring with customizable progress listeners.
663
664
```kotlin { .api }
665
/**
666
* Body progress tracking plugin
667
*/
668
object BodyProgress : HttpClientPlugin<Unit, BodyProgress> {
669
override val key: AttributeKey<BodyProgress> = AttributeKey("BodyProgress")
670
}
671
672
/** Progress listener typealias */
673
typealias ProgressListener = suspend (bytesSentTotal: Long, contentLength: Long) -> Unit
674
675
/** Configure upload progress tracking */
676
fun HttpRequestBuilder.onUpload(listener: ProgressListener?)
677
678
/** Configure download progress tracking */
679
fun HttpRequestBuilder.onDownload(listener: ProgressListener?)
680
```
681
682
**Usage Examples:**
683
684
```kotlin
685
val client = HttpClient {
686
install(BodyProgress)
687
}
688
689
// Track upload progress
690
val response = client.post("https://api.example.com/upload") {
691
setBody(fileContent)
692
onUpload { bytesSentTotal, contentLength ->
693
val progress = (bytesSentTotal.toDouble() / contentLength * 100).toInt()
694
println("Upload progress: $progress% ($bytesSentTotal/$contentLength bytes)")
695
}
696
}
697
698
// Track download progress
699
val response = client.get("https://example.com/large-file.zip") {
700
onDownload { bytesReceivedTotal, contentLength ->
701
val progress = if (contentLength > 0) {
702
(bytesReceivedTotal.toDouble() / contentLength * 100).toInt()
703
} else {
704
-1 // Unknown content length
705
}
706
if (progress >= 0) {
707
println("Download progress: $progress% ($bytesReceivedTotal/$contentLength bytes)")
708
} else {
709
println("Downloaded: $bytesReceivedTotal bytes")
710
}
711
}
712
}
713
714
// File upload with progress
715
val file = File("document.pdf")
716
val response = client.post("https://api.example.com/documents") {
717
setBody(file.readBytes())
718
onUpload { sent, total ->
719
println("Uploading ${file.name}: ${(sent * 100 / total)}%")
720
}
721
}
722
```
723
724
### Default Request Plugin
725
726
Configures default request parameters applied to all requests made by the client.
727
728
```kotlin { .api }
729
/**
730
* Default request configuration plugin
731
*/
732
object DefaultRequest : HttpClientPlugin<DefaultRequest.DefaultRequestBuilder, DefaultRequest> {
733
override val key: AttributeKey<DefaultRequest> = AttributeKey("DefaultRequest")
734
735
/**
736
* Default request builder for configuring defaults
737
*/
738
class DefaultRequestBuilder : HttpRequestBuilder() {
739
fun host(value: String)
740
fun port(value: Int)
741
fun headers(block: HeadersBuilder.() -> Unit)
742
fun cookie(name: String, value: String, encoding: CookieEncoding = CookieEncoding.URI_ENCODING)
743
}
744
}
745
746
/** Configure default request settings in client config */
747
fun HttpClientConfig<*>.defaultRequest(block: DefaultRequest.DefaultRequestBuilder.() -> Unit)
748
```
749
750
**Usage Examples:**
751
752
```kotlin
753
val client = HttpClient {
754
install(DefaultRequest) {
755
// Default host and port
756
host = "api.example.com"
757
port = 443
758
url.protocol = URLProtocol.HTTPS
759
760
// Default headers
761
headers {
762
append("User-Agent", "MyApp/1.0")
763
append("Accept", "application/json")
764
}
765
766
// Default authentication
767
bearerAuth("default-token")
768
769
// Default parameters
770
parameter("version", "v1")
771
parameter("format", "json")
772
}
773
}
774
775
// All requests will inherit defaults
776
val response1 = client.get("/users") // GET https://api.example.com:443/users?version=v1&format=json
777
val response2 = client.post("/users") { // POST with default headers and auth
778
contentType(ContentType.Application.Json)
779
setBody(newUser)
780
}
781
782
// Override defaults per request
783
val response3 = client.get("https://other-api.com/data") {
784
// This overrides the default host
785
bearerAuth("different-token") // Override default auth
786
}
787
788
// Alternative configuration using defaultRequest extension
789
val client2 = HttpClient {
790
defaultRequest {
791
url("https://jsonplaceholder.typicode.com/")
792
header("X-Custom", "value")
793
}
794
}
795
```
796
797
### Plugin Creation APIs
798
799
Modern plugin creation utilities for simplified plugin development with DSL support.
800
801
```kotlin { .api }
802
/**
803
* Creates a client plugin with configuration support
804
*/
805
fun <PluginConfigT> createClientPlugin(
806
name: String,
807
createConfiguration: () -> PluginConfigT,
808
body: ClientPluginBuilder<PluginConfigT>.() -> Unit
809
): ClientPlugin<PluginConfigT>
810
811
/**
812
* Creates a client plugin without configuration
813
*/
814
fun createClientPlugin(
815
name: String,
816
body: ClientPluginBuilder<Unit>.() -> Unit
817
): ClientPlugin<Unit>
818
819
/**
820
* Simplified plugin interface
821
*/
822
interface ClientPlugin<PluginConfig : Any> {
823
val key: AttributeKey<*>
824
fun prepare(block: PluginConfig.() -> Unit = {}): Any
825
fun install(plugin: Any, scope: HttpClient)
826
}
827
828
/**
829
* Plugin builder DSL for simplified plugin creation
830
*/
831
class ClientPluginBuilder<PluginConfig : Any> {
832
/** Plugin configuration hook */
833
fun onRequest(block: suspend OnRequestContext.(request: HttpRequestBuilder, config: PluginConfig) -> Unit)
834
835
/** Response processing hook */
836
fun onResponse(block: suspend OnResponseContext.(call: HttpClientCall, config: PluginConfig) -> Unit)
837
838
/** Request body transformation hook */
839
fun transformRequestBody(block: suspend TransformRequestBodyContext.(request: HttpRequestBuilder, config: PluginConfig) -> Unit)
840
841
/** Response body transformation hook */
842
fun transformResponseBody(block: suspend TransformResponseBodyContext.(call: HttpClientCall, config: PluginConfig) -> Unit)
843
844
/** Plugin installation hook */
845
fun onInstall(block: (HttpClient, PluginConfig) -> Unit)
846
847
/** Plugin close hook */
848
fun onClose(block: (PluginConfig) -> Unit)
849
}
850
851
/** Context classes for plugin hooks */
852
class OnRequestContext(val client: HttpClient)
853
class OnResponseContext(val client: HttpClient)
854
class TransformRequestBodyContext(val client: HttpClient)
855
class TransformResponseBodyContext(val client: HttpClient)
856
857
/** Plugin access functions */
858
fun <B : Any, F : Any> HttpClient.pluginOrNull(plugin: HttpClientPlugin<B, F>): F?
859
fun <B : Any, F : Any> HttpClient.plugin(plugin: HttpClientPlugin<B, F>): F
860
```
861
862
**Usage Examples:**
863
864
```kotlin
865
import io.ktor.client.plugins.api.*
866
867
// Simple plugin without configuration
868
val RequestLogging = createClientPlugin("RequestLogging") {
869
onRequest { request, _ ->
870
println("Making request to: ${request.url}")
871
}
872
873
onResponse { call, _ ->
874
println("Received response: ${call.response.status}")
875
}
876
}
877
878
// Plugin with configuration
879
val CustomHeaders = createClientPlugin(
880
name = "CustomHeaders",
881
createConfiguration = { CustomHeadersConfig() }
882
) {
883
val headers = mutableMapOf<String, String>()
884
885
onInstall { client, config ->
886
headers.putAll(config.headers)
887
}
888
889
onRequest { request, config ->
890
headers.forEach { (name, value) ->
891
request.headers[name] = value
892
}
893
}
894
}
895
896
data class CustomHeadersConfig(
897
val headers: MutableMap<String, String> = mutableMapOf()
898
) {
899
fun header(name: String, value: String) {
900
headers[name] = value
901
}
902
}
903
904
// Install and use plugins
905
val client = HttpClient {
906
install(RequestLogging)
907
908
install(CustomHeaders) {
909
header("X-Custom-Client", "MyApp/1.0")
910
header("X-Request-ID", UUID.randomUUID().toString())
911
}
912
}
913
914
// Access plugin instance
915
val customHeaders = client.pluginOrNull(CustomHeaders)
916
if (customHeaders != null) {
917
println("CustomHeaders plugin is installed")
918
}
919
```