Ktor Server Core library providing foundational infrastructure for building asynchronous web applications and REST APIs with Kotlin
—
Ktor's server engine system provides flexible deployment options through multiple engine implementations. The engine architecture separates application logic from server infrastructure, enabling the same application to run on different server technologies.
interface ApplicationEngine {
val environment: ApplicationEnvironment
fun start(wait: Boolean = false): ApplicationEngine
suspend fun startSuspend(wait: Boolean = false): ApplicationEngine
fun stop(gracePeriodMillis: Long = 500, timeoutMillis: Long = 500)
suspend fun resolvedConnectors(): List<EngineConnectorConfig>
}// Start server and optionally wait
val engine = embeddedServer(Netty, port = 8080) {
routing {
get("/") { call.respondText("Hello!") }
}
}
// Start without blocking
engine.start(wait = false)
// Start with blocking (typical for main function)
engine.start(wait = true)
// Start asynchronously
runBlocking {
engine.startSuspend(wait = false)
// Do other work
delay(1000)
engine.stop(1000, 5000)
}
// Stop with grace period and timeout
engine.stop(
gracePeriodMillis = 1000, // Allow 1 second for graceful shutdown
timeoutMillis = 5000 // Force shutdown after 5 seconds
)interface ApplicationEngineFactory<TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configuration> {
fun create(
environment: ApplicationEnvironment,
configure: TConfiguration.() -> Unit = {}
): TEngine
}// Netty engine (high-performance NIO)
object Netty : ApplicationEngineFactory<NettyApplicationEngine, NettyApplicationEngine.Configuration>
// CIO engine (coroutine-based)
object CIO : ApplicationEngineFactory<CIOApplicationEngine, CIOApplicationEngine.Configuration>
// Jetty engine (traditional servlet)
object Jetty : ApplicationEngineFactory<JettyApplicationEngine, JettyApplicationEngine.Configuration>
// Tomcat engine (traditional servlet)
object Tomcat : ApplicationEngineFactory<TomcatApplicationEngine, TomcatApplicationEngine.Configuration>// Create embedded server with factory and configuration
fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configuration> embeddedServer(
factory: ApplicationEngineFactory<TEngine, TConfiguration>,
port: Int = 80,
host: String = "0.0.0.0",
configure: TConfiguration.() -> Unit = {},
module: Application.() -> Unit
): EmbeddedServer<TEngine, TConfiguration>
// Simple embedded server
fun main() {
embeddedServer(Netty, port = 8080) {
routing {
get("/") {
call.respondText("Hello, Ktor!")
}
}
}.start(wait = true)
}// Create embedded server with environment
fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configuration> embeddedServer(
factory: ApplicationEngineFactory<TEngine, TConfiguration>,
environment: ApplicationEnvironment,
configure: TConfiguration.() -> Unit = {}
): EmbeddedServer<TEngine, TConfiguration>
// Example with custom environment
fun main() {
val environment = applicationEnvironment {
config = HoconApplicationConfig(ConfigFactory.load())
log = LoggerFactory.getLogger("Application")
}
embeddedServer(Netty, environment) {
// Netty-specific configuration
connectionGroupSize = 2
workerGroupSize = 5
callGroupSize = 10
connector {
port = 8080
host = "0.0.0.0"
}
connector {
port = 8443
host = "0.0.0.0"
// SSL configuration would go here
}
}.start(wait = true)
}open class ApplicationEngine.Configuration {
var parallelism: Int = availableProcessors()
var connectionGroupSize: Int = parallelism
var workerGroupSize: Int = parallelism
var callGroupSize: Int = parallelism
var shutdownGracePeriod: Long = 1000
var shutdownTimeout: Long = 5000
internal val connectors = mutableListOf<EngineConnectorConfig>()
fun takeFrom(other: Configuration) {
// Copy configuration from another instance
parallelism = other.parallelism
connectionGroupSize = other.connectionGroupSize
workerGroupSize = other.workerGroupSize
callGroupSize = other.callGroupSize
shutdownGracePeriod = other.shutdownGracePeriod
shutdownTimeout = other.shutdownTimeout
}
}interface EngineConnectorConfig {
val type: ConnectorType
val host: String
val port: Int
}
enum class ConnectorType {
HTTP, HTTPS
}
// Configure connectors
embeddedServer(Netty, port = 8080) {
// Default connector is automatically created
// Add additional connectors
connector {
port = 8443
host = "0.0.0.0"
// Additional connector configuration
}
}abstract class BaseApplicationEngine(
environment: ApplicationEnvironment,
pipeline: EnginePipeline
) : ApplicationEngine {
override val environment: ApplicationEnvironment = environment
// Lifecycle management
abstract suspend fun startServer(): Unit
abstract suspend fun stopServer(): Unit
// Resolved connector information
override fun resolvedConnectors(): List<EngineConnectorConfig>
}abstract class BaseApplicationCall(
application: Application
) : ApplicationCall {
override val application: Application = application
override val attributes: Attributes = Attributes()
abstract override val request: BaseApplicationRequest
abstract override val response: BaseApplicationResponse
// Parameter resolution
private val _parameters = lazy {
request.queryParameters + request.rawParameters
}
override val parameters: Parameters get() = _parameters.value
}
abstract class BaseApplicationRequest(call: ApplicationCall) : ApplicationRequest
abstract class BaseApplicationResponse(call: ApplicationCall) : ApplicationResponseclass EnginePipeline(
val developmentMode: Boolean = false
) : Pipeline<Unit, ApplicationCall>(Before, Call) {
companion object {
val Before = PipelinePhase("Before")
val Call = PipelinePhase("Call")
}
}
// Default engine pipeline creation
fun defaultEnginePipeline(
environment: ApplicationEnvironment
): EnginePipeline {
return EnginePipeline(environment.developmentMode)
}class DefaultEnginePipeline(
application: Application,
developmentMode: Boolean = false
) : EnginePipeline(developmentMode) {
init {
// Install default pipeline phases and interceptors
intercept(Call) {
try {
// Execute application call pipeline
application.execute(call)
} catch (e: Throwable) {
handleEngineException(e)
}
}
}
}interface ApplicationEnvironment {
val rootPath: String
val log: Logger
val config: ApplicationConfig
val monitor: Events
val developmentMode: Boolean
val parentCoroutineContext: CoroutineContext
val watchPaths: List<String>
}class ApplicationEnvironmentBuilder {
var rootPath: String = ""
var log: Logger = LoggerFactory.getLogger("Application")
var config: ApplicationConfig = MapApplicationConfig()
var developmentMode: Boolean = false
var parentCoroutineContext: CoroutineContext? = null
val watchPaths: MutableList<String> = mutableListOf()
fun build(): ApplicationEnvironment
}
// Create application environment
fun applicationEnvironment(
block: ApplicationEnvironmentBuilder.() -> Unit = {}
): ApplicationEnvironment {
return ApplicationEnvironmentBuilder().apply(block).build()
}// Configure environment from config file/object
fun ApplicationEnvironmentBuilder.configure(
config: ApplicationConfig
) {
this.config = config
// Extract common configuration
rootPath = config.propertyOrNull("ktor.deployment.rootPath")?.getString() ?: ""
developmentMode = config.propertyOrNull("ktor.development")?.getString()?.toBoolean() ?: false
// Configure watch paths for development
config.configList("ktor.deployment.watch").forEach { watchConfig ->
watchPaths.add(watchConfig.property("path").getString())
}
}
// Example configuration usage
fun createEnvironment(): ApplicationEnvironment {
return applicationEnvironment {
config = HoconApplicationConfig(ConfigFactory.load())
configure(config)
log = LoggerFactory.getLogger("MyApp")
developmentMode = System.getenv("ENVIRONMENT") != "production"
}
}class CommandLineConfig(args: Array<String>) {
val port: Int
val host: String
val configPath: String?
val developmentMode: Boolean
// Parse command line arguments
init {
// Implementation parses -port, -host, -config, -dev arguments
}
}
// Configuration keys
object ConfigKeys {
const val applicationIdPath = "ktor.application.id"
const val hostConfigPath = "ktor.deployment.host"
const val portConfigPath = "ktor.deployment.port"
const val shutdownGracePeriodPath = "ktor.deployment.shutdown.gracePeriod"
const val shutdownTimeoutPath = "ktor.deployment.shutdown.timeout"
}// Load common configuration from ApplicationConfig
fun ApplicationEngine.Configuration.loadCommonConfiguration(config: ApplicationConfig) {
config.propertyOrNull("ktor.deployment.shutdown.gracePeriod")?.getString()?.toLong()?.let {
shutdownGracePeriod = it
}
config.propertyOrNull("ktor.deployment.shutdown.timeout")?.getString()?.toLong()?.let {
shutdownTimeout = it
}
config.propertyOrNull("ktor.deployment.connectionGroupSize")?.getString()?.toInt()?.let {
connectionGroupSize = it
}
config.propertyOrNull("ktor.deployment.workerGroupSize")?.getString()?.toInt()?.let {
workerGroupSize = it
}
}// Add shutdown hook to embedded server
fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configuration>
EmbeddedServer<TEngine, TConfiguration>.addShutdownHook() {
Runtime.getRuntime().addShutdownHook(Thread {
stop(1000, 5000)
})
}
// Example usage
fun main() {
val server = embeddedServer(Netty, port = 8080) {
routing {
get("/") { call.respondText("Hello!") }
}
}
server.addShutdownHook()
server.start(wait = true)
}// Stop server when coroutine context is cancelled
suspend fun ApplicationEngine.stopServerOnCancellation() {
try {
awaitCancellation()
} finally {
stopSuspend(1000, 5000)
}
}
// Usage with structured concurrency
fun main() = runBlocking {
val server = embeddedServer(Netty, port = 8080) {
routing {
get("/") { call.respondText("Hello!") }
}
}
launch {
server.start()
server.stopServerOnCancellation()
}
// Server will stop when this coroutine scope completes
delay(60000) // Run for 1 minute
}// Default uncaught exception handler for engines
class DefaultUncaughtExceptionHandler : Thread.UncaughtExceptionHandler {
override fun uncaughtException(t: Thread, e: Throwable) {
// Log uncaught exceptions
logger.error("Uncaught exception in thread ${t.name}", e)
}
}
// Map exceptions to HTTP status codes
fun defaultExceptionStatusCode(exception: Throwable): HttpStatusCode {
return when (exception) {
is NotFoundException -> HttpStatusCode.NotFound
is ParameterConversionException -> HttpStatusCode.BadRequest
is UnsupportedMediaTypeException -> HttpStatusCode.UnsupportedMediaType
is PayloadTooLargeException -> HttpStatusCode.PayloadTooLarge
else -> HttpStatusCode.InternalServerError
}
}fun main() {
val environment = applicationEnvironment {
// Load configuration from application.conf
config = HoconApplicationConfig(ConfigFactory.load())
// Configure from config file
configure(config)
// Production settings
developmentMode = false
log = LoggerFactory.getLogger("ProductionApp")
}
val server = embeddedServer(Netty, environment) {
// Production configuration
connectionGroupSize = 4
workerGroupSize = 8
callGroupSize = 16
shutdownGracePeriod = 5000
shutdownTimeout = 10000
// Multiple connectors for different interfaces
connector {
port = 8080
host = "0.0.0.0"
}
connector {
port = 8443
host = "0.0.0.0"
// SSL configuration
}
connector {
port = 9090
host = "127.0.0.1" // Internal management interface
}
}
// Add shutdown hook for graceful termination
server.addShutdownHook()
// Start server
server.start(wait = true)
}fun main() {
val server = embeddedServer(Netty, port = 8080) {
// Development configuration - optimized for fast restarts
connectionGroupSize = 1
workerGroupSize = 1
callGroupSize = 1
connector {
port = 8080
host = "127.0.0.1" // Local only
}
}
Runtime.getRuntime().addShutdownHook(Thread {
server.stop(100, 1000) // Faster shutdown for development
})
server.start(wait = true)
}fun main() {
// Create shared environment
val sharedEnvironment = applicationEnvironment {
config = HoconApplicationConfig(ConfigFactory.load())
configure(config)
}
// API server
val apiServer = embeddedServer(Netty, sharedEnvironment) {
connector {
port = 8080
host = "0.0.0.0"
}
}
// Admin server
val adminServer = embeddedServer(CIO, sharedEnvironment) {
connector {
port = 8081
host = "127.0.0.1" // Admin interface local only
}
}
// Start both servers
apiServer.start(wait = false)
adminServer.start(wait = false)
// Keep main thread alive
runBlocking {
awaitCancellation()
}
}embeddedServer(Netty, port = 8080) {
// Netty-specific settings
connectionGroupSize = 2 // Boss threads
workerGroupSize = 8 // Worker threads
callGroupSize = 16 // Call handler threads
shutdownGracePeriod = 2000
shutdownTimeout = 5000
// Request timeout configuration
requestQueueLimit = 16
runningLimit = 32
// TCP configuration
tcpKeepAlive = true
reuseAddress = true
connector {
port = 8080
host = "0.0.0.0"
}
}embeddedServer(CIO, port = 8080) {
// CIO-specific settings
connectionGroupSize = 1
workerGroupSize = availableProcessors()
callGroupSize = availableProcessors() * 2
connectionIdleTimeout = 45000
connector {
port = 8080
host = "0.0.0.0"
}
}This comprehensive documentation covers all aspects of Ktor's server engine system, from basic embedded server creation to advanced production deployment configurations.
Install with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor-server-core