Ktor HTTP client core library - asynchronous framework for creating HTTP clients in Kotlin multiplatform
—
Comprehensive response handling with typed body access, streaming capabilities, and flexible content processing for HTTP responses.
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: HttpRequestUsage 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}")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): TUsage 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 neededReusable 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}")
}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")]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(): ByteReadChannelUsage 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)
}
}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}")
}
}Advanced streaming capabilities for large responses or real-time data.
/**
* Process response as stream without loading entire content into memory
*/
suspend fun HttpResponse.bodyAsChannel(): ByteReadChannelUsage 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)
}
}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