Response handling with multiple content types, streaming support, and type-safe body extraction. The response processing system provides comprehensive tools for handling HTTP responses, including content transformation, streaming, and error management.
Abstract base class representing an HTTP response with access to status, headers, and content.
/**
* Represents an HTTP response
*/
abstract class HttpResponse {
/** The call that produced this response */
abstract val call: HttpClientCall
/** HTTP status code */
abstract val status: HttpStatusCode
/** HTTP protocol version */
abstract val version: HttpProtocolVersion
/** Timestamp when the request was sent */
abstract val requestTime: GMTDate
/** Timestamp when the response was received */
abstract val responseTime: GMTDate
/** Response headers */
abstract val headers: Headers
/** Raw response content channel */
abstract val content: ByteReadChannel
}Extension functions for extracting response body content in various formats.
/**
* Read response body as text with optional charset fallback
*/
suspend fun HttpResponse.bodyAsText(
fallbackCharset: Charset = Charsets.UTF_8
): String
/**
* Read response body as byte array
*/
suspend fun HttpResponse.bodyAsBytes(): ByteArray
/**
* Get response body as a readable channel for streaming
*/
fun HttpResponse.bodyAsChannel(): ByteReadChannel
/**
* Read response body as a typed object using installed transformers
*/
suspend inline fun <reified T> HttpResponse.body(): T
/**
* Read response body as a typed object with explicit type information
*/
suspend fun <T> HttpResponse.body(typeInfo: TypeInfo): TPrepared HTTP request for deferred execution with streaming and resource management support.
/**
* A prepared HTTP request statement for deferred execution
*/
class HttpStatement(
private val builder: HttpRequestBuilder,
private val client: HttpClient
) {
/**
* Execute the request with a streaming response handler
* The response is automatically closed after the block executes
*/
suspend fun <T> execute(
block: suspend (response: HttpResponse) -> T
): T
/**
* Execute the request and return the complete response
* The caller is responsible for closing the response
*/
suspend fun execute(): HttpResponse
/**
* Execute the request and return the typed response body
*/
suspend inline fun <reified T> body(): T
/**
* Execute the request and process the typed response body
*/
suspend inline fun <reified T, R> body(
noinline block: suspend (response: T) -> R
): R
}Functions for extracting typed response bodies from HttpClientCall objects.
/**
* Extract typed response body from call
*/
suspend inline fun <reified T> HttpClientCall.body(): T
/**
* Extract typed response body with explicit type information
*/
suspend fun <T> HttpClientCall.body(typeInfo: TypeInfo): T
/**
* Extract nullable typed response body from call
*/
suspend fun <T> HttpClientCall.bodyNullable(typeInfo: TypeInfo): T?Built-in response validation and error handling mechanisms.
/**
* HTTP status code representation
*/
data class HttpStatusCode(
val value: Int,
val description: String
) {
companion object {
// 2xx Success
val OK: HttpStatusCode // 200
val Created: HttpStatusCode // 201
val Accepted: HttpStatusCode // 202
val NoContent: HttpStatusCode // 204
// 3xx Redirection
val MovedPermanently: HttpStatusCode // 301
val Found: HttpStatusCode // 302
val NotModified: HttpStatusCode // 304
// 4xx Client Error
val BadRequest: HttpStatusCode // 400
val Unauthorized: HttpStatusCode // 401
val Forbidden: HttpStatusCode // 403
val NotFound: HttpStatusCode // 404
val MethodNotAllowed: HttpStatusCode // 405
val Conflict: HttpStatusCode // 409
val UnprocessableEntity: HttpStatusCode // 422
val TooManyRequests: HttpStatusCode // 429
// 5xx Server Error
val InternalServerError: HttpStatusCode // 500
val NotImplemented: HttpStatusCode // 501
val BadGateway: HttpStatusCode // 502
val ServiceUnavailable: HttpStatusCode // 503
val GatewayTimeout: HttpStatusCode // 504
}
}
/**
* Check if status code represents success (2xx)
*/
val HttpStatusCode.isSuccess: Boolean
/**
* Check if status code represents informational response (1xx)
*/
val HttpStatusCode.isInformational: Boolean
/**
* Check if status code represents redirection (3xx)
*/
val HttpStatusCode.isredirection: Boolean
/**
* Check if status code represents client error (4xx)
*/
val HttpStatusCode.isClientError: Boolean
/**
* Check if status code represents server error (5xx)
*/
val HttpStatusCode.isServerError: BooleanResponse processing pipeline for custom transformations and interceptors.
/**
* Pipeline for processing HTTP responses
*/
class HttpResponsePipeline {
companion object {
/** Receive phase - initial response processing */
val Receive: PipelinePhase
/** Parse phase - content parsing and transformation */
val Parse: PipelinePhase
/** Transform phase - response transformation */
val Transform: PipelinePhase
/** State phase - state management */
val State: PipelinePhase
/** After phase - final processing */
val After: PipelinePhase
}
/**
* Install an interceptor in the pipeline
*/
fun intercept(
phase: PipelinePhase,
block: suspend PipelineContext<Any, HttpClientCall>.(Any) -> Unit
)
}Exception types for response processing errors.
/**
* Exception thrown when response body is received twice
*/
class DoubleReceiveException(
call: HttpClientCall
) : IllegalStateException()
/**
* Exception thrown when response pipeline processing fails
*/
class ReceivePipelineException(
request: HttpClientCall,
info: TypeInfo,
cause: Throwable
) : IllegalStateException()
/**
* Exception thrown when no transformation is found for response content
*/
class NoTransformationFoundException(
response: HttpResponse,
from: KClass<*>,
to: KClass<*>
) : UnsupportedOperationException()Usage Examples:
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.client.call.*
import io.ktor.http.*
import kotlinx.serialization.Serializable
val client = HttpClient()
// Basic response handling
val response = client.get("https://api.example.com/users")
val statusCode = response.status
val headers = response.headers
val bodyText = response.bodyAsText()
println("Status: ${statusCode.value} ${statusCode.description}")
println("Content-Type: ${headers[HttpHeaders.ContentType]}")
println("Body: $bodyText")
// Typed response body extraction
@Serializable
data class User(val id: Int, val name: String, val email: String)
val user: User = client.get("https://api.example.com/users/123").body()
// Response body as byte array
val imageBytes = client.get("https://api.example.com/images/logo.png").bodyAsBytes()
// Streaming response processing
val largeFileStatement = client.prepareGet("https://api.example.com/large-file.zip")
largeFileStatement.execute { response ->
val channel = response.bodyAsChannel()
val file = File("downloaded-file.zip")
val fileChannel = file.writeChannel()
try {
channel.copyAndClose(fileChannel)
println("File downloaded successfully")
} catch (e: Exception) {
println("Download failed: ${e.message}")
}
}
// Response validation
try {
val response = client.get("https://api.example.com/protected-resource")
when {
response.status.isSuccess -> {
val data = response.bodyAsText()
println("Success: $data")
}
response.status.isClientError -> {
val error = response.bodyAsText()
println("Client error (${response.status.value}): $error")
}
response.status.isServerError -> {
val error = response.bodyAsText()
println("Server error (${response.status.value}): $error")
}
response.status.isRedirection -> {
val location = response.headers[HttpHeaders.Location]
println("Redirect to: $location")
}
}
} catch (e: ClientRequestException) {
println("4xx error: ${e.response.status}")
} catch (e: ServerResponseException) {
println("5xx error: ${e.response.status}")
} catch (e: RedirectResponseException) {
println("3xx redirect: ${e.response.status}")
}
// Manual response resource management
val manualResponse = client.prepareGet("https://api.example.com/data").execute()
try {
val data = manualResponse.bodyAsText()
// Process data
} finally {
manualResponse.close() // Important: close response to free resources
}
// Processing call directly
val call = client.call {
method = HttpMethod.Get
url("https://api.example.com/users")
}
val callResponse = call.response
val callData: List<User> = call.body()
client.close()Content type handling and detection for responses.
/**
* Content type representation
*/
data class ContentType(
val contentType: String,
val contentSubtype: String,
val parameters: List<HeaderValueParam> = emptyList()
) {
companion object {
val Any: ContentType
object Application {
val Json: ContentType
val Xml: ContentType
val FormUrlEncoded: ContentType
val OctetStream: ContentType
val Pdf: ContentType
val Zip: ContentType
}
object Text {
val Plain: ContentType
val Html: ContentType
val CSS: ContentType
val JavaScript: ContentType
val CSV: ContentType
}
object Image {
val PNG: ContentType
val JPEG: ContentType
val GIF: ContentType
val SVG: ContentType
}
object MultiPart {
val FormData: ContentType
val Mixed: ContentType
val Alternative: ContentType
}
}
/**
* Check if this content type matches another
*/
fun match(other: ContentType): Boolean
/**
* Check if this content type matches a pattern
*/
fun match(pattern: String): Boolean
/**
* Add or replace a parameter
*/
fun withParameter(name: String, value: String): ContentType
/**
* Add charset parameter
*/
fun withCharset(charset: Charset): ContentType
}
/**
* Get content type from response headers
*/
val HttpResponse.contentType: ContentType?Header access and manipulation for HTTP responses.
/**
* HTTP headers interface
*/
interface Headers {
/** Get all header names */
fun names(): Set<String>
/** Get all values for a header */
fun getAll(name: String): List<String>?
/** Get first value for a header */
operator fun get(name: String): String?
/** Check if header exists */
fun contains(name: String): Boolean
/** Check if header exists with specific value */
fun contains(name: String, value: String): Boolean
/** Iterate over all headers */
fun forEach(action: (String, List<String>) -> Unit)
}
/**
* Common HTTP header names
*/
object HttpHeaders {
const val Accept = "Accept"
const val AcceptCharset = "Accept-Charset"
const val AcceptEncoding = "Accept-Encoding"
const val AcceptLanguage = "Accept-Language"
const val Authorization = "Authorization"
const val CacheControl = "Cache-Control"
const val Connection = "Connection"
const val ContentEncoding = "Content-Encoding"
const val ContentLength = "Content-Length"
const val ContentType = "Content-Type"
const val Cookie = "Cookie"
const val Date = "Date"
const val ETag = "ETag"
const val Expires = "Expires"
const val Host = "Host"
const val IfMatch = "If-Match"
const val IfModifiedSince = "If-Modified-Since"
const val IfNoneMatch = "If-None-Match"
const val LastModified = "Last-Modified"
const val Location = "Location"
const val Pragma = "Pragma"
const val Referer = "Referer"
const val Server = "Server"
const val SetCookie = "Set-Cookie"
const val UserAgent = "User-Agent"
const val WWWAuthenticate = "WWW-Authenticate"
}