Ktor HTTP client core library providing asynchronous HTTP client functionality for multiplatform applications with macOS ARM64 support.
—
Response processing capabilities for extracting data from HTTP responses, handling different content types, and streaming response data with type-safe APIs.
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}")
}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)
}
}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
): StringUsage 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)
}
}
}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}")
}
}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
}
}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
): TUsage 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)
}
}
}
}
}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
)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