CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-ktor--ktor

A multiplatform asynchronous framework for creating microservices, web applications, and HTTP clients written in Kotlin from the ground up

Pending
Overview
Eval results
Files

response-handling.mddocs/

HTTP Response Handling

Response processing, body consumption, and content type handling for working with HTTP responses and consuming response data.

Capabilities

HttpResponse Class

Abstract base class representing an HTTP response with status, headers, and content.

/**
 * Abstract HTTP response representing the server's response to a request
 */
abstract class HttpResponse : HttpMessage, CoroutineScope {
    /** The call that generated this response */
    abstract val call: HttpClientCall
    
    /** HTTP status code and description */
    abstract val status: HttpStatusCode
    
    /** HTTP protocol version */
    abstract val version: HttpProtocolVersion
    
    /** Timestamp when the request was sent */
    abstract val requestTime: GMTDate
    
    /** Timestamp when the response was received */
    abstract val responseTime: GMTDate
    
    /** Raw response content as a byte channel */
    abstract val rawContent: ByteReadChannel
}

Response Body Consumption

Extension functions for consuming response body content in various formats.

/**
 * Get response body as text with optional charset fallback
 * @param fallbackCharset Charset to use if not specified in response
 * @return Response body as string
 */
suspend fun HttpResponse.bodyAsText(
    fallbackCharset: Charset = Charsets.UTF_8
): String

/**
 * Get response body as raw byte channel
 * @return ByteReadChannel for streaming response content
 */
suspend fun HttpResponse.bodyAsChannel(): ByteReadChannel

/**
 * Get response body as byte array
 * @return Response body as ByteArray
 */
suspend fun HttpResponse.bodyAsBytes(): ByteArray

/**
 * Get response body as typed object using content negotiation
 * @return Deserialized response body of type T
 */
suspend inline fun <reified T> HttpResponse.body(): T

/**
 * Get response body as typed object with explicit type information
 * @param typeInfo Type information for deserialization
 * @return Deserialized response body
 */
suspend fun <T> HttpResponse.body(typeInfo: TypeInfo): T

Usage Examples:

import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*

val client = HttpClient()

// Get response as text
val response = client.get("https://api.example.com/users")
val textContent = response.bodyAsText()

// Get response as bytes
val imageResponse = client.get("https://example.com/image.png")
val imageBytes = imageResponse.bodyAsBytes()

// Get response as typed object (requires serialization plugin)
val usersResponse = client.get("https://api.example.com/users")
val users: List<User> = usersResponse.body()

// Stream response content
val streamResponse = client.get("https://example.com/large-file")
val channel = streamResponse.bodyAsChannel()
while (!channel.isClosedForRead) {
    val data = channel.readBuffer(8192)
    // Process data chunk
}

HttpStatement Class

Deferred HTTP request execution that allows for custom response handling.

/**
 * Represents a prepared HTTP request that can be executed multiple times
 */
class HttpStatement(
    private val builder: HttpRequestBuilder,
    internal val client: HttpClient
) {
    /**
     * Execute the request with custom response handling
     * @param block Response handling block
     * @return Result of the response handling block
     */
    suspend fun <T> execute(
        block: suspend (response: HttpResponse) -> T
    ): T
    
    /**
     * Execute the request and return the response
     * @return HttpResponse from the server
     */
    suspend fun execute(): HttpResponse
    
    /**
     * Execute the request and get typed response body
     * @return Deserialized response body of type T
     */
    suspend inline fun <reified T> body(): T
    
    /**
     * Execute the request and get typed response body with custom handling
     * @param block Custom handling block for the typed response
     * @return Result of the handling block
     */
    suspend inline fun <reified T, R> body(
        crossinline block: suspend (response: T) -> R
    ): R
}

Usage Examples:

import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*

val client = HttpClient()

// Prepare statement for later execution
val statement = client.prepareGet("https://api.example.com/users")

// Execute with custom response handling
val result = statement.execute { response ->
    when {
        response.status.isSuccess() -> response.bodyAsText()
        response.status.value == 404 -> "Not found"
        else -> "Error: ${response.status}"
    }
}

// Execute and get typed body
val users: List<User> = statement.body()

// Execute with typed body and custom handling
val userCount = statement.body<List<User>> { users ->
    users.size
}

HttpClientCall Class

Represents a complete HTTP request-response cycle with access to both request and response.

/**
 * Represents a complete HTTP call including request and response
 */
open class HttpClientCall(
    val client: HttpClient
) : CoroutineScope {
    /** Attributes for storing call metadata */
    val attributes: Attributes
    
    /** The request that was sent */
    lateinit var request: HttpRequest
    
    /** The response that was received */
    lateinit var response: HttpResponse
    
    /**
     * Get response body with nullable handling
     * @param info Type information for deserialization
     * @return Deserialized body or null
     */
    suspend fun bodyNullable(info: TypeInfo): Any?
    
    /**
     * Get response body with type information
     * @param info Type information for deserialization
     * @return Deserialized body
     */
    suspend fun body(info: TypeInfo): Any
}

Call Body Extension Functions

Extension functions for getting typed response bodies from HttpClientCall instances.

/**
 * Get typed response body from call
 * @return Deserialized response body of type T
 */
suspend inline fun <reified T> HttpClientCall.body(): T

/**
 * Get typed response body from response
 * @return Deserialized response body of type T
 */
suspend inline fun <reified T> HttpResponse.body(): T

Usage Examples:

import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*

val client = HttpClient()

val response = client.get("https://api.example.com/user/123")

// Access the call
val call = response.call

// Get typed body from call
val user: User = call.body()

// Access request information
println("Request URL: ${call.request.url}")
println("Response Status: ${call.response.status}")

Response Utility Functions

Utility functions for working with response content types and character sets.

/**
 * Get the charset from response Content-Type header
 * @return Charset if specified, null otherwise
 */
fun HttpResponse.charset(): Charset?

/**
 * Get the content type from response headers
 * @return ContentType if specified, null otherwise
 */
fun HttpMessage.contentType(): ContentType?

Usage Examples:

import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*

val response = client.get("https://api.example.com/data")

// Check content type
val contentType = response.contentType()
when {
    contentType?.match(ContentType.Application.Json) == true -> {
        val jsonData = response.bodyAsText()
        // Process JSON
    }
    contentType?.match(ContentType.Text.Plain) == true -> {
        val textData = response.bodyAsText()
        // Process plain text
    }
}

// Use specific charset if available
val charset = response.charset() ?: Charsets.UTF_8
val text = response.bodyAsText(charset)

Exception Handling

Response-related exceptions that may be thrown during response processing:

/**
 * Thrown when attempting to receive response body multiple times
 */
class DoubleReceiveException(call: HttpClientCall) : IllegalStateException()

/**
 * Thrown when no type transformer is found for response deserialization
 */
class NoTransformationFoundException(
    response: HttpResponse,
    from: KClass<*>,
    to: KClass<*>
) : UnsupportedOperationException()

/**
 * Exception in response receive pipeline
 */
class ReceivePipelineException(
    request: HttpClientCall,
    info: TypeInfo,
    override val cause: Throwable
) : IllegalStateException()

/**
 * Base class for HTTP response exceptions
 */
open class ResponseException(
    response: HttpResponse,
    cachedResponseText: String
) : IllegalStateException()

/**
 * Exception for HTTP redirect responses
 */
class RedirectResponseException(
    response: HttpResponse,
    cachedResponseText: String
) : ResponseException(response, cachedResponseText)

/**
 * Exception for HTTP client error responses (4xx)
 */
class ClientRequestException(
    response: HttpResponse,
    cachedResponseText: String
) : ResponseException(response, cachedResponseText)

/**
 * Exception for HTTP server error responses (5xx)
 */
class ServerResponseException(
    response: HttpResponse,
    cachedResponseText: String
) : ResponseException(response, cachedResponseText)

Usage Examples:

import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.client.plugins.*

val client = HttpClient {
    // Configure to not throw on non-2xx responses
    expectSuccess = false
}

try {
    val response = client.get("https://api.example.com/might-fail")
    val content = response.bodyAsText()
    
    // Try to read body again - throws DoubleReceiveException
    val duplicateContent = response.bodyAsText() // ERROR!
    
} catch (e: ClientRequestException) {
    // Handle 4xx responses
    println("Client error: ${e.response.status}")
} catch (e: ServerResponseException) {
    // Handle 5xx responses
    println("Server error: ${e.response.status}")
} catch (e: DoubleReceiveException) {
    // Handle duplicate receive attempts
    println("Response body already consumed")
}

Types

Response handling related types:

/**
 * HTTP status code with value and description
 */
data class HttpStatusCode(val value: Int, val description: String) {
    companion object {
        val Continue = HttpStatusCode(100, "Continue")
        val OK = HttpStatusCode(200, "OK")
        val Created = HttpStatusCode(201, "Created")
        val NoContent = HttpStatusCode(204, "No Content")
        val NotModified = HttpStatusCode(304, "Not Modified")
        val BadRequest = HttpStatusCode(400, "Bad Request")
        val Unauthorized = HttpStatusCode(401, "Unauthorized")
        val Forbidden = HttpStatusCode(403, "Forbidden")
        val NotFound = HttpStatusCode(404, "Not Found")
        val InternalServerError = HttpStatusCode(500, "Internal Server Error")
        val BadGateway = HttpStatusCode(502, "Bad Gateway")
    }
    
    fun isSuccess(): Boolean = value in 200..299
    fun isInformational(): Boolean = value in 100..199
    fun isRedirect(): Boolean = value in 300..399
    fun isClientError(): Boolean = value in 400..499
    fun isServerError(): Boolean = value in 500..599
}

/**
 * HTTP protocol version
 */
data class HttpProtocolVersion(
    val name: String,
    val major: Int,
    val minor: Int
) {
    companion object {
        val HTTP_1_0 = HttpProtocolVersion("HTTP", 1, 0)
        val HTTP_1_1 = HttpProtocolVersion("HTTP", 1, 1)
        val HTTP_2_0 = HttpProtocolVersion("HTTP", 2, 0)
        val SPDY_3 = HttpProtocolVersion("SPDY", 3, 0)
        val QUIC = HttpProtocolVersion("QUIC", 1, 0)
    }
}

/**
 * Type information for response deserialization
 */
interface TypeInfo {
    val type: KClass<*>
    val reifiedType: KType
    val kotlinType: KType?
}

/**
 * GMT date representation
 */
data class GMTDate(val timestamp: Long) {
    companion object {
        fun now(): GMTDate
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-io-ktor--ktor

docs

client-management.md

engine-system.md

form-data.md

http-utilities.md

index.md

plugin-system.md

request-operations.md

response-handling.md

routing-system.md

server-framework.md

tile.json