or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

client-configuration.mdcookie-management.mdengine-configuration.mdform-handling.mdhttp-caching.mdindex.mdplugin-system.mdrequest-building.mdresponse-handling.mdwebsocket-support.md

plugin-system.mddocs/

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

```