or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

built-in-plugins.mdengine-configuration.mdform-data-content.mdhttp-client.mdindex.mdplugin-system.mdrequest-building.mdresponse-handling.md

plugin-system.mddocs/

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

```