CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-ktor--ktor-client-core-iosx64

Ktor HTTP client core library - asynchronous framework for creating HTTP clients in Kotlin multiplatform

Pending
Overview
Eval results
Files

response-handling.mddocs/

Response Handling

Comprehensive response handling with typed body access, streaming capabilities, and flexible content processing for HTTP responses.

Capabilities

HttpResponse Class

Abstract base class representing HTTP responses with access to status, headers, and content.

/**
 * Abstract HTTP response class implementing HttpMessage and CoroutineScope
 */
abstract class HttpResponse : HttpMessage, CoroutineScope {
    /** Associated client call */
    abstract val call: HttpClientCall
    
    /** Response status code */
    abstract val status: HttpStatusCode
    
    /** HTTP protocol version */
    abstract val version: HttpProtocolVersion
    
    /** Request start time */
    abstract val requestTime: GMTDate
    
    /** Response start time */
    abstract val responseTime: GMTDate
    
    /** Raw response content as byte channel */
    abstract val content: ByteReadChannel
    
    /** Coroutine context inherited from call */
    override val coroutineContext: CoroutineContext
}

/**
 * Get the associated request for this response
 */
val HttpResponse.request: HttpRequest

Usage Examples:

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

val client = HttpClient()
val response: HttpResponse = client.get("https://api.example.com/users")

// Access response properties
println("Status: ${response.status}")
println("Status Code: ${response.status.value}")
println("HTTP Version: ${response.version}")
println("Request Time: ${response.requestTime}")
println("Response Time: ${response.responseTime}")

// Access headers
println("Content-Type: ${response.headers[HttpHeaders.ContentType]}")
println("Content-Length: ${response.headers[HttpHeaders.ContentLength]}")

// Access request information
println("Request URL: ${response.request.url}")
println("Request Method: ${response.request.method}")

Response Body Access

Functions for accessing response body content in various formats with type safety.

/**
 * Get response body as text with optional charset fallback
 */
suspend fun HttpResponse.bodyAsText(fallbackCharset: Charset = Charsets.UTF_8): String

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

/**
 * Get typed response body using reified generics
 */
suspend inline fun <reified T> HttpResponse.body(): T

/**
 * Get typed response body with explicit type information
 */
suspend fun <T> HttpResponse.body(typeInfo: TypeInfo): T

Usage Examples:

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

// Get as text
val textContent: String = response.bodyAsText()
println("Response: $textContent")

// Get as text with specific charset
val utf8Content = response.bodyAsText(Charsets.UTF_8)

// Get as typed object (requires content negotiation plugin)
data class User(val id: Int, val name: String, val email: String)
val user: User = response.body()

// Get as list of objects
val users: List<User> = response.body()

// Get as byte array
val bytes: ByteArray = response.body()

// Streaming content
val channel: ByteReadChannel = response.bodyAsChannel()
// Process channel data as needed

HttpStatement Class

Reusable prepared request that can be executed multiple times with different response handling.

/**
 * Reusable prepared HTTP request
 */
class HttpStatement(
    private val builder: HttpRequestBuilder,
    private val client: HttpClient
) {
    /**
     * Execute request with custom response handler
     */
    suspend fun <T> execute(block: suspend (response: HttpResponse) -> T): T
    
    /**
     * Execute request and get downloaded response
     */
    suspend fun execute(): HttpResponse
    
    /**
     * Execute request and get typed response body
     */
    suspend inline fun <reified T> body(): T
    
    /**
     * Execute request with typed response handler
     */
    suspend inline fun <reified T, R> body(
        crossinline block: suspend (response: T) -> R
    ): R
}

Usage Examples:

// Create prepared statement
val statement = client.prepareGet("https://api.example.com/users/{id}")

// Execute with custom handler
val userInfo = statement.execute { response ->
    when (response.status) {
        HttpStatusCode.OK -> response.bodyAsText()
        HttpStatusCode.NotFound -> "User not found"
        else -> "Error: ${response.status}"
    }
}

// Execute and get response
val response = statement.execute()
val content = response.bodyAsText()

// Execute and get typed body
val user: User = statement.body()

// Execute with typed handler
val userName = statement.body<User> { user ->
    user.name
}

// Reuse statement multiple times
repeat(5) { id ->
    val user = statement.body<User>()
    println("User $id: ${user.name}")
}

HttpClientCall Class

Request/response pair representing a complete HTTP exchange with attribute support.

/**
 * HTTP client call representing request/response pair
 */
class HttpClientCall(
    val client: HttpClient
) : CoroutineScope {
    /** Call attributes inherited from request */
    val attributes: Attributes
    
    /** Request part of the call */
    val request: HttpRequest
    
    /** Response part of the call */
    val response: HttpResponse
    
    /** Coroutine context from response */
    override val coroutineContext: CoroutineContext
    
    /**
     * Get typed body from response
     */
    suspend inline fun <reified T> body(): T
    
    /**
     * Get typed body with explicit type information
     */
    suspend fun <T> body(info: TypeInfo): T
    
    /**
     * Get nullable typed body
     */
    suspend fun bodyNullable(info: TypeInfo): Any?
}

Usage Examples:

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

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

// Access client
val sameClient = call.client

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

// Access call attributes
val requestId = call.attributes[AttributeKey<String>("RequestId")]

Response Body Extensions

Additional convenience functions for response body access.

/**
 * Get typed response body using reified generics
 */
suspend inline fun <reified T> HttpResponse.body(): T

/**
 * Get typed response body with explicit type information  
 */
suspend fun <T> HttpResponse.body(typeInfo: TypeInfo): T

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

/**
 * Save response body to file (JVM only)
 */
suspend fun HttpResponse.bodyAsChannel(): ByteReadChannel

Usage Examples:

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

// Different ways to access body
val jsonString: String = response.body()
val jsonBytes: ByteArray = response.bodyAsBytes()
val userData: UserData = response.body()

// Stream processing
val channel = response.bodyAsChannel()
val buffer = ByteArray(8192)
while (!channel.isClosedForRead) {
    val bytesRead = channel.readAvailable(buffer)
    if (bytesRead > 0) {
        // Process buffer data
        processChunk(buffer, bytesRead)
    }
}

Error Handling

Exception classes and patterns for handling response errors.

/**
 * Thrown when attempting to receive response body twice
 */
class DoubleReceiveException(message: String) : IllegalStateException(message)

/**
 * Thrown when response pipeline fails
 */
class ReceivePipelineException(
    message: String, 
    cause: Throwable
) : IllegalStateException(message, cause)

/**
 * Thrown when no suitable content transformation is found
 */
class NoTransformationFoundException(
    from: KType, 
    to: KType
) : UnsupportedOperationException()

Usage Examples:

try {
    val response = client.get("https://api.example.com/users")
    val content1 = response.bodyAsText()
    
    // This will throw DoubleReceiveException
    val content2 = response.bodyAsText()
} catch (e: DoubleReceiveException) {
    println("Cannot receive response body twice: ${e.message}")
}

try {
    val response = client.get("https://api.example.com/data")
    val customObject: MyCustomType = response.body()
} catch (e: NoTransformationFoundException) {
    println("No transformer available for type: ${e.message}")
}

// Proper error handling pattern
val response = client.get("https://api.example.com/users")
when (response.status) {
    HttpStatusCode.OK -> {
        val users: List<User> = response.body()
        // Handle success
    }
    HttpStatusCode.NotFound -> {
        // Handle not found
    }
    in HttpStatusCode.InternalServerError..HttpStatusCode.fromValue(599) -> {
        // Handle server errors
        val errorBody = response.bodyAsText()
        println("Server error: $errorBody")
    }
    else -> {
        // Handle other status codes  
        println("Unexpected status: ${response.status}")
    }
}

Streaming Response Handling

Advanced streaming capabilities for large responses or real-time data.

/**
 * Process response as stream without loading entire content into memory
 */
suspend fun HttpResponse.bodyAsChannel(): ByteReadChannel

Usage Examples:

// Download large file
val response = client.get("https://example.com/large-file.zip")
val channel = response.bodyAsChannel()

// Stream to file
val file = File("downloaded-file.zip")
file.outputStream().use { output ->
    val buffer = ByteArray(8192)
    while (!channel.isClosedForRead) {
        val bytesRead = channel.readAvailable(buffer)
        if (bytesRead > 0) {
            output.write(buffer, 0, bytesRead)
        }
    }
}

// Process streaming JSON
val streamResponse = client.get("https://api.example.com/stream")
val streamChannel = streamResponse.bodyAsChannel()

// Read line by line (for text streams)
val reader = streamChannel.toInputStream().bufferedReader()
reader.useLines { lines ->
    lines.forEach { line ->
        // Process each line as it arrives
        processJsonLine(line)
    }
}

Response Validation

Built-in response validation patterns and custom validation.

/**
 * Response validation is handled by HttpCallValidator plugin
 * Default validation can be configured in client setup
 */

Usage Examples:

import io.ktor.client.plugins.*

// Client with validation
val client = HttpClient {
    expectSuccess = true // Throw exception for non-success status codes
    
    HttpResponseValidator {
        validateResponse { response ->
            // Custom validation logic
            if (response.status.value in 400..499) {
                throw ClientRequestException(response, response.bodyAsText())
            }
        }
        
        handleResponseExceptionWithRequest { exception, request ->
            // Custom exception handling
            when (exception) {
                is ClientRequestException -> {
                    println("Client error for ${request.url}: ${exception.message}")
                }
                is ServerResponseException -> {
                    println("Server error for ${request.url}: ${exception.message}")
                }
            }
            throw exception
        }
    }
}

// Usage with validation
try {
    val response = client.get("https://api.example.com/users/999")
    val user: User = response.body()
} catch (e: ClientRequestException) {
    // Handle 4xx errors
    println("Client error: ${e.response.status}")
} catch (e: ServerResponseException) {
    // Handle 5xx errors
    println("Server error: ${e.response.status}")
}

Install with Tessl CLI

npx tessl i tessl/maven-io-ktor--ktor-client-core-iosx64

docs

client-configuration.md

cookie-management.md

engine-configuration.md

form-handling.md

http-caching.md

index.md

plugin-system.md

request-building.md

response-handling.md

websocket-support.md

tile.json