CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-ktor--ktor-server-core

Ktor Server Core library providing foundational infrastructure for building asynchronous web applications and REST APIs with Kotlin

Pending
Overview
Eval results
Files

request-response.mddocs/

Request and Response Handling

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.

ApplicationRequest Interface

Core Request Interface

interface ApplicationRequest {
    val local: RequestConnectionPoint
    val pipeline: ApplicationReceivePipeline  
    val queryParameters: Parameters
    val headers: Headers
    val cookies: RequestCookies
}

Request Properties

// 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

// 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 Header Content

// 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

// 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>()
    }
}

Content Receiving

Receive Functions

// 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")
    }
}

Receive Pipeline

// 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
}

Content Type Examples

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))
    }
}

ApplicationResponse Interface

Core Response Interface

interface ApplicationResponse {
    val status: HttpStatusCode?
    val headers: ResponseHeaders
    val cookies: ResponseCookies  
    val pipeline: ApplicationSendPipeline
    val isCommitted: Boolean
}

Response Functions

// 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
)

Redirect Responses

// 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 and Cookies

// 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")
}

Response Pipeline

ApplicationSendPipeline

// 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 Content Types

// Response type information
class ResponseType(
    val jvmErasure: KClass<*>,
    val reifiedType: Type
)

// Content type utilities
fun ApplicationResponse.defaultTextContentType(charset: Charset? = null): ContentType

HTTP/2 Push Support

ResponsePushBuilder

// 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>

Push Promise Example

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)
}

Request Connection Point

Connection Information

// 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
    ))
}

Origin Extensions

// 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
    }
}

Exception Handling

Request/Response Exceptions

// 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)

Exception Handling Examples

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))
        }
    }
}

Advanced Request/Response Patterns

Content Negotiation

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)
        }
    }
}

Conditional Responses

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)
    }
}

Streaming Responses

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)
}

Complete Request/Response Example

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

docs

application.md

configuration.md

engine.md

index.md

request-response.md

routing.md

tile.json