Ktor Client
JetBrains-developed HTTP client supporting Kotlin multiplatform. Native Kotlin coroutines support, multiple engine compatibility (OkHttp, Apache, Curl, etc.). Enables common code across JVM, Android, JavaScript, Native platforms. Intuitive API design with DSL format.
GitHub Overview
ktorio/ktor
Framework for quickly creating connected applications in Kotlin with minimal effort
Topics
Star History
Library
Ktor Client
Overview
Ktor Client is a multiplatform HTTP client framework developed in Kotlin by JetBrains. It runs on various platforms including JVM, iOS, JavaScript, and Native, featuring coroutine-based asynchronous processing, rich plugin system, and type-safe APIs. Supporting WebSocket and Server-Sent Events (SSE), it handles a wide range of use cases from real-time communication to regular HTTP API access. It has been established as the standard HTTP solution in Kotlin Multiplatform development.
Details
Ktor Client 2025 edition has increased maturity as the core HTTP library in the Kotlin Multiplatform (KMP) ecosystem. Platform-specific optimization engines (OkHttp, CIO, Apache, etc.) deliver the best performance for each environment while providing a unified development experience through consistent APIs. Complete integration with coroutines enables intuitive asynchronous processing, with extensibility through comprehensive plugins for authentication, logging, caching, retry, etc. It offers a powerful architecture that allows sharing common HTTP logic across Android, iOS, desktop, and web development.
Key Features
- Multiplatform Support: Unified API across JVM, iOS, JS, and Native
- Complete Coroutine Integration: Natural and efficient asynchronous processing
- Rich Plugin System: Authentication, logging, caching, retry, etc.
- Real-time Communication: Native support for WebSocket and SSE
- Platform Optimization: High performance through platform-specific engines
- Type-safe API: Safety leveraging Kotlin's type system
Pros and Cons
Pros
- Code sharing and consistency in Kotlin multiplatform development
- Natural and readable asynchronous processing code based on coroutines
- High extensibility and customizability through plugin architecture
- High performance through optimization engines for each platform
- Integrated support for real-time communication like WebSocket and SSE
- Continuous development and community support by JetBrains
Cons
- Kotlin-only library, unavailable from other languages
- Complexity of multiplatform configuration (especially initial setup)
- Platform-specific constraints and behavioral differences exist
- Platform dependencies in some plugins and features
- Error handling may differ by platform in some cases
- Documentation and samples still developing in some areas
Reference Pages
Code Examples
Dependency Setup and Project Preparation
// build.gradle.kts (common module)
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.ktor:ktor-client-core:2.3.7")
implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
implementation("io.ktor:ktor-client-logging:2.3.7")
implementation("io.ktor:ktor-client-auth:2.3.7")
}
}
val jvmMain by getting {
dependencies {
implementation("io.ktor:ktor-client-cio:2.3.7")
implementation("io.ktor:ktor-client-apache:2.3.7")
implementation("ch.qos.logback:logback-classic:1.4.14")
}
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-okhttp:2.3.7")
}
}
val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-darwin:2.3.7")
}
}
val jsMain by getting {
dependencies {
implementation("io.ktor:ktor-client-js:2.3.7")
}
}
}
}
// Project configuration example
// gradle.properties
kotlin.mpp.enableCInteropCommonization=true
kotlin.native.ignoreDisabledTargets=true
// settings.gradle.kts
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
Basic HTTP Client Implementation
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.*
import kotlinx.serialization.Serializable
// Data class definitions
@Serializable
data class User(
val id: Int? = null,
val name: String,
val email: String,
val age: Int
)
@Serializable
data class ApiResponse<T>(
val success: Boolean,
val data: T?,
val message: String?
)
// Basic client configuration
class HttpClientExample {
private val client = HttpClient(CIO) {
install(ContentNegotiation) {
json()
}
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.INFO
}
}
// Basic GET request
suspend fun getUsers(): List<User> {
return try {
val response: HttpResponse = client.get("https://api.example.com/users")
println("Status: ${response.status}")
println("Headers: ${response.headers}")
response.body<List<User>>()
} catch (e: Exception) {
println("Error: ${e.message}")
emptyList()
}
}
// GET request with query parameters
suspend fun getUsersWithPagination(page: Int, limit: Int): List<User> {
return client.get("https://api.example.com/users") {
parameter("page", page)
parameter("limit", limit)
parameter("sort", "created_at")
headers {
append(HttpHeaders.Accept, "application/json")
append(HttpHeaders.UserAgent, "Ktor-Client/1.0")
}
}.body()
}
// POST request (JSON sending)
suspend fun createUser(user: User): User? {
return try {
val response: HttpResponse = client.post("https://api.example.com/users") {
contentType(ContentType.Application.Json)
setBody(user)
headers {
append(HttpHeaders.Authorization, "Bearer your-token")
}
}
if (response.status.isSuccess()) {
response.body<User>()
} else {
println("Error: ${response.status}")
null
}
} catch (e: Exception) {
println("Error creating user: ${e.message}")
null
}
}
// PUT request (data update)
suspend fun updateUser(id: Int, user: User): User? {
return try {
client.put("https://api.example.com/users/$id") {
contentType(ContentType.Application.Json)
setBody(user)
headers {
append(HttpHeaders.Authorization, "Bearer your-token")
}
}.body<User>()
} catch (e: Exception) {
println("Error updating user: ${e.message}")
null
}
}
// DELETE request
suspend fun deleteUser(id: Int): Boolean {
return try {
val response = client.delete("https://api.example.com/users/$id") {
headers {
append(HttpHeaders.Authorization, "Bearer your-token")
}
}
response.status == HttpStatusCode.NoContent
} catch (e: Exception) {
println("Error deleting user: ${e.message}")
false
}
}
fun close() {
client.close()
}
}
// Usage example
suspend fun main() = coroutineScope {
val httpClient = HttpClientExample()
try {
// Get user list
val users = httpClient.getUsers()
println("Number of users fetched: ${users.size}")
// Get with pagination
val pagedUsers = httpClient.getUsersWithPagination(1, 10)
println("Paginated users count: ${pagedUsers.size}")
// Create new user
val newUser = User(name = "John Doe", email = "[email protected]", age = 30)
val createdUser = httpClient.createUser(newUser)
println("Created user: $createdUser")
// Update user
createdUser?.let { user ->
val updatedUser = user.copy(name = "Jane Doe", age = 31)
val result = httpClient.updateUser(user.id!!, updatedUser)
println("Updated user: $result")
}
// Delete user
createdUser?.id?.let { id ->
val deleted = httpClient.deleteUser(id)
println("Deletion success: $deleted")
}
} finally {
httpClient.close()
}
}
Plugin Configuration and Advanced Features
import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
import io.ktor.client.plugins.cache.*
import io.ktor.client.plugins.compression.*
import io.ktor.client.plugins.cookies.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.coroutines.delay
// Advanced client configuration
class AdvancedHttpClient {
val client = HttpClient(CIO) {
// Timeout configuration
install(HttpTimeout) {
requestTimeoutMillis = 30_000
connectTimeoutMillis = 10_000
socketTimeoutMillis = 10_000
}
// Retry configuration
install(HttpRequestRetry) {
retryOnServerErrors(maxRetries = 3)
retryOnException(maxRetries = 3, retryOnTimeout = true)
exponentialDelay() // Exponential backoff
}
// Logging configuration
install(Logging) {
logger = object : Logger {
override fun log(message: String) {
println("HTTP: $message")
}
}
level = LogLevel.ALL
filter { request ->
request.url.host.contains("api.example.com")
}
}
// Authentication configuration
install(Auth) {
bearer {
loadTokens {
BearerTokens("your-access-token", "your-refresh-token")
}
refreshTokens {
// Token refresh logic
val refreshTokenInfo = client.post("https://api.example.com/auth/refresh") {
parameter("refresh_token", oldTokens?.refreshToken)
}.body<TokenResponse>()
BearerTokens(refreshTokenInfo.accessToken, refreshTokenInfo.refreshToken)
}
}
}
// Cookie management
install(HttpCookies) {
storage = AcceptAllCookiesStorage()
}
// Response compression support
install(ContentEncoding) {
gzip()
deflate()
}
// Cache configuration
install(HttpCache)
// Custom header configuration
defaultRequest {
header(HttpHeaders.UserAgent, "MyApp/1.0 (Ktor Client)")
header("X-API-Version", "v2")
}
// Response validation
HttpResponseValidator {
validateResponse { response ->
val error = when (response.status.value) {
in 300..399 -> "Redirect: ${response.status}"
in 400..499 -> "Client error: ${response.status}"
in 500..599 -> "Server error: ${response.status}"
else -> null
}
if (error != null) {
throw ClientRequestException(response, error)
}
}
handleResponseExceptionWithRequest { exception, request ->
val clientException = exception as? ClientRequestException
val response = clientException?.response
if (response?.status == HttpStatusCode.Unauthorized) {
println("Unauthorized access to ${request.url}")
}
throw exception
}
}
}
// Request with Bearer authentication
suspend fun authenticatedRequest(): String {
return client.get("https://api.example.com/protected") {
bearerAuth("your-access-token")
}.body()
}
// Basic authentication
suspend fun basicAuthRequest(): String {
return client.get("https://api.example.com/basic-auth") {
basicAuth("username", "password")
}.body()
}
// Request with custom headers
suspend fun customHeadersRequest(): String {
return client.get("https://api.example.com/data") {
headers {
append("X-Custom-Header", "CustomValue")
append("X-Request-ID", generateRequestId())
append("Accept-Language", "en-US,ja-JP")
}
// SSL configuration (for development environment)
url {
protocol = URLProtocol.HTTPS
host = "secure-api.example.com"
port = 443
}
}.body()
}
// Proxy configuration example
suspend fun proxyRequest(): String {
return HttpClient(CIO) {
engine {
proxy = ProxyBuilder.http("http://proxy.example.com:8080")
}
}.use { proxyClient ->
proxyClient.get("https://api.example.com/data").body()
}
}
private fun generateRequestId(): String {
return "req-${System.currentTimeMillis()}-${(1000..9999).random()}"
}
}
@Serializable
data class TokenResponse(
val accessToken: String,
val refreshToken: String,
val expiresIn: Long
)
WebSocket and Server-Sent Events (SSE)
import io.ktor.client.*
import io.ktor.client.plugins.websocket.*
import io.ktor.client.plugins.sse.*
import io.ktor.websocket.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.*
// WebSocket communication implementation
class WebSocketClient {
private val client = HttpClient(CIO) {
install(WebSockets) {
pingInterval = 20_000
maxFrameSize = Long.MAX_VALUE
}
}
// WebSocket connection and message sending/receiving
suspend fun connectWebSocket() {
client.webSocket(
method = HttpMethod.Get,
host = "ws.example.com",
port = 8080,
path = "/websocket"
) {
// Connection successful
println("WebSocket connection established")
// Send and receive messages concurrently
val messageJob = launch {
try {
for (frame in incoming) {
when (frame) {
is Frame.Text -> {
val message = frame.readText()
println("Received message: $message")
// JSON parsing example
try {
val data = Json.decodeFromString<WebSocketMessage>(message)
handleWebSocketMessage(data)
} catch (e: Exception) {
println("JSON parsing error: ${e.message}")
}
}
is Frame.Binary -> {
val bytes = frame.readBytes()
println("Binary data received: ${bytes.size} bytes")
}
is Frame.Close -> {
val reason = frame.readReason()
println("WebSocket disconnected: $reason")
break
}
else -> {
println("Other frame: ${frame.frameType}")
}
}
}
} catch (e: Exception) {
println("Message reception error: ${e.message}")
}
}
// Send ping messages periodically
val pingJob = launch {
while (isActive) {
try {
send(Frame.Ping("ping".toByteArray()))
delay(30_000) // 30-second interval
} catch (e: Exception) {
println("Ping send error: ${e.message}")
break
}
}
}
// User message sending example
launch {
delay(1000) // Wait for connection to stabilize
val messages = listOf(
WebSocketMessage("user_join", "John Doe joined the chat"),
WebSocketMessage("chat_message", "Hello everyone!"),
WebSocketMessage("typing", "Typing..."),
WebSocketMessage("chat_message", "How are you doing?")
)
for (message in messages) {
val json = Json.encodeToString(message)
send(Frame.Text(json))
println("Sent message: $json")
delay(2000)
}
}
// Connection maintenance and cleanup
try {
messageJob.join()
} finally {
pingJob.cancel()
println("WebSocket connection ended")
}
}
}
private fun handleWebSocketMessage(message: WebSocketMessage) {
when (message.type) {
"chat_message" -> println("Chat: ${message.data}")
"user_join" -> println("Join notification: ${message.data}")
"user_leave" -> println("Leave notification: ${message.data}")
"notification" -> println("Notification: ${message.data}")
else -> println("Unknown message type: ${message.type}")
}
}
fun close() {
client.close()
}
}
// Server-Sent Events (SSE) client
class SSEClient {
private val client = HttpClient(CIO) {
install(SSE)
}
// SSE connection and event reception
suspend fun connectSSE() {
client.sse(
host = "api.example.com",
port = 443,
path = "/events"
) {
println("SSE connection started")
// Event stream processing
incoming.collect { event ->
when (event.event) {
"message" -> {
println("Message event: ${event.data}")
}
"notification" -> {
val notification = Json.decodeFromString<NotificationEvent>(event.data ?: "")
handleNotification(notification)
}
"status" -> {
println("Status update: ${event.data}")
}
"heartbeat" -> {
println("Heartbeat: ${event.id}")
}
else -> {
println("Unknown event [${event.event}]: ${event.data}")
}
}
}
}
}
// Authenticated SSE connection
suspend fun authenticatedSSE(token: String) {
client.sse(
urlString = "https://api.example.com/secured-events",
request = {
bearerAuth(token)
header("Accept", "text/event-stream")
header("Cache-Control", "no-cache")
}
) {
incoming.collect { event ->
println("Authenticated event [${event.event}]: ${event.data}")
// Reconnection logic
if (event.event == "reconnect") {
println("Reconnection request from server")
// Implement reconnection logic here
}
}
}
}
private fun handleNotification(notification: NotificationEvent) {
println("Notification: ${notification.title} - ${notification.message}")
when (notification.priority) {
"high" -> println("⚠️ Important notification")
"medium" -> println("ℹ️ Normal notification")
"low" -> println("💡 Information notification")
}
}
fun close() {
client.close()
}
}
@Serializable
data class WebSocketMessage(
val type: String,
val data: String,
val timestamp: Long = System.currentTimeMillis()
)
@Serializable
data class NotificationEvent(
val title: String,
val message: String,
val priority: String = "medium",
val timestamp: Long = System.currentTimeMillis()
)
// Usage examples
suspend fun webSocketExample() = coroutineScope {
val wsClient = WebSocketClient()
try {
wsClient.connectWebSocket()
} catch (e: Exception) {
println("WebSocket connection error: ${e.message}")
} finally {
wsClient.close()
}
}
suspend fun sseExample() = coroutineScope {
val sseClient = SSEClient()
try {
// Regular SSE connection
launch {
sseClient.connectSSE()
}
// Authenticated SSE connection
launch {
sseClient.authenticatedSSE("your-bearer-token")
}
delay(30_000) // Maintain connection for 30 seconds
} catch (e: Exception) {
println("SSE connection error: ${e.message}")
} finally {
sseClient.close()
}
}
Error Handling and Retry Functionality
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.*
import kotlin.time.Duration.Companion.seconds
// Custom exception classes
sealed class ApiException(message: String, cause: Throwable? = null) : Exception(message, cause) {
class NetworkException(message: String, cause: Throwable? = null) : ApiException(message, cause)
class AuthenticationException(message: String) : ApiException(message)
class AuthorizationException(message: String) : ApiException(message)
class NotFoundException(message: String) : ApiException(message)
class RateLimitException(message: String, val retryAfter: Long? = null) : ApiException(message)
class ServerException(message: String, val statusCode: Int) : ApiException(message)
class TimeoutException(message: String, cause: Throwable? = null) : ApiException(message, cause)
}
// Robust HTTP client implementation
class RobustHttpClient {
private val client = HttpClient(CIO) {
// Timeout configuration
install(HttpTimeout) {
requestTimeoutMillis = 30_000
connectTimeoutMillis = 10_000
socketTimeoutMillis = 15_000
}
// Retry configuration
install(HttpRequestRetry) {
retryOnServerErrors(maxRetries = 3)
retryOnException(maxRetries = 3, retryOnTimeout = true)
retryIf(maxRetries = 5) { request, response ->
response.status == HttpStatusCode.TooManyRequests
}
exponentialDelay(base = 2.0, maxDelayMs = 10_000)
modifyRequest { request ->
request.headers.append("X-Retry-Count", retryCount.toString())
}
}
// Error response handling
HttpResponseValidator {
validateResponse { response ->
when (response.status.value) {
in 200..299 -> return@validateResponse
401 -> throw ApiException.AuthenticationException("Authentication failed")
403 -> throw ApiException.AuthorizationException("Access permission denied")
404 -> throw ApiException.NotFoundException("Resource not found")
429 -> {
val retryAfter = response.headers["Retry-After"]?.toLongOrNull()
throw ApiException.RateLimitException("Rate limit reached", retryAfter)
}
in 500..599 -> throw ApiException.ServerException(
"Server error occurred", response.status.value
)
else -> throw ApiException.NetworkException("HTTP error: ${response.status}")
}
}
handleResponseExceptionWithRequest { exception, request ->
when (exception) {
is HttpRequestTimeoutException -> {
throw ApiException.TimeoutException("Request timeout: ${request.url}", exception)
}
is ConnectTimeoutException -> {
throw ApiException.NetworkException("Connection timeout: ${request.url}", exception)
}
else -> throw exception
}
}
}
}
// Safe API call implementation
suspend fun <T> safeApiCall(
request: suspend HttpClient.() -> HttpResponse,
parser: suspend (HttpResponse) -> T
): Result<T> {
return try {
val response = client.request()
val result = parser(response)
Result.success(result)
} catch (e: ApiException) {
println("API exception: ${e.message}")
Result.failure(e)
} catch (e: Exception) {
println("Unexpected exception: ${e.message}")
Result.failure(ApiException.NetworkException("Unexpected error", e))
}
}
// Manual retry implementation
suspend fun <T> retryableRequest(
maxRetries: Int = 3,
initialDelay: Long = 1000,
maxDelay: Long = 10000,
request: suspend () -> T
): T {
var currentDelay = initialDelay
var lastException: Exception? = null
repeat(maxRetries + 1) { attempt ->
try {
return request()
} catch (e: ApiException.RateLimitException) {
// Wait specified time for rate limiting
val waitTime = e.retryAfter?.let { it * 1000 } ?: currentDelay
println("Rate limit - waiting ${waitTime}ms before retry (attempt ${attempt + 1})")
delay(waitTime)
lastException = e
} catch (e: ApiException.ServerException) {
if (attempt < maxRetries) {
println("Server error - waiting ${currentDelay}ms before retry (attempt ${attempt + 1})")
delay(currentDelay)
currentDelay = minOf(currentDelay * 2, maxDelay)
lastException = e
} else {
throw e
}
} catch (e: ApiException.NetworkException) {
if (attempt < maxRetries) {
println("Network error - waiting ${currentDelay}ms before retry (attempt ${attempt + 1})")
delay(currentDelay)
currentDelay = minOf(currentDelay * 2, maxDelay)
lastException = e
} else {
throw e
}
} catch (e: Exception) {
throw e // Immediately re-throw other exceptions
}
}
throw lastException ?: ApiException.NetworkException("Maximum retry attempts reached")
}
// Usage example: Get user (with error handling)
suspend fun getUser(id: Int): Result<User> {
return safeApiCall(
request = {
get("https://api.example.com/users/$id") {
header(HttpHeaders.Accept, "application/json")
}
},
parser = { response -> response.body<User>() }
)
}
// Usage example: Parallel requests (with error handling)
suspend fun getMultipleUsers(ids: List<Int>): List<Result<User>> {
return withContext(Dispatchers.IO) {
ids.map { id ->
async {
retryableRequest {
getUser(id).getOrThrow()
}
}.let { deferred ->
try {
Result.success(deferred.await())
} catch (e: Exception) {
Result.failure(e)
}
}
}
}
}
// Circuit breaker pattern implementation
class CircuitBreaker(
private val failureThreshold: Int = 5,
private val recoveryTimeout: Long = 60_000, // 1 minute
private val halfOpenMaxCalls: Int = 3
) {
private var state = State.CLOSED
private var failureCount = 0
private var lastFailureTime = 0L
private var halfOpenCalls = 0
enum class State { CLOSED, OPEN, HALF_OPEN }
suspend fun <T> execute(operation: suspend () -> T): T {
when (state) {
State.CLOSED -> {
return try {
val result = operation()
reset()
result
} catch (e: Exception) {
recordFailure()
throw e
}
}
State.OPEN -> {
if (System.currentTimeMillis() - lastFailureTime > recoveryTimeout) {
state = State.HALF_OPEN
halfOpenCalls = 0
return execute(operation)
} else {
throw ApiException.NetworkException("Circuit breaker is open")
}
}
State.HALF_OPEN -> {
return try {
if (halfOpenCalls >= halfOpenMaxCalls) {
throw ApiException.NetworkException("Half-open state attempt limit exceeded")
}
halfOpenCalls++
val result = operation()
reset()
result
} catch (e: Exception) {
state = State.OPEN
lastFailureTime = System.currentTimeMillis()
throw e
}
}
}
}
private fun recordFailure() {
failureCount++
lastFailureTime = System.currentTimeMillis()
if (failureCount >= failureThreshold) {
state = State.OPEN
}
}
private fun reset() {
failureCount = 0
state = State.CLOSED
}
fun getState(): State = state
fun getFailureCount(): Int = failureCount
}
fun close() {
client.close()
}
}
// Usage example
suspend fun errorHandlingExample() = coroutineScope {
val robustClient = RobustHttpClient()
val circuitBreaker = RobustHttpClient.CircuitBreaker()
try {
// Single user retrieval
val userResult = robustClient.getUser(123)
userResult.fold(
onSuccess = { user -> println("User retrieval success: $user") },
onFailure = { error -> println("User retrieval failed: ${error.message}") }
)
// Multiple users parallel retrieval
val userIds = listOf(1, 2, 3, 4, 5)
val results = robustClient.getMultipleUsers(userIds)
results.forEachIndexed { index, result ->
result.fold(
onSuccess = { user -> println("User ${userIds[index]} retrieval success: $user") },
onFailure = { error -> println("User ${userIds[index]} retrieval failed: ${error.message}") }
)
}
// Circuit breaker usage example
repeat(10) { i ->
try {
circuitBreaker.execute {
robustClient.getUser(i).getOrThrow()
}
println("User retrieval via circuit breaker success: $i")
} catch (e: Exception) {
println("Circuit breaker via failure: ${e.message}")
println("Circuit breaker state: ${circuitBreaker.getState()}")
}
delay(1000)
}
} finally {
robustClient.close()
}
}
Multiplatform Support and Platform-specific Implementation
// common/src/commonMain/kotlin/HttpClientFactory.kt
expect object HttpClientFactory {
fun create(): HttpClient
}
// common/src/commonMain/kotlin/ApiClient.kt
class ApiClient {
private val client = HttpClientFactory.create()
suspend fun getData(): String {
return client.get("https://api.example.com/data").body()
}
fun close() {
client.close()
}
}
// jvm/src/jvmMain/kotlin/HttpClientFactory.kt
import io.ktor.client.*
import io.ktor.client.engine.cio.*
actual object HttpClientFactory {
actual fun create(): HttpClient = HttpClient(CIO) {
engine {
maxConnectionsCount = 1000
endpoint {
maxConnectionsPerRoute = 100
pipelineMaxSize = 20
keepAliveTime = 5000
connectTimeout = 5000
connectAttempts = 5
}
}
}
}
// android/src/androidMain/kotlin/HttpClientFactory.kt
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
actual object HttpClientFactory {
actual fun create(): HttpClient = HttpClient(OkHttp) {
engine {
preconfigured = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
}
}
}
// ios/src/iosMain/kotlin/HttpClientFactory.kt
import io.ktor.client.*
import io.ktor.client.engine.darwin.*
actual object HttpClientFactory {
actual fun create(): HttpClient = HttpClient(Darwin) {
engine {
configureRequest {
setAllowsCellularAccess(true)
setAllowsExpensiveNetworkAccess(true)
setAllowsConstrainedNetworkAccess(true)
}
}
}
}
// js/src/jsMain/kotlin/HttpClientFactory.kt
import io.ktor.client.*
import io.ktor.client.engine.js.*
actual object HttpClientFactory {
actual fun create(): HttpClient = HttpClient(Js) {
engine {
// JavaScript engine specific configuration
}
}
}
// Usage example: Platform-common API calls
// common/src/commonMain/kotlin/UserRepository.kt
class UserRepository {
private val apiClient = ApiClient()
suspend fun getUsers(): List<User> {
return try {
val response = apiClient.client.get("https://api.example.com/users")
response.body<List<User>>()
} catch (e: Exception) {
println("Error fetching users: ${e.message}")
emptyList()
}
}
suspend fun createUser(user: User): User? {
return try {
val response = apiClient.client.post("https://api.example.com/users") {
contentType(ContentType.Application.Json)
setBody(user)
}
response.body<User>()
} catch (e: Exception) {
println("Error creating user: ${e.message}")
null
}
}
fun close() {
apiClient.close()
}
}
// Usage example for each platform
suspend fun multiplatformExample() {
val userRepository = UserRepository()
try {
// Same code works on all platforms
val users = userRepository.getUsers()
println("Number of users fetched: ${users.size}")
val newUser = User(name = "Test User", email = "[email protected]", age = 25)
val createdUser = userRepository.createUser(newUser)
createdUser?.let {
println("User creation success: ${it.name}")
}
} finally {
userRepository.close()
}
}