or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application.mdconfig.mdengine.mdindex.mdplugins.mdrequest.mdresponse.mdrouting.md

plugins.mddocs/

0

# Plugin System and Extensions

1

2

Plugin architecture for extending Ktor functionality with application-level and route-scoped plugins, providing lifecycle management, configuration systems, and extensible middleware patterns.

3

4

## Capabilities

5

6

### Plugin Architecture

7

8

Core plugin interfaces and implementation patterns for creating reusable extensions to Ktor applications.

9

10

```kotlin { .api }

11

/**

12

* Base plugin interface for all Ktor plugins

13

*/

14

interface Plugin<

15

in TPipeline : Pipeline<*, PipelineCall>,

16

out TConfiguration : Any,

17

TPlugin : Any

18

> {

19

/** Unique key for plugin identification */

20

val key: AttributeKey<TPlugin>

21

/** Install plugin into pipeline */

22

fun install(pipeline: TPipeline, configure: TConfiguration.() -> Unit): TPlugin

23

}

24

25

/**

26

* Interface for application-level plugins

27

*/

28

interface BaseApplicationPlugin<

29

TPipeline : ApplicationCallPipeline,

30

TConfiguration : Any,

31

TPlugin : Any

32

> : Plugin<TPipeline, TConfiguration, TPlugin>

33

34

/**

35

* Interface for simple application plugins

36

*/

37

interface ApplicationPlugin<TConfiguration : Any> :

38

BaseApplicationPlugin<ApplicationCallPipeline, TConfiguration, PluginInstance>

39

40

/**

41

* Interface for route-scoped plugins

42

*/

43

interface BaseRouteScopedPlugin<TConfiguration : Any, TPlugin : Any> :

44

Plugin<ApplicationCallPipeline, TConfiguration, TPlugin>

45

46

/**

47

* Interface for simple route-scoped plugins

48

*/

49

interface RouteScopedPlugin<TConfiguration : Any> :

50

BaseRouteScopedPlugin<TConfiguration, PluginInstance>

51

52

/**

53

* Builder for creating plugin configurations

54

*/

55

class PluginBuilder<TConfiguration : Any> {

56

/** Plugin configuration instance */

57

var pluginConfig: TConfiguration? = null

58

/** Called during call processing */

59

fun onCall(block: suspend PipelineContext<Unit, ApplicationCall>.(ApplicationCall) -> Unit)

60

/** Called when receiving request content */

61

fun onCallReceive(block: suspend PipelineContext<ApplicationReceiveRequest, ApplicationCall>.(ApplicationReceiveRequest) -> Unit)

62

/** Called when sending response content */

63

fun onCallRespond(block: suspend PipelineContext<Any, ApplicationCall>.(Any) -> Unit)

64

}

65

66

/**

67

* Builder for creating route-scoped plugin configurations

68

*/

69

class RouteScopedPluginBuilder<TConfiguration : Any> {

70

/** Plugin configuration instance */

71

var pluginConfig: TConfiguration? = null

72

/** Called during call processing */

73

fun onCall(block: suspend PipelineContext<Unit, ApplicationCall>.(ApplicationCall) -> Unit)

74

/** Called when receiving request content */

75

fun onCallReceive(block: suspend PipelineContext<ApplicationReceiveRequest, ApplicationCall>.(ApplicationReceiveRequest) -> Unit)

76

/** Called when sending response content */

77

fun onCallRespond(block: suspend PipelineContext<Any, ApplicationCall>.(Any) -> Unit)

78

}

79

```

80

81

### Plugin Creation

82

83

Factory functions for creating custom application and route-scoped plugins with configuration support.

84

85

```kotlin { .api }

86

/**

87

* Creates an application plugin with configuration

88

* @param name - Plugin name for identification

89

* @param createConfiguration - Function to create default configuration

90

* @param body - Plugin builder configuration

91

*/

92

fun <PluginConfigT : Any> createApplicationPlugin(

93

name: String,

94

createConfiguration: () -> PluginConfigT,

95

body: PluginBuilder<PluginConfigT>.() -> Unit

96

): ApplicationPlugin<PluginConfigT>

97

98

/**

99

* Creates an application plugin with configuration path

100

* @param name - Plugin name for identification

101

* @param configurationPath - Path in configuration file to plugin config

102

* @param createConfiguration - Function to create configuration from ApplicationConfig

103

* @param body - Plugin builder configuration

104

*/

105

fun <PluginConfigT : Any> createApplicationPlugin(

106

name: String,

107

configurationPath: String,

108

createConfiguration: (config: ApplicationConfig) -> PluginConfigT,

109

body: PluginBuilder<PluginConfigT>.() -> Unit

110

): ApplicationPlugin<PluginConfigT>

111

112

/**

113

* Creates a simple application plugin without configuration

114

* @param name - Plugin name

115

* @param body - Plugin installation logic

116

*/

117

fun createApplicationPlugin(

118

name: String,

119

body: PluginBuilder<Unit>.() -> Unit

120

): ApplicationPlugin<Unit>

121

122

/**

123

* Creates a route-scoped plugin with configuration

124

* @param name - Plugin name for identification

125

* @param createConfiguration - Function to create default configuration

126

* @param body - Plugin builder configuration

127

*/

128

fun <PluginConfigT : Any> createRouteScopedPlugin(

129

name: String,

130

createConfiguration: () -> PluginConfigT,

131

body: RouteScopedPluginBuilder<PluginConfigT>.() -> Unit

132

): RouteScopedPlugin<PluginConfigT>

133

134

/**

135

* Creates a simple route-scoped plugin without configuration

136

* @param name - Plugin name

137

* @param body - Plugin installation logic

138

*/

139

fun createRouteScopedPlugin(

140

name: String,

141

body: RouteScopedPluginBuilder<Unit>.() -> Unit

142

): RouteScopedPlugin<Unit>

143

```

144

145

### Plugin Installation

146

147

Functions for installing plugins into applications and routes with configuration support.

148

149

```kotlin { .api }

150

/**

151

* Install application plugin with configuration

152

* @param plugin - Plugin to install

153

* @param configure - Configuration block

154

*/

155

fun <TConfiguration : Any> Application.install(

156

plugin: ApplicationPlugin<TConfiguration>,

157

configure: TConfiguration.() -> Unit = {}

158

): PluginInstance

159

160

/**

161

* Install base application plugin with configuration

162

* @param plugin - Plugin to install

163

* @param configure - Configuration block

164

*/

165

fun <TConfiguration : Any, TPlugin : Any> ApplicationCallPipeline.install(

166

plugin: BaseApplicationPlugin<*, TConfiguration, TPlugin>,

167

configure: TConfiguration.() -> Unit = {}

168

): TPlugin

169

170

/**

171

* Get installed plugin instance

172

* @param plugin - Plugin to get

173

* @return Plugin instance or null if not installed

174

*/

175

fun <TPlugin : Any> ApplicationCallPipeline.pluginOrNull(

176

plugin: Plugin<*, *, TPlugin>

177

): TPlugin?

178

179

/**

180

* Get installed plugin instance

181

* @param plugin - Plugin to get

182

* @return Plugin instance

183

* @throws PluginNotInstalledException if plugin not installed

184

*/

185

fun <TPlugin : Any> ApplicationCallPipeline.plugin(

186

plugin: Plugin<*, *, TPlugin>

187

): TPlugin

188

189

/**

190

* Install route-scoped plugin with configuration

191

* @param plugin - Plugin to install

192

* @param configure - Configuration block

193

*/

194

fun <TConfiguration : Any> Route.install(

195

plugin: RouteScopedPlugin<TConfiguration, *>,

196

configure: TConfiguration.() -> Unit = {}

197

)

198

199

/**

200

* Get installed plugin instance

201

* @param plugin - Plugin key to retrieve

202

* @return Plugin instance

203

*/

204

fun <A : Any, B : Any> Application.plugin(plugin: Plugin<A, B, *>): B

205

206

/**

207

* Check if plugin is installed

208

* @param plugin - Plugin to check

209

* @return True if plugin is installed

210

*/

211

fun Application.pluginOrNull(plugin: Plugin<*, *, *>): Any?

212

```

213

214

### Built-in Plugin Exceptions

215

216

Standard exception types for plugin error handling and request validation.

217

218

```kotlin { .api }

219

/**

220

* Exception for bad request errors (400)

221

*/

222

class BadRequestException(message: String, cause: Throwable? = null) : Exception(message, cause)

223

224

/**

225

* Exception for not found errors (404)

226

*/

227

class NotFoundException(message: String? = null, cause: Throwable? = null) : Exception(message, cause)

228

229

/**

230

* Exception for unsupported media type errors (415)

231

*/

232

class UnsupportedMediaTypeException(contentType: ContentType) : Exception("Content type $contentType is not supported")

233

234

/**

235

* Exception for missing request parameters

236

*/

237

class MissingRequestParameterException(parameterName: String) : BadRequestException("Request parameter $parameterName is missing")

238

239

/**

240

* Exception for parameter conversion errors

241

*/

242

class ParameterConversionException(

243

parameterName: String,

244

type: String,

245

cause: Throwable? = null

246

) : BadRequestException("Value for parameter $parameterName cannot be converted to $type", cause)

247

248

/**

249

* Exception for configuration errors

250

*/

251

class ConfigurationException(message: String, cause: Throwable? = null) : Exception(message, cause)

252

```

253

254

### Plugin Configuration

255

256

Base classes and patterns for plugin configuration with validation and type safety.

257

258

```kotlin { .api }

259

/**

260

* Base interface for plugin configurations

261

*/

262

interface PluginConfiguration

263

264

/**

265

* Configuration builder with validation

266

*/

267

abstract class ConfigurationBuilder {

268

/** Validate configuration before installation */

269

abstract fun validate()

270

/** Build final configuration */

271

abstract fun build(): PluginConfiguration

272

}

273

274

/**

275

* Attribute key for storing plugin instances

276

*/

277

data class AttributeKey<T>(val name: String) {

278

companion object {

279

/** Create new attribute key */

280

fun <T> create(name: String): AttributeKey<T> = AttributeKey(name)

281

}

282

}

283

```

284

285

### Plugin Lifecycle Hooks

286

287

Hooks for plugin lifecycle management and integration with application events.

288

289

```kotlin { .api }

290

/**

291

* Plugin lifecycle events

292

*/

293

interface PluginLifecycle {

294

/** Called after plugin installation */

295

fun onInstall() {}

296

/** Called when application starts */

297

fun onStart() {}

298

/** Called when application stops */

299

fun onStop() {}

300

/** Called when application configuration changes */

301

fun onConfigurationChange() {}

302

}

303

304

/**

305

* Hook definition for plugin events

306

*/

307

class PluginHook<T : Function<Unit>> {

308

/** Install event handler */

309

fun install(handler: T)

310

/** Uninstall event handler */

311

fun uninstall(handler: T)

312

}

313

```

314

315

**Usage Examples:**

316

317

```kotlin

318

import io.ktor.server.application.*

319

import io.ktor.server.plugins.*

320

import io.ktor.server.response.*

321

import io.ktor.server.routing.*

322

323

// Creating a simple application plugin

324

val RequestLoggingPlugin = createApplicationPlugin("RequestLogging") {

325

onInstall { pipeline ->

326

pipeline.intercept(ApplicationCallPipeline.Setup) {

327

val start = System.currentTimeMillis()

328

329

proceed()

330

331

val duration = System.currentTimeMillis() - start

332

application.log.info("${call.request.httpMethod.value} ${call.request.uri} - ${duration}ms")

333

}

334

}

335

}

336

337

// Creating a configurable application plugin

338

class RateLimitConfig {

339

var requestsPerMinute: Int = 60

340

var keyProvider: (ApplicationCall) -> String = { it.request.local.remoteHost }

341

}

342

343

val RateLimitPlugin = createApplicationPlugin(

344

name = "RateLimit",

345

createConfiguration = ::RateLimitConfig

346

) {

347

val config = pluginConfig as RateLimitConfig

348

val requestCounts = mutableMapOf<String, MutableList<Long>>()

349

350

onInstall { pipeline ->

351

pipeline.intercept(ApplicationCallPipeline.Plugins) {

352

val key = config.keyProvider(call)

353

val now = System.currentTimeMillis()

354

val windowStart = now - 60_000 // 1 minute window

355

356

// Clean old requests

357

requestCounts[key]?.removeAll { it < windowStart }

358

359

// Check rate limit

360

val requests = requestCounts.getOrPut(key) { mutableListOf() }

361

if (requests.size >= config.requestsPerMinute) {

362

call.respond(HttpStatusCode.TooManyRequests, "Rate limit exceeded")

363

return@intercept finish()

364

}

365

366

// Add current request

367

requests.add(now)

368

proceed()

369

}

370

}

371

}

372

373

// Creating a route-scoped plugin

374

class AuthConfig {

375

var validate: suspend (String) -> Boolean = { false }

376

var challenge: suspend ApplicationCall.() -> Unit = {

377

respond(HttpStatusCode.Unauthorized, "Authentication required")

378

}

379

}

380

381

val BasicAuthPlugin = createRouteScopedPlugin(

382

name = "BasicAuth",

383

createConfiguration = ::AuthConfig

384

) {

385

val config = pluginConfig as AuthConfig

386

387

onInstall { pipeline ->

388

pipeline.intercept(ApplicationCallPipeline.Plugins) {

389

val authHeader = call.request.headers["Authorization"]

390

391

if (authHeader?.startsWith("Basic ") != true) {

392

config.challenge(call)

393

return@intercept finish()

394

}

395

396

val token = authHeader.removePrefix("Basic ")

397

if (!config.validate(token)) {

398

config.challenge(call)

399

return@intercept finish()

400

}

401

402

proceed()

403

}

404

}

405

}

406

407

// Installing and using plugins

408

fun Application.configurePlugins() {

409

// Install simple plugin

410

install(RequestLoggingPlugin)

411

412

// Install configurable plugin

413

install(RateLimitPlugin) {

414

requestsPerMinute = 100

415

keyProvider = { call ->

416

call.request.headers["X-API-Key"] ?: call.request.local.remoteHost

417

}

418

}

419

}

420

421

fun Application.configureRouting() {

422

routing {

423

// Public routes

424

get("/") {

425

call.respondText("Welcome!")

426

}

427

428

get("/health") {

429

call.respondText("OK")

430

}

431

432

// Protected admin routes

433

route("/admin") {

434

install(BasicAuthPlugin) {

435

validate = { token ->

436

// Decode and validate Basic auth token

437

val decoded = Base64.getDecoder().decode(token).toString(Charsets.UTF_8)

438

val (username, password) = decoded.split(":", limit = 2)

439

username == "admin" && password == "secret"

440

}

441

challenge = {

442

response.headers.append("WWW-Authenticate", "Basic realm=\"Admin Area\"")

443

respond(HttpStatusCode.Unauthorized, "Admin access required")

444

}

445

}

446

447

get("/dashboard") {

448

call.respondText("Admin Dashboard")

449

}

450

451

get("/users") {

452

call.respondText("User Management")

453

}

454

}

455

456

// API routes with specific rate limiting

457

route("/api") {

458

install(RateLimitPlugin) {

459

requestsPerMinute = 30 // Lower limit for API

460

}

461

462

get("/data") {

463

call.respond(mapOf("data" to "API response"))

464

}

465

}

466

}

467

}

468

469

// Advanced plugin with lifecycle management

470

class DatabaseConnectionPlugin {

471

lateinit var connection: DatabaseConnection

472

473

companion object : ApplicationPlugin<DatabaseConfig, DatabaseConnectionPlugin, DatabaseConnectionPlugin> {

474

override val key = AttributeKey<DatabaseConnectionPlugin>("DatabaseConnection")

475

476

override fun install(

477

pipeline: ApplicationCallPipeline,

478

configure: DatabaseConfig.() -> Unit

479

): DatabaseConnectionPlugin {

480

val config = DatabaseConfig().apply(configure)

481

val plugin = DatabaseConnectionPlugin()

482

483

plugin.connection = createConnection(config)

484

485

// Add connection to call attributes

486

pipeline.intercept(ApplicationCallPipeline.Setup) {

487

call.attributes.put(DatabaseConnectionKey, plugin.connection)

488

}

489

490

return plugin

491

}

492

}

493

}

494

495

data class DatabaseConfig(

496

var url: String = "jdbc:h2:mem:test",

497

var driver: String = "org.h2.Driver",

498

var user: String = "sa",

499

var password: String = ""

500

)

501

502

val DatabaseConnectionKey = AttributeKey<DatabaseConnection>("DatabaseConnection")

503

504

// Usage with lifecycle

505

fun Application.configureDatabasePlugin() {

506

install(DatabaseConnectionPlugin) {

507

url = "jdbc:postgresql://localhost/mydb"

508

user = "dbuser"

509

password = "dbpass"

510

}

511

512

// Access database in routes

513

routing {

514

get("/users") {

515

val db = call.attributes[DatabaseConnectionKey]

516

val users = db.query("SELECT * FROM users")

517

call.respond(users)

518

}

519

}

520

}

521

```