CtrlK
BlogDocsLog inGet started
Tessl Logo

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

Ktor HTTP client core library providing asynchronous HTTP client capabilities for Kotlin multiplatform applications

Pending
Overview
Eval results
Files

response-handling.mddocs/

Response Handling

Complete HTTP response processing including content reading, status handling, headers access, and type-safe response body parsing with support for various content types.

Capabilities

HttpResponse

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
}

Response Body Reading

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(): T

Usage 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()

Response Body Processing

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]
    }
}

Status Code Handling

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}")
    }
}

Header Access

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(", ")}")
}

Response Validation

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
}

Response Extensions

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
}

Types

// 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

docs

client-configuration.md

content-handling.md

engine-architecture.md

events-monitoring.md

http-statement.md

index.md

plugin-system.md

request-building.md

response-handling.md

websocket-support.md

tile.json