Core server functionality for the Ktor asynchronous web framework, providing essential building blocks for HTTP servers including application lifecycle management, routing foundations, request/response handling, and plugin architecture specifically compiled for JVM targets.
—
Plugin architecture for extending Ktor functionality with application-level and route-scoped plugins, providing lifecycle management, configuration systems, and extensible middleware patterns.
Core plugin interfaces and implementation patterns for creating reusable extensions to Ktor applications.
/**
* Base plugin interface for all Ktor plugins
*/
interface Plugin<
in TPipeline : Pipeline<*, PipelineCall>,
out TConfiguration : Any,
TPlugin : Any
> {
/** Unique key for plugin identification */
val key: AttributeKey<TPlugin>
/** Install plugin into pipeline */
fun install(pipeline: TPipeline, configure: TConfiguration.() -> Unit): TPlugin
}
/**
* Interface for application-level plugins
*/
interface BaseApplicationPlugin<
TPipeline : ApplicationCallPipeline,
TConfiguration : Any,
TPlugin : Any
> : Plugin<TPipeline, TConfiguration, TPlugin>
/**
* Interface for simple application plugins
*/
interface ApplicationPlugin<TConfiguration : Any> :
BaseApplicationPlugin<ApplicationCallPipeline, TConfiguration, PluginInstance>
/**
* Interface for route-scoped plugins
*/
interface BaseRouteScopedPlugin<TConfiguration : Any, TPlugin : Any> :
Plugin<ApplicationCallPipeline, TConfiguration, TPlugin>
/**
* Interface for simple route-scoped plugins
*/
interface RouteScopedPlugin<TConfiguration : Any> :
BaseRouteScopedPlugin<TConfiguration, PluginInstance>
/**
* Builder for creating plugin configurations
*/
class PluginBuilder<TConfiguration : Any> {
/** Plugin configuration instance */
var pluginConfig: TConfiguration? = null
/** Called during call processing */
fun onCall(block: suspend PipelineContext<Unit, ApplicationCall>.(ApplicationCall) -> Unit)
/** Called when receiving request content */
fun onCallReceive(block: suspend PipelineContext<ApplicationReceiveRequest, ApplicationCall>.(ApplicationReceiveRequest) -> Unit)
/** Called when sending response content */
fun onCallRespond(block: suspend PipelineContext<Any, ApplicationCall>.(Any) -> Unit)
}
/**
* Builder for creating route-scoped plugin configurations
*/
class RouteScopedPluginBuilder<TConfiguration : Any> {
/** Plugin configuration instance */
var pluginConfig: TConfiguration? = null
/** Called during call processing */
fun onCall(block: suspend PipelineContext<Unit, ApplicationCall>.(ApplicationCall) -> Unit)
/** Called when receiving request content */
fun onCallReceive(block: suspend PipelineContext<ApplicationReceiveRequest, ApplicationCall>.(ApplicationReceiveRequest) -> Unit)
/** Called when sending response content */
fun onCallRespond(block: suspend PipelineContext<Any, ApplicationCall>.(Any) -> Unit)
}Factory functions for creating custom application and route-scoped plugins with configuration support.
/**
* Creates an application plugin with configuration
* @param name - Plugin name for identification
* @param createConfiguration - Function to create default configuration
* @param body - Plugin builder configuration
*/
fun <PluginConfigT : Any> createApplicationPlugin(
name: String,
createConfiguration: () -> PluginConfigT,
body: PluginBuilder<PluginConfigT>.() -> Unit
): ApplicationPlugin<PluginConfigT>
/**
* Creates an application plugin with configuration path
* @param name - Plugin name for identification
* @param configurationPath - Path in configuration file to plugin config
* @param createConfiguration - Function to create configuration from ApplicationConfig
* @param body - Plugin builder configuration
*/
fun <PluginConfigT : Any> createApplicationPlugin(
name: String,
configurationPath: String,
createConfiguration: (config: ApplicationConfig) -> PluginConfigT,
body: PluginBuilder<PluginConfigT>.() -> Unit
): ApplicationPlugin<PluginConfigT>
/**
* Creates a simple application plugin without configuration
* @param name - Plugin name
* @param body - Plugin installation logic
*/
fun createApplicationPlugin(
name: String,
body: PluginBuilder<Unit>.() -> Unit
): ApplicationPlugin<Unit>
/**
* Creates a route-scoped plugin with configuration
* @param name - Plugin name for identification
* @param createConfiguration - Function to create default configuration
* @param body - Plugin builder configuration
*/
fun <PluginConfigT : Any> createRouteScopedPlugin(
name: String,
createConfiguration: () -> PluginConfigT,
body: RouteScopedPluginBuilder<PluginConfigT>.() -> Unit
): RouteScopedPlugin<PluginConfigT>
/**
* Creates a simple route-scoped plugin without configuration
* @param name - Plugin name
* @param body - Plugin installation logic
*/
fun createRouteScopedPlugin(
name: String,
body: RouteScopedPluginBuilder<Unit>.() -> Unit
): RouteScopedPlugin<Unit>Functions for installing plugins into applications and routes with configuration support.
/**
* Install application plugin with configuration
* @param plugin - Plugin to install
* @param configure - Configuration block
*/
fun <TConfiguration : Any> Application.install(
plugin: ApplicationPlugin<TConfiguration>,
configure: TConfiguration.() -> Unit = {}
): PluginInstance
/**
* Install base application plugin with configuration
* @param plugin - Plugin to install
* @param configure - Configuration block
*/
fun <TConfiguration : Any, TPlugin : Any> ApplicationCallPipeline.install(
plugin: BaseApplicationPlugin<*, TConfiguration, TPlugin>,
configure: TConfiguration.() -> Unit = {}
): TPlugin
/**
* Get installed plugin instance
* @param plugin - Plugin to get
* @return Plugin instance or null if not installed
*/
fun <TPlugin : Any> ApplicationCallPipeline.pluginOrNull(
plugin: Plugin<*, *, TPlugin>
): TPlugin?
/**
* Get installed plugin instance
* @param plugin - Plugin to get
* @return Plugin instance
* @throws PluginNotInstalledException if plugin not installed
*/
fun <TPlugin : Any> ApplicationCallPipeline.plugin(
plugin: Plugin<*, *, TPlugin>
): TPlugin
/**
* Install route-scoped plugin with configuration
* @param plugin - Plugin to install
* @param configure - Configuration block
*/
fun <TConfiguration : Any> Route.install(
plugin: RouteScopedPlugin<TConfiguration, *>,
configure: TConfiguration.() -> Unit = {}
)
/**
* Get installed plugin instance
* @param plugin - Plugin key to retrieve
* @return Plugin instance
*/
fun <A : Any, B : Any> Application.plugin(plugin: Plugin<A, B, *>): B
/**
* Check if plugin is installed
* @param plugin - Plugin to check
* @return True if plugin is installed
*/
fun Application.pluginOrNull(plugin: Plugin<*, *, *>): Any?Standard exception types for plugin error handling and request validation.
/**
* Exception for bad request errors (400)
*/
class BadRequestException(message: String, cause: Throwable? = null) : Exception(message, cause)
/**
* Exception for not found errors (404)
*/
class NotFoundException(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
/**
* Exception for unsupported media type errors (415)
*/
class UnsupportedMediaTypeException(contentType: ContentType) : Exception("Content type $contentType is not supported")
/**
* Exception for missing request parameters
*/
class MissingRequestParameterException(parameterName: String) : BadRequestException("Request parameter $parameterName is missing")
/**
* Exception for parameter conversion errors
*/
class ParameterConversionException(
parameterName: String,
type: String,
cause: Throwable? = null
) : BadRequestException("Value for parameter $parameterName cannot be converted to $type", cause)
/**
* Exception for configuration errors
*/
class ConfigurationException(message: String, cause: Throwable? = null) : Exception(message, cause)Base classes and patterns for plugin configuration with validation and type safety.
/**
* Base interface for plugin configurations
*/
interface PluginConfiguration
/**
* Configuration builder with validation
*/
abstract class ConfigurationBuilder {
/** Validate configuration before installation */
abstract fun validate()
/** Build final configuration */
abstract fun build(): PluginConfiguration
}
/**
* Attribute key for storing plugin instances
*/
data class AttributeKey<T>(val name: String) {
companion object {
/** Create new attribute key */
fun <T> create(name: String): AttributeKey<T> = AttributeKey(name)
}
}Hooks for plugin lifecycle management and integration with application events.
/**
* Plugin lifecycle events
*/
interface PluginLifecycle {
/** Called after plugin installation */
fun onInstall() {}
/** Called when application starts */
fun onStart() {}
/** Called when application stops */
fun onStop() {}
/** Called when application configuration changes */
fun onConfigurationChange() {}
}
/**
* Hook definition for plugin events
*/
class PluginHook<T : Function<Unit>> {
/** Install event handler */
fun install(handler: T)
/** Uninstall event handler */
fun uninstall(handler: T)
}Usage Examples:
import io.ktor.server.application.*
import io.ktor.server.plugins.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
// Creating a simple application plugin
val RequestLoggingPlugin = createApplicationPlugin("RequestLogging") {
onInstall { pipeline ->
pipeline.intercept(ApplicationCallPipeline.Setup) {
val start = System.currentTimeMillis()
proceed()
val duration = System.currentTimeMillis() - start
application.log.info("${call.request.httpMethod.value} ${call.request.uri} - ${duration}ms")
}
}
}
// Creating a configurable application plugin
class RateLimitConfig {
var requestsPerMinute: Int = 60
var keyProvider: (ApplicationCall) -> String = { it.request.local.remoteHost }
}
val RateLimitPlugin = createApplicationPlugin(
name = "RateLimit",
createConfiguration = ::RateLimitConfig
) {
val config = pluginConfig as RateLimitConfig
val requestCounts = mutableMapOf<String, MutableList<Long>>()
onInstall { pipeline ->
pipeline.intercept(ApplicationCallPipeline.Plugins) {
val key = config.keyProvider(call)
val now = System.currentTimeMillis()
val windowStart = now - 60_000 // 1 minute window
// Clean old requests
requestCounts[key]?.removeAll { it < windowStart }
// Check rate limit
val requests = requestCounts.getOrPut(key) { mutableListOf() }
if (requests.size >= config.requestsPerMinute) {
call.respond(HttpStatusCode.TooManyRequests, "Rate limit exceeded")
return@intercept finish()
}
// Add current request
requests.add(now)
proceed()
}
}
}
// Creating a route-scoped plugin
class AuthConfig {
var validate: suspend (String) -> Boolean = { false }
var challenge: suspend ApplicationCall.() -> Unit = {
respond(HttpStatusCode.Unauthorized, "Authentication required")
}
}
val BasicAuthPlugin = createRouteScopedPlugin(
name = "BasicAuth",
createConfiguration = ::AuthConfig
) {
val config = pluginConfig as AuthConfig
onInstall { pipeline ->
pipeline.intercept(ApplicationCallPipeline.Plugins) {
val authHeader = call.request.headers["Authorization"]
if (authHeader?.startsWith("Basic ") != true) {
config.challenge(call)
return@intercept finish()
}
val token = authHeader.removePrefix("Basic ")
if (!config.validate(token)) {
config.challenge(call)
return@intercept finish()
}
proceed()
}
}
}
// Installing and using plugins
fun Application.configurePlugins() {
// Install simple plugin
install(RequestLoggingPlugin)
// Install configurable plugin
install(RateLimitPlugin) {
requestsPerMinute = 100
keyProvider = { call ->
call.request.headers["X-API-Key"] ?: call.request.local.remoteHost
}
}
}
fun Application.configureRouting() {
routing {
// Public routes
get("/") {
call.respondText("Welcome!")
}
get("/health") {
call.respondText("OK")
}
// Protected admin routes
route("/admin") {
install(BasicAuthPlugin) {
validate = { token ->
// Decode and validate Basic auth token
val decoded = Base64.getDecoder().decode(token).toString(Charsets.UTF_8)
val (username, password) = decoded.split(":", limit = 2)
username == "admin" && password == "secret"
}
challenge = {
response.headers.append("WWW-Authenticate", "Basic realm=\"Admin Area\"")
respond(HttpStatusCode.Unauthorized, "Admin access required")
}
}
get("/dashboard") {
call.respondText("Admin Dashboard")
}
get("/users") {
call.respondText("User Management")
}
}
// API routes with specific rate limiting
route("/api") {
install(RateLimitPlugin) {
requestsPerMinute = 30 // Lower limit for API
}
get("/data") {
call.respond(mapOf("data" to "API response"))
}
}
}
}
// Advanced plugin with lifecycle management
class DatabaseConnectionPlugin {
lateinit var connection: DatabaseConnection
companion object : ApplicationPlugin<DatabaseConfig, DatabaseConnectionPlugin, DatabaseConnectionPlugin> {
override val key = AttributeKey<DatabaseConnectionPlugin>("DatabaseConnection")
override fun install(
pipeline: ApplicationCallPipeline,
configure: DatabaseConfig.() -> Unit
): DatabaseConnectionPlugin {
val config = DatabaseConfig().apply(configure)
val plugin = DatabaseConnectionPlugin()
plugin.connection = createConnection(config)
// Add connection to call attributes
pipeline.intercept(ApplicationCallPipeline.Setup) {
call.attributes.put(DatabaseConnectionKey, plugin.connection)
}
return plugin
}
}
}
data class DatabaseConfig(
var url: String = "jdbc:h2:mem:test",
var driver: String = "org.h2.Driver",
var user: String = "sa",
var password: String = ""
)
val DatabaseConnectionKey = AttributeKey<DatabaseConnection>("DatabaseConnection")
// Usage with lifecycle
fun Application.configureDatabasePlugin() {
install(DatabaseConnectionPlugin) {
url = "jdbc:postgresql://localhost/mydb"
user = "dbuser"
password = "dbpass"
}
// Access database in routes
routing {
get("/users") {
val db = call.attributes[DatabaseConnectionKey]
val users = db.query("SELECT * FROM users")
call.respond(users)
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor-server-core-jvm