Ktor HTTP client core library providing asynchronous HTTP client capabilities for Kotlin multiplatform applications
—
Complete HTTP response processing including content reading, status handling, headers access, and type-safe response body parsing with support for various content types.
Core response handling functionality.
/**
* Abstract representation of HTTP response
*/
abstract class HttpResponse : HttpMessage, CoroutineScope {
/** HTTP status code */
abstract val status: HttpStatusCode
/** HTTP protocol version */
abstract val version: HttpProtocolVersion
/** Request timestamp */
abstract val requestTime: GMTDate
/** Response timestamp */
abstract val responseTime: GMTDate
/** Associated HTTP client call */
abstract val call: HttpClientCall
/** Response headers */
abstract override val headers: Headers
/** Response content */
abstract val content: ByteReadChannel
}Read response content in various formats.
/**
* Read response body as text
* @param charset - Character encoding (defaults to UTF-8)
*/
suspend fun HttpResponse.bodyAsText(
charset: Charset = Charsets.UTF_8
): String
/**
* Read response body with generic type
*/
suspend inline fun <reified T> HttpResponse.body(): T
/**
* Get response body as ByteReadChannel for streaming
*/
suspend fun HttpResponse.bodyAsChannel(): ByteReadChannel
/**
* Read response body with type information
* @param typeInfo - Type information for deserialization
*/
suspend fun <T> HttpResponse.body(typeInfo: TypeInfo): T
/**
* Read response body as specific type (requires content negotiation)
*/
suspend inline fun <reified T> HttpResponse.body(): TUsage Examples:
val client = HttpClient()
// Read as text
val response = client.get("https://api.example.com/data")
val textContent: String = response.bodyAsText()
println(textContent)
// Read as byte array
val imageResponse = client.get("https://api.example.com/image.jpg")
val imageBytes: ByteArray = imageResponse.body()
// Stream content
val largeFileResponse = client.get("https://api.example.com/large-file")
val channel: ByteReadChannel = largeFileResponse.bodyAsChannel()
while (!channel.isClosedForRead) {
val packet = channel.readRemaining(8192)
// Process packet
}
// Deserialize JSON (with ContentNegotiation plugin)
data class User(val id: Int, val name: String, val email: String)
val userResponse = client.get("https://api.example.com/users/123")
val user: User = userResponse.body()Advanced body processing with various content reading strategies.
/**
* Read response body as byte array
*/
suspend fun HttpResponse.bodyAsBytes(): ByteArray
/**
* Read response body with fallback charset
*/
suspend fun HttpResponse.bodyAsText(
fallbackCharset: Charset = Charsets.UTF_8
): String
/**
* Check if response body can be received multiple times
*/
val HttpResponse.isClosedForReceive: Boolean
/**
* Response container for typed responses
*/
data class HttpResponseContainer(
val expectedType: TypeInfo,
val response: Any
)Usage Examples:
val client = HttpClient()
// Read as different formats
val response = client.get("https://api.example.com/data")
// As bytes
val bytes: ByteArray = response.bodyAsBytes()
// As text with specific charset
val text = response.bodyAsText(Charsets.ISO_8859_1)
// Streaming large responses
val largeResponse = client.get("https://api.example.com/large-file")
val channel = largeResponse.bodyAsChannel()
val buffer = ByteArray(8192)
while (!channel.isClosedForRead) {
val bytesRead = channel.readAvailable(buffer)
if (bytesRead > 0) {
// Process buffer[0..bytesRead]
}
}Work with HTTP status codes and handle different response scenarios.
/**
* 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 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 {
// Success codes
val OK = HttpStatusCode(200, "OK")
val Created = HttpStatusCode(201, "Created")
val Accepted = HttpStatusCode(202, "Accepted")
val NoContent = HttpStatusCode(204, "No Content")
// Redirection codes
val MovedPermanently = HttpStatusCode(301, "Moved Permanently")
val Found = HttpStatusCode(302, "Found")
val NotModified = HttpStatusCode(304, "Not Modified")
// Client error codes
val BadRequest = HttpStatusCode(400, "Bad Request")
val Unauthorized = HttpStatusCode(401, "Unauthorized")
val Forbidden = HttpStatusCode(403, "Forbidden")
val NotFound = HttpStatusCode(404, "Not Found")
// Server error codes
val InternalServerError = HttpStatusCode(500, "Internal Server Error")
val BadGateway = HttpStatusCode(502, "Bad Gateway")
val ServiceUnavailable = HttpStatusCode(503, "Service Unavailable")
}
}Usage Examples:
val response = client.get("https://api.example.com/users/123")
when {
response.status.isSuccess() -> {
val user = response.bodyAsText()
println("User found: $user")
}
response.status == HttpStatusCode.NotFound -> {
println("User not found")
}
response.status.isClientError() -> {
println("Client error: ${response.status}")
}
response.status.isServerError() -> {
println("Server error: ${response.status}")
}
}Access and manipulate response headers.
/**
* Response headers interface
*/
interface Headers {
/** Get header value */
operator fun get(name: String): String?
/** Get all values for header */
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 all header names */
fun names(): Set<String>
/** Check if headers are empty */
fun isEmpty(): Boolean
}Usage Examples:
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 lastModified = response.headers["Last-Modified"]
// Check for header existence
if (response.headers.contains("ETag")) {
val etag = response.headers["ETag"]
println("ETag: $etag")
}
// Get all values for a header (e.g., Set-Cookie)
val cookies = response.headers.getAll("Set-Cookie")
cookies?.forEach { cookie ->
println("Cookie: $cookie")
}
// Iterate all headers
response.headers.names().forEach { headerName ->
val values = response.headers.getAll(headerName)
println("$headerName: ${values?.joinToString(", ")}")
}Validate responses and handle errors.
/**
* Exception thrown for client error responses (4xx)
*/
class ClientRequestException(
val response: HttpResponse,
val cachedResponseText: String
) : ResponseException(response, cachedResponseText)
/**
* Exception thrown for server error responses (5xx)
*/
class ServerResponseException(
val response: HttpResponse,
val cachedResponseText: String
) : ResponseException(response, cachedResponseText)
/**
* Exception thrown for redirect responses when followRedirects is false
*/
class RedirectResponseException(
val response: HttpResponse,
val cachedResponseText: String
) : ResponseException(response, cachedResponseText)
/**
* Base exception for HTTP response errors
*/
abstract class ResponseException(
val response: HttpResponse,
val cachedResponseText: String
) : IllegalStateException("Bad response: ${response.status}")Usage Examples:
try {
val response = client.get("https://api.example.com/protected-resource") {
// Client configured with expectSuccess = true (default)
}
val data = response.bodyAsText()
println(data)
} catch (e: ClientRequestException) {
when (e.response.status) {
HttpStatusCode.Unauthorized -> println("Authentication required")
HttpStatusCode.Forbidden -> println("Access denied")
HttpStatusCode.NotFound -> println("Resource not found")
else -> println("Client error: ${e.response.status}")
}
} catch (e: ServerResponseException) {
println("Server error: ${e.response.status}")
} catch (e: RedirectResponseException) {
println("Redirect not followed: ${e.response.status}")
}
// Or handle manually with expectSuccess = false
val client = HttpClient {
expectSuccess = false
}
val response = client.get("https://api.example.com/resource")
if (response.status.isSuccess()) {
val data = response.bodyAsText()
// Process successful response
} else {
val errorBody = response.bodyAsText()
// Handle error response
}Utility extensions for common response operations.
/**
* Safely read response body as text with error handling
*/
suspend fun HttpResponse.bodyAsTextOrNull(): String? = try {
bodyAsText()
} catch (e: Exception) {
null
}
/**
* Read response body with fallback
*/
suspend fun HttpResponse.bodyAsTextOrElse(
fallback: suspend () -> String
): String = try {
bodyAsText()
} catch (e: Exception) {
fallback()
}
/**
* Check if response has specific content type
*/
fun HttpResponse.hasContentType(contentType: ContentType): Boolean {
val responseContentType = headers[HttpHeaders.ContentType]
return responseContentType?.contains(contentType.toString()) == true
}// Response types
data class HttpResponseData(
val statusCode: HttpStatusCode,
val requestTime: GMTDate,
val headers: Headers,
val version: HttpProtocolVersion,
val body: Any,
val callContext: CoroutineContext
)
// Protocol version
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, 1)
val QUIC = HttpProtocolVersion("QUIC", 1, 0)
}
}
// Time types
class GMTDate(
val timestamp: Long
) {
fun toJvmDate(): java.util.Date
override fun toString(): String
}
// Type information for deserialization
class TypeInfo(
val type: KClass<*>,
val reifiedType: Type,
val kotlinType: KType? = null
)
// Exception types
sealed class HttpRequestException(
message: String,
cause: Throwable? = null
) : Exception(message, cause)
class DoubleReceiveException(
call: HttpClientCall
) : IllegalStateException(
"Response has already been received for call: $call"
)Install with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor-client-core-jvm