CtrlK
BlogDocsLog inGet started
Tessl Logo

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

Ktor HTTP client core library providing asynchronous HTTP client functionality for multiplatform applications with macOS ARM64 support.

Pending
Overview
Eval results
Files

response-handling.mddocs/

Response Handling and Processing

Response processing capabilities for extracting data from HTTP responses, handling different content types, and streaming response data with type-safe APIs.

Capabilities

HttpResponse Interface

Main interface for representing HTTP responses with access to status, headers, and body content.

/**
 * HTTP response representation providing access to status, headers, and body
 */
interface HttpResponse {
    /** Associated HTTP client call */
    val call: HttpClientCall
    
    /** HTTP status code */
    val status: HttpStatusCode
    
    /** HTTP protocol version */
    val version: HttpProtocolVersion
    
    /** Request timestamp */
    val requestTime: GMTDate
    
    /** Response timestamp */
    val responseTime: GMTDate
    
    /** Response headers */
    val headers: Headers
    
    /** Coroutine context for the response */
    override val coroutineContext: CoroutineContext
}

Usage Examples:

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

val client = HttpClient()

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

// Access response properties
println("Status: ${response.status}")
println("Content-Type: ${response.headers["Content-Type"]}")
println("Request time: ${response.requestTime}")
println("Response time: ${response.responseTime}")

// Check status
if (response.status.isSuccess()) {
    val content = response.bodyAsText()
    println("Success: $content")
} else {
    println("Error: ${response.status.description}")
}

// Check specific status codes
when (response.status) {
    HttpStatusCode.OK -> println("Success")
    HttpStatusCode.NotFound -> println("Resource not found")
    HttpStatusCode.Unauthorized -> println("Authentication required")
    else -> println("Status: ${response.status.value}")
}

HttpStatement Class

Prepared HTTP statement for deferred execution and streaming response handling.

/**
 * Prepared HTTP statement for deferred execution with streaming support
 */
class HttpStatement(
    private val builder: HttpRequestBuilder,
    private val client: HttpClient
) {
    /** Execute statement and handle response with custom block */
    suspend fun <T> execute(block: suspend (response: HttpResponse) -> T): T
    
    /** Execute statement and deserialize response body to specified type */
    suspend inline fun <reified T> body(): T
    
    /** Execute statement and get response body as text */
    suspend fun bodyAsText(fallbackCharset: Charset = Charsets.UTF_8): String
    
    /** Execute statement and get response body as byte read channel */
    suspend fun bodyAsChannel(): ByteReadChannel
    
    /** Execute statement and get response body as byte array */
    suspend fun bodyAsBytes(): ByteArray
}

Usage Examples:

import io.ktor.client.*
import io.ktor.client.statement.*
import io.ktor.utils.io.*

val client = HttpClient()

// Prepare statement for reuse
val statement = client.prepareGet("https://api.example.com/data")

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

// Direct body extraction
val text = statement.bodyAsText()
val bytes = statement.bodyAsBytes()

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

Response Body Extraction

Extension functions for extracting response body content in various formats.

/**
 * Deserialize response body to specified type using installed content negotiation
 * @return Deserialized object of type T
 */
suspend inline fun <reified T> HttpResponse.body(): T

/**
 * 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 byte read channel for streaming
 * @return ByteReadChannel for streaming response content
 */
suspend fun HttpResponse.bodyAsChannel(): ByteReadChannel

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

/**
 * Read complete response body as byte array (alias for bodyAsBytes)
 * @return Response body as ByteArray
 */
suspend fun HttpResponse.readBytes(): ByteArray

/**
 * Read complete response body as text (alias for bodyAsText)
 * @param fallbackCharset Charset fallback
 * @return Response body as string
 */
suspend fun HttpResponse.readText(
    fallbackCharset: Charset = Charsets.UTF_8
): String

Usage Examples:

import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.client.call.*
import kotlinx.serialization.Serializable

val client = HttpClient {
    install(ContentNegotiation) {
        json()
    }
}

@Serializable
data class User(val id: Long, val name: String, val email: String)

@Serializable
data class ApiResponse<T>(val data: T, val status: String)

// Deserialize JSON response
val response = client.get("https://api.example.com/users/123")
val user: User = response.body()
println("User: ${user.name}")

// Generic API response
val apiResponse: ApiResponse<User> = response.body()
println("Status: ${apiResponse.status}, User: ${apiResponse.data.name}")

// Text response
val textResponse = client.get("https://api.example.com/status")
val statusText = textResponse.bodyAsText()
println("Status: $statusText")

// Binary response
val imageResponse = client.get("https://example.com/image.png")
val imageBytes = imageResponse.bodyAsBytes()
// Save to file or process binary data

// Streaming large response
val largeResponse = client.get("https://example.com/large-file")
val channel = largeResponse.bodyAsChannel()
val outputFile = File("downloaded-file")

outputFile.outputStream().use { output ->
    val buffer = ByteArray(8192)
    while (!channel.isClosedForRead) {
        val bytesRead = channel.readAvailable(buffer)
        if (bytesRead > 0) {
            output.write(buffer, 0, bytesRead)
        }
    }
}

Response Status Handling

Utilities for handling HTTP status codes and response validation.

/**
 * HTTP status code representation
 */
class HttpStatusCode(val value: Int, val description: String) {
    /** Check if status indicates success (2xx) */
    fun isSuccess(): Boolean = value in 200..299
    
    /** Check if status indicates redirection (3xx) */
    fun isRedirection(): Boolean = value in 300..399
    
    /** Check if status indicates client error (4xx) */
    fun isClientError(): Boolean = value in 400..499
    
    /** Check if status indicates server error (5xx) */
    fun isServerError(): Boolean = value in 500..599
    
    companion object {
        // Common status codes
        val Continue = HttpStatusCode(100, "Continue")
        val OK = HttpStatusCode(200, "OK")
        val Created = HttpStatusCode(201, "Created")
        val Accepted = HttpStatusCode(202, "Accepted")
        val NoContent = HttpStatusCode(204, "No Content")
        val MovedPermanently = HttpStatusCode(301, "Moved Permanently")
        val Found = HttpStatusCode(302, "Found")
        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 MethodNotAllowed = HttpStatusCode(405, "Method Not Allowed")
        val NotAcceptable = HttpStatusCode(406, "Not Acceptable")
        val Conflict = HttpStatusCode(409, "Conflict")
        val UnprocessableEntity = HttpStatusCode(422, "Unprocessable Entity")
        val InternalServerError = HttpStatusCode(500, "Internal Server Error")
        val NotImplemented = HttpStatusCode(501, "Not Implemented")
        val BadGateway = HttpStatusCode(502, "Bad Gateway")
        val ServiceUnavailable = HttpStatusCode(503, "Service Unavailable")
        val GatewayTimeout = HttpStatusCode(504, "Gateway Timeout")
    }
}

Usage Examples:

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

val client = HttpClient()

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

// Status checking
when {
    response.status.isSuccess() -> {
        println("Success: ${response.bodyAsText()}")
    }
    response.status.isClientError() -> {
        println("Client error: ${response.status.description}")
        if (response.status == HttpStatusCode.Unauthorized) {
            // Handle authentication
        } else if (response.status == HttpStatusCode.NotFound) {
            // Handle resource not found
        }
    }
    response.status.isServerError() -> {
        println("Server error: ${response.status.description}")
        // Handle server errors, maybe retry
    }
    response.status.isRedirection() -> {
        println("Redirection: ${response.headers["Location"]}")
    }
}

// Specific status code handling
val createResponse = client.post("https://api.example.com/users") {
    setBody(userData)
}

when (createResponse.status) {
    HttpStatusCode.Created -> {
        val newUser = createResponse.body<User>()
        println("User created with ID: ${newUser.id}")
    }
    HttpStatusCode.BadRequest -> {
        val error = createResponse.bodyAsText()
        println("Validation error: $error")
    }
    HttpStatusCode.Conflict -> {
        println("User already exists")
    }
    else -> {
        println("Unexpected status: ${createResponse.status}")
    }
}

Response Headers Access

Utilities for accessing and processing response headers.

/**
 * Response headers interface
 */
interface Headers : StringValues {
    /** Get header value by name */
    operator fun get(name: String): String?
    
    /** Get all header values by name */
    fun getAll(name: String): List<String>?
    
    /** Check if header exists */
    fun contains(name: String): Boolean
    
    /** Check if header contains specific value */
    fun contains(name: String, value: String): Boolean
    
    /** Get header names */
    fun names(): Set<String>
    
    /** Check if headers are empty */
    fun isEmpty(): Boolean
    
    /** Iterate over all header entries */
    fun entries(): Set<Map.Entry<String, List<String>>>
    
    /** Convert to map */
    fun toMap(): Map<String, List<String>>
}

Usage Examples:

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

val client = HttpClient()

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

// Access specific headers
val contentType = response.headers["Content-Type"]
val contentLength = response.headers["Content-Length"]?.toLongOrNull()
val etag = response.headers["ETag"]
val lastModified = response.headers["Last-Modified"]

println("Content-Type: $contentType")
println("Content-Length: $contentLength")
println("ETag: $etag")

// Check for header existence
if (response.headers.contains("Cache-Control")) {
    val cacheControl = response.headers["Cache-Control"]
    println("Cache-Control: $cacheControl")
}

// Get all values for multi-value headers
val setCookies = response.headers.getAll("Set-Cookie")
setCookies?.forEach { cookie ->
    println("Set-Cookie: $cookie")
}

// Iterate over all headers
response.headers.entries().forEach { (name, values) ->
    values.forEach { value ->
        println("$name: $value")
    }
}

// Check content type
val contentType2 = response.headers["Content-Type"]
when {
    contentType2?.contains("application/json") == true -> {
        val jsonData = response.body<JsonObject>()
        // Handle JSON response
    }
    contentType2?.contains("text/html") == true -> {
        val htmlContent = response.bodyAsText()
        // Handle HTML response
    }
    contentType2?.contains("image/") == true -> {
        val imageBytes = response.bodyAsBytes()
        // Handle image response
    }
}

Response Streaming and Processing

Advanced response processing for streaming, chunked, and large responses.

/**
 * Process response in streaming fashion
 * @param block Processing block receiving ByteReadChannel
 */
suspend fun <T> HttpResponse.bodyAsFlow(
    block: suspend (ByteReadChannel) -> T
): T

/**
 * Process response with custom channel handling
 * @param block Channel processing block
 */
suspend fun <T> HttpStatement.execute(
    block: suspend (HttpResponse) -> T
): T

Usage Examples:

import io.ktor.client.*
import io.ktor.client.statement.*
import io.ktor.utils.io.*
import java.io.File

val client = HttpClient()

// Download large file with progress
val downloadResponse = client.prepareGet("https://example.com/large-file.zip")

val outputFile = File("downloaded-file.zip")
var bytesDownloaded = 0L
val totalBytes = downloadResponse.execute { response ->
    response.headers["Content-Length"]?.toLongOrNull()
}

downloadResponse.execute { response ->
    val channel = response.bodyAsChannel()
    
    outputFile.outputStream().use { output ->
        val buffer = ByteArray(8192)
        while (!channel.isClosedForRead) {
            val bytesRead = channel.readAvailable(buffer)
            if (bytesRead > 0) {
                output.write(buffer, 0, bytesRead)
                bytesDownloaded += bytesRead
                
                // Progress reporting
                totalBytes?.let { total ->
                    val progress = (bytesDownloaded * 100 / total).toInt()
                    println("Download progress: $progress%")
                }
            }
        }
    }
}

// Process JSON streaming response
val streamingResponse = client.prepareGet("https://api.example.com/stream")

streamingResponse.execute { response ->
    val channel = response.bodyAsChannel()
    val buffer = StringBuilder()
    val tempBuffer = ByteArray(1024)
    
    while (!channel.isClosedForRead) {
        val bytesRead = channel.readAvailable(tempBuffer)
        if (bytesRead > 0) {
            val chunk = String(tempBuffer, 0, bytesRead, Charsets.UTF_8)
            buffer.append(chunk)
            
            // Process complete JSON objects
            while (buffer.contains('\n')) {
                val line = buffer.substring(0, buffer.indexOf('\n'))
                buffer.delete(0, buffer.indexOf('\n') + 1)
                
                if (line.isNotBlank()) {
                    // Process JSON line
                    processJsonLine(line)
                }
            }
        }
    }
}

Types

Response Types

class HttpResponseData(
    val statusCode: HttpStatusCode,
    val requestTime: GMTDate,
    val headers: Headers,
    val version: HttpProtocolVersion,
    val body: Any,
    val callContext: CoroutineContext
)

data class HttpResponseContainer(
    val expectedType: TypeInfo,
    val response: Any
)

enum class HttpProtocolVersion(val name: String, val major: Int, val minor: Int) {
    HTTP_1_0("HTTP/1.0", 1, 0),
    HTTP_1_1("HTTP/1.1", 1, 1),
    HTTP_2_0("HTTP/2.0", 2, 0),
    SPDY_3("SPDY/3", 3, 0),
    QUIC("QUIC", 1, 0)
}

data class GMTDate(
    val timestamp: Long,
    val seconds: Int,
    val minutes: Int,
    val hours: Int,
    val dayOfMonth: Int,
    val month: Month,
    val year: Int,
    val dayOfWeek: Weekday,
    val dayOfYear: Int
)

Exception Types

class DoubleReceiveException(call: HttpClientCall) : 
    IllegalStateException("Response already received: $call")

class NoTransformationFoundException(val from: KType, val to: KType) : 
    UnsupportedOperationException("No transformation found: $from -> $to")

class ResponseException(
    val response: HttpResponse,
    val cachedResponseText: String
) : IllegalStateException("Bad response: ${response.status}")

class RedirectResponseException(
    response: HttpResponse,
    cachedResponseText: String
) : ResponseException(response, cachedResponseText)

class ClientRequestException(
    response: HttpResponse,
    cachedResponseText: String
) : ResponseException(response, cachedResponseText)

class ServerResponseException(
    response: HttpResponse,
    cachedResponseText: String
) : ResponseException(response, cachedResponseText)

Install with Tessl CLI

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

docs

built-in-plugins.md

engine-configuration.md

form-data-content.md

http-client.md

index.md

plugin-system.md

request-building.md

response-handling.md

tile.json