Ktor Server Core library providing foundational infrastructure for building asynchronous web applications and REST APIs with Kotlin
—
Ktor provides comprehensive request and response handling capabilities through the ApplicationRequest and ApplicationResponse interfaces, along with powerful pipeline-based content transformation and type-safe serialization support.
interface ApplicationRequest {
val local: RequestConnectionPoint
val pipeline: ApplicationReceivePipeline
val queryParameters: Parameters
val headers: Headers
val cookies: RequestCookies
}// Request URI and path information
val ApplicationRequest.uri: String // Full URI including query string
val ApplicationRequest.path: String // URL path without query string
val ApplicationRequest.document: String // Document name from path
val ApplicationRequest.queryString: String // Query string
// HTTP method and version
val ApplicationRequest.httpMethod: HttpMethod // HTTP method (possibly overridden)
val ApplicationRequest.httpVersion: String // HTTP version string
// Content information
fun ApplicationRequest.contentType(): ContentType? // Request content type
fun ApplicationRequest.contentLength(): Long? // Content-Length header value
fun ApplicationRequest.contentCharset(): Charset? // Request charset// Header access functions
fun ApplicationRequest.header(name: String): String? // Get header value by name
// Standard header accessors
fun ApplicationRequest.authorization(): String? // Authorization header
fun ApplicationRequest.location(): String? // Location header
fun ApplicationRequest.accept(): String? // Accept header
fun ApplicationRequest.acceptEncoding(): String? // Accept-Encoding header
fun ApplicationRequest.acceptLanguage(): String? // Accept-Language header
fun ApplicationRequest.acceptCharset(): String? // Accept-Charset header
fun ApplicationRequest.userAgent(): String? // User-Agent header
fun ApplicationRequest.cacheControl(): String? // Cache-Control header
// Host and port information
fun ApplicationRequest.host(): String // Host without port
fun ApplicationRequest.port(): Int // Port number (default: 80 for HTTP, 443 for HTTPS)// Parsed Accept header items
fun ApplicationRequest.acceptItems(): List<HeaderValue> // Parsed Accept header content types
fun ApplicationRequest.acceptEncodingItems(): List<HeaderValue> // Parsed Accept-Encoding items
fun ApplicationRequest.acceptLanguageItems(): List<HeaderValue> // Parsed Accept-Language items
fun ApplicationRequest.acceptCharsetItems(): List<HeaderValue> // Parsed Accept-Charset items
// Range header parsing
fun ApplicationRequest.ranges(): RangesSpecifier? // Parsed Range header
// Example usage
get("/content") {
val acceptItems = call.request.acceptItems()
val preferredType = acceptItems.firstOrNull()?.value
when (preferredType) {
"application/json" -> call.respond(jsonData)
"application/xml" -> call.respondText(xmlData, ContentType.Application.Xml)
else -> call.respondText("Unsupported content type", status = HttpStatusCode.NotAcceptable)
}
}// Content type detection functions
fun ApplicationRequest.isChunked(): Boolean // Check if request is chunk-encoded
fun ApplicationRequest.isMultipart(): Boolean // Check if request is multipart
// Usage example
post("/upload") {
if (call.request.isMultipart()) {
// Handle multipart form data
val multipart = call.receiveMultipart()
} else {
// Handle regular content
val content = call.receive<String>()
}
}// Receive typed content from request body
suspend fun <T : Any> ApplicationCall.receive(): T
suspend fun <T : Any> ApplicationCall.receive(typeInfo: TypeInfo): T
suspend fun <T : Any> ApplicationCall.receiveNullable(): T?
suspend fun <T : Any> ApplicationCall.receiveNullable(typeInfo: TypeInfo): T?
// Examples
post("/users") {
// Receive JSON as data class
val user = call.receive<User>()
val created = userService.create(user)
call.respond(HttpStatusCode.Created, created)
}
post("/data") {
// Receive nullable content
val data = call.receiveNullable<RequestData>()
if (data != null) {
processData(data)
call.respond(HttpStatusCode.OK)
} else {
call.respond(HttpStatusCode.BadRequest, "Invalid data")
}
}// Application receive pipeline for content transformation
class ApplicationReceivePipeline : Pipeline<Any, ApplicationCall> {
companion object {
val Before = PipelinePhase("Before")
val Transform = PipelinePhase("Transform")
val After = PipelinePhase("After")
}
}
// Pipeline request implementation
interface PipelineRequest : ApplicationRequest {
// Pipeline-specific request functionality
}routing {
// Receive different content types
post("/json") {
val data = call.receive<JsonData>()
call.respond(processJsonData(data))
}
post("/form") {
val parameters = call.receiveParameters()
val name = parameters["name"] ?: ""
val email = parameters["email"] ?: ""
call.respondText("Received: $name, $email")
}
post("/text") {
val text = call.receiveText()
call.respondText("Echo: $text")
}
post("/bytes") {
val bytes = call.receive<ByteArray>()
call.respondBytes(processBytes(bytes))
}
}interface ApplicationResponse {
val status: HttpStatusCode?
val headers: ResponseHeaders
val cookies: ResponseCookies
val pipeline: ApplicationSendPipeline
val isCommitted: Boolean
}// Send typed response
suspend fun ApplicationCall.respond(message: Any)
suspend fun ApplicationCall.respond(status: HttpStatusCode, message: Any)
suspend fun ApplicationCall.respond(message: Any, typeInfo: TypeInfo)
suspend fun ApplicationCall.respondNullable(message: Any?)
// Send text response
suspend fun ApplicationCall.respondText(
text: String,
contentType: ContentType? = null,
status: HttpStatusCode? = null
)
// Send byte array response
suspend fun ApplicationCall.respondBytes(
bytes: ByteArray,
contentType: ContentType? = null,
status: HttpStatusCode? = null
)
// Send from kotlinx-io Source
suspend fun ApplicationCall.respondSource(
source: Source,
contentType: ContentType? = null,
status: HttpStatusCode? = null,
contentLength: Long? = null
)
// Send with producer function
suspend fun ApplicationCall.respondBytesWriter(
contentType: ContentType? = null,
status: HttpStatusCode? = null,
contentLength: Long? = null,
producer: suspend ByteWriteChannel.() -> Unit
)// Send redirect response
suspend fun ApplicationCall.respondRedirect(url: String, permanent: Boolean = false)
// Examples
get("/old-path") {
call.respondRedirect("/new-path", permanent = true)
}
post("/login") {
val credentials = call.receive<LoginRequest>()
if (authenticate(credentials)) {
call.respondRedirect("/dashboard")
} else {
call.respondRedirect("/login?error=invalid")
}
}// Response headers
interface ResponseHeaders : Headers {
operator fun set(name: String, value: String)
fun append(name: String, value: String)
fun remove(name: String)
}
// Response cookies
interface ResponseCookies {
operator fun set(name: String, value: String)
fun append(name: String, value: String, vararg items: CookieEncoding)
fun appendExpired(name: String)
}
// Usage examples
get("/set-headers") {
call.response.headers.append("X-Custom-Header", "custom-value")
call.response.headers["Cache-Control"] = "no-cache"
call.respondText("Headers set")
}
get("/set-cookies") {
call.response.cookies.append("session-id", "abc123")
call.response.cookies.append("theme", "dark", CookieEncoding.URI_ENCODING)
call.respondText("Cookies set")
}// Application send pipeline for response transformation
class ApplicationSendPipeline : Pipeline<Any, ApplicationCall> {
companion object {
val Before = PipelinePhase("Before")
val Transform = PipelinePhase("Transform")
val Render = PipelinePhase("Render")
val ContentEncoding = PipelinePhase("ContentEncoding")
val TransferEncoding = PipelinePhase("TransferEncoding")
val After = PipelinePhase("After")
}
}
// Pipeline response implementation
interface PipelineResponse : ApplicationResponse {
// Pipeline-specific response functionality
}// Response type information
class ResponseType(
val jvmErasure: KClass<*>,
val reifiedType: Type
)
// Content type utilities
fun ApplicationResponse.defaultTextContentType(charset: Charset? = null): ContentType// HTTP/2 push promise builder
interface ResponsePushBuilder {
var method: HttpMethod
var url: URLBuilder
val headers: HeadersBuilder
val versions: MutableList<Version>
}
// Default push builder implementation
class DefaultResponsePushBuilder(
call: ApplicationCall
) : ResponsePushBuilder {
override var method: HttpMethod = HttpMethod.Get
override val url: URLBuilder = URLBuilder()
override val headers: HeadersBuilder = HeadersBuilder()
override val versions: MutableList<Version> = mutableListOf()
}
// HTTP/2 push attribute
object UseHttp2Push : AttributeKey<ResponsePushBuilder>get("/page") {
// Send push promises for resources
val pushBuilder = DefaultResponsePushBuilder(call)
pushBuilder.url.path("/styles.css")
call.attributes.put(UseHttp2Push, pushBuilder)
// Send main response
call.respondText(generateHtmlPage(), ContentType.Text.Html)
}// Connection point information (immutable)
interface RequestConnectionPoint {
val scheme: String // "http" or "https"
val version: String // HTTP version
val port: Int // Port number
val host: String // Host name
val uri: String // Request URI
val method: HttpMethod // HTTP method
}
// Mutable connection point for modifications
class MutableOriginConnectionPoint : RequestConnectionPoint {
override var scheme: String
override var version: String
override var port: Int
override var host: String
override var uri: String
override var method: HttpMethod
}
// Access connection information
get("/info") {
val local = call.request.local
call.respond(mapOf(
"scheme" to local.scheme,
"host" to local.host,
"port" to local.port,
"method" to local.method.value,
"uri" to local.uri
))
}// Access origin connection point
val ApplicationRequest.origin: RequestConnectionPoint
// Access mutable origin for proxies/load balancers
val ApplicationRequest.mutableOriginConnectionPoint: MutableOriginConnectionPoint
// Example: Handle X-Forwarded headers
intercept(ApplicationCallPipeline.Setup) {
val forwarded = call.request.header("X-Forwarded-For")
if (forwarded != null) {
call.request.mutableOriginConnectionPoint.host = forwarded
}
val forwardedProto = call.request.header("X-Forwarded-Proto")
if (forwardedProto != null) {
call.request.mutableOriginConnectionPoint.scheme = forwardedProto
}
}// HTTP 404 exception
class NotFoundException(message: String? = "Not Found") : Exception(message)
// Missing parameter exception
class MissingRequestParameterException(parameterName: String) : Exception(
"Required parameter $parameterName is missing"
)
// Parameter conversion exception
class ParameterConversionException(
parameterName: String,
type: String,
cause: Throwable? = null
) : Exception("Parameter $parameterName cannot be converted to $type", cause)
// Content transformation exception
class CannotTransformContentToTypeException(type: TypeInfo) : Exception(
"Cannot transform content to ${type.type}"
)
// Unsupported media type exception
class UnsupportedMediaTypeException(contentType: ContentType) : Exception(
"Content type $contentType is not supported"
)
// Payload too large exception
class PayloadTooLargeException(message: String) : Exception(message)routing {
get("/users/{id}") {
try {
val id = call.parameters["id"]
?: throw MissingRequestParameterException("id")
val userId = id.toLongOrNull()
?: throw ParameterConversionException("id", "Long")
val user = userService.findById(userId)
?: throw NotFoundException("User with id $userId not found")
call.respond(user)
} catch (e: MissingRequestParameterException) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to e.message))
} catch (e: ParameterConversionException) {
call.respond(HttpStatusCode.BadRequest, mapOf("error" to e.message))
} catch (e: NotFoundException) {
call.respond(HttpStatusCode.NotFound, mapOf("error" to e.message))
}
}
post("/upload") {
try {
if (!call.request.isMultipart()) {
throw UnsupportedMediaTypeException(call.request.contentType()!!)
}
val multipart = call.receiveMultipart()
// Process multipart data
} catch (e: UnsupportedMediaTypeException) {
call.respond(HttpStatusCode.UnsupportedMediaType, mapOf("error" to e.message))
} catch (e: PayloadTooLargeException) {
call.respond(HttpStatusCode.PayloadTooLarge, mapOf("error" to e.message))
}
}
}get("/api/data") {
val acceptHeader = call.request.accept()
val data = dataService.getData()
when {
acceptHeader?.contains("application/json") == true -> {
call.respond(data)
}
acceptHeader?.contains("application/xml") == true -> {
call.respondText(data.toXml(), ContentType.Application.Xml)
}
acceptHeader?.contains("text/csv") == true -> {
call.respondText(data.toCsv(), ContentType.Text.CSV)
}
else -> {
call.respond(HttpStatusCode.NotAcceptable)
}
}
}get("/files/{filename}") {
val filename = call.parameters["filename"]!!
val file = File("uploads/$filename")
if (!file.exists()) {
call.respond(HttpStatusCode.NotFound)
return@get
}
// Handle If-Modified-Since
val ifModifiedSince = call.request.header("If-Modified-Since")
if (ifModifiedSince != null) {
val modifiedDate = parseHttpDate(ifModifiedSince)
if (file.lastModified() <= modifiedDate.toEpochMilli()) {
call.respond(HttpStatusCode.NotModified)
return@get
}
}
// Handle Range requests
val ranges = call.request.ranges()
if (ranges != null) {
// Handle partial content
call.response.status(HttpStatusCode.PartialContent)
// Implement range serving
} else {
call.respondFile(file)
}
}get("/stream") {
call.respondBytesWriter(ContentType.Text.Plain) {
repeat(1000) { i ->
writeStringUtf8("Line $i\n")
flush()
delay(10) // Simulate slow data generation
}
}
}
get("/download") {
val file = File("large-file.dat")
call.response.header("Content-Disposition", "attachment; filename=\"${file.name}\"")
call.respondFile(file)
}fun Application.configureRequestResponse() {
routing {
// Handle different content types
post("/api/users") {
try {
// Validate content type
val contentType = call.request.contentType()
if (contentType?.match(ContentType.Application.Json) != true) {
call.respond(
HttpStatusCode.UnsupportedMediaType,
"Expected application/json"
)
return@post
}
// Receive and validate user data
val createRequest = call.receiveNullable<CreateUserRequest>()
if (createRequest == null) {
call.respond(HttpStatusCode.BadRequest, "Invalid user data")
return@post
}
// Create user
val user = userService.createUser(createRequest)
// Set response headers
call.response.headers["Location"] = "/api/users/${user.id}"
call.response.headers["X-Created-At"] = Instant.now().toString()
// Send response
call.respond(HttpStatusCode.Created, user)
} catch (e: CannotTransformContentToTypeException) {
call.respond(HttpStatusCode.BadRequest, "Invalid JSON format")
} catch (e: Exception) {
application.log.error("Error creating user", e)
call.respond(HttpStatusCode.InternalServerError, "Internal server error")
}
}
// File upload with progress
post("/upload") {
val contentLength = call.request.contentLength()
if (contentLength != null && contentLength > 10_000_000) {
call.respond(HttpStatusCode.PayloadTooLarge, "File too large")
return@post
}
if (!call.request.isMultipart()) {
call.respond(HttpStatusCode.BadRequest, "Multipart content expected")
return@post
}
val multipart = call.receiveMultipart()
val uploadedFiles = mutableListOf<String>()
multipart.forEachPart { part ->
when (part) {
is PartData.FileItem -> {
val fileName = part.originalFileName ?: "unknown"
val file = File("uploads/$fileName")
part.streamProvider().use { input ->
file.outputStream().use { output ->
input.copyTo(output)
}
}
uploadedFiles.add(fileName)
}
else -> part.dispose()
}
}
call.respond(mapOf(
"message" to "Upload successful",
"files" to uploadedFiles
))
}
}
}
data class CreateUserRequest(
val name: String,
val email: String,
val age: Int?
)This comprehensive documentation covers all aspects of Ktor's request and response handling system, from basic content receiving and sending to advanced patterns like streaming and content negotiation.
Install with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor-server-core