Ktor HTTP Client has a common interface for performing HTTP Requests, but allows to specify an engine that does the internal job. Different engines have different configurations, dependencies and supporting features.
Table of contents:
By calling to the HttpClient method without specifying an engine, it uses a default engine.
val client = HttpClient()
In the case of the JVM, the default engine is resolved with a ServiceLoader, getting the first one available. Thus depends on the artifacts you have included.
For native, it uses the predefined one.
Ktor HttpClient lets you configure the parameters of each engine by calling HttpClient(MyHttpEngine) { engine { ... } }.
Every engine config has two common properties that can be set:
dispatcher property is the CoroutineDispatcher used when processing client requests.sslContext is a javax.net.ssl.SSLContext
allowing you to set custom keys, a trust manager or custom source for secure random data.val client = HttpClient(MyHttpEngine) {
    engine {
        dispatcher = HTTP_CLIENT_DEFAULT_DISPATCHER
        sslContext = SSLContext.getDefault()
    }
}
You can also adjust maximum total connections and maximum connections per route in Apache and CIO clients (but not Jetty).
Apache is the most configurable HTTP client about right now. It supports HTTP/1.1 and HTTP/2.
It is the only one that supports following redirects and allows you to configure timeouts,
proxies among other things it is supported by org.apache.httpcomponents:httpasyncclient.
A sample configuration would look like:
val client = HttpClient(Apache) {
    engine {
        followRedirects = true  // Follow HTTP Location redirects - default false. It uses the default number of redirects defined by Apache's HttpClient that is 50.
        // For timeouts: 0 means infinite, while negative value mean to use the system's default value
        socketTimeout = 10_000  // Max time between TCP packets - default 10 seconds
        connectTimeout = 10_000 // Max time to establish an HTTP connection - default 10 seconds
        connectionRequestTimeout = 20_000 // Max time for the connection manager to start a request - 20 seconds
        customizeClient {
            // Apache's HttpAsyncClientBuilder
            setProxy(HttpHost("127.0.0.1", 8080))
            setMaxConnTotal(1000) // Maximum number of socket connections.
            setMaxConnPerRoute(100) // Maximum number of requests for a specific endpoint route.
        }
        customizeRequest {
            // Apache's RequestConfig.Builder
        }
    }
}
io.ktor.client.engine.apache.Apache
        
        
            in the artifact io.ktor:ktor-client-apache:$ktor_version.
        
        
        It includes the org.apache.httpcomponents:httpasyncclient transitive dependency.
        
    dependencies {
    compile "io.ktor:ktor-client-apache:$ktor_version"
}
dependencies {
    compile("io.ktor:ktor-client-apache:$ktor_version")
}
<project>
    ...
    <dependencies>
        <dependency>
            <groupId>io.ktor</groupId>
            <artifactId>ktor-client-apache</artifactId>
            <version>${ktor.version}</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>
CIO (Coroutine-based I/O) is a Ktor implementation with no additional dependencies and is fully asynchronous. It only supports HTTP/1.x for now.
CIO provides maxConnectionsCount and a endpointConfig for configuring.
A sample configuration would look like:
val client = HttpClient(CIO) {
    engine {
        maxConnectionsCount = 1000 // Maximum number of socket connections.
        endpoint.apply {
            maxConnectionsPerRoute = 100 // Maximum number of requests for a specific endpoint route.
            pipelineMaxSize = 20 // Max number of opened endpoints.
            keepAliveTime = 5000 // Max number of milliseconds to keep each connection alive.
            connectTimeout = 5000 // Number of milliseconds to wait trying to connect to the server.
            connectRetryAttempts = 5 // Maximum number of attempts for retrying a connection.
        }
    }
}
io.ktor.client.engine.cio.CIO
        
        
            in the artifact io.ktor:ktor-client-cio:$ktor_version.
        
        
        
        
    dependencies {
    compile "io.ktor:ktor-client-cio:$ktor_version"
}
dependencies {
    compile("io.ktor:ktor-client-cio:$ktor_version")
}
<project>
    ...
    <dependencies>
        <dependency>
            <groupId>io.ktor</groupId>
            <artifactId>ktor-client-cio</artifactId>
            <version>${ktor.version}</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>
Jetty provides an additional sslContextFactory for configuring. It only supports HTTP/2 for now.
A sample configuration would look like:
val client = HttpClient(Jetty) {
    engine {
        sslContextFactory = SslContextFactory()
    }
}
io.ktor.client.engine.jetty.Jetty
        
        
            in the artifact io.ktor:ktor-client-jetty:$ktor_version.
        
        
        It includes the org.eclipse.jetty.http2:http2-client transitive dependency.
        
    dependencies {
    compile "io.ktor:ktor-client-jetty:$ktor_version"
}
dependencies {
    compile("io.ktor:ktor-client-jetty:$ktor_version")
}
<project>
    ...
    <dependencies>
        <dependency>
            <groupId>io.ktor</groupId>
            <artifactId>ktor-client-jetty</artifactId>
            <version>${ktor.version}</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>
There is a engine based on OkHttp:
val client = HttpClient(OkHttp) {
    engine {
        // https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.Builder.html
        config { // this: OkHttpClient.Builder ->
            // ...
            followRedirects(true)
            // ...
        }    
        
        // https://square.github.io/okhttp/3.x/okhttp/okhttp3/Interceptor.html
        addInterceptor(interceptor)
        addNetworkInterceptor(interceptor)
    }
    
}
io.ktor.client.engine.okhttp.OkHttp
        
        
            in the artifact io.ktor:ktor-client-okhttp:$ktor_version.
        
        
        It includes the com.squareup.okhttp3:okhttp transitive dependency.
        
    dependencies {
    compile "io.ktor:ktor-client-okhttp:$ktor_version"
}
dependencies {
    compile("io.ktor:ktor-client-okhttp:$ktor_version")
}
<project>
    ...
    <dependencies>
        <dependency>
            <groupId>io.ktor</groupId>
            <artifactId>ktor-client-okhttp</artifactId>
            <version>${ktor.version}</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>
The Android engine, doesn’t have additional dependencies, and uses a ThreadPool with a normal HttpURLConnection, to perform the requests. And can be configured like this:
val client = HttpClient(Android) {
    engine {
        connectTimeout = 100_000
        socketTimeout = 100_000
    }
}
io.ktor.client.engine.android.Android
        
        
            in the artifact io.ktor:ktor-client-android:$ktor_version.
        
        
        
        
    dependencies {
    compile "io.ktor:ktor-client-android:$ktor_version"
}
dependencies {
    compile("io.ktor:ktor-client-android:$ktor_version")
}
<project>
    ...
    <dependencies>
        <dependency>
            <groupId>io.ktor</groupId>
            <artifactId>ktor-client-android</artifactId>
            <version>${ktor.version}</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>
The iOS engine, uses the asynchronous NSURLSession internally. And have no additional configuration.
val client = HttpClient(Ios) {
}
io.ktor.client.engine.ios.Ios
        
        
            in the artifact io.ktor:ktor-client-ios:$ktor_version.
        
        
        
        
    dependencies {
    compile "io.ktor:ktor-client-ios:$ktor_version"
}
dependencies {
    compile("io.ktor:ktor-client-ios:$ktor_version")
}
<project>
    ...
    <dependencies>
        <dependency>
            <groupId>io.ktor</groupId>
            <artifactId>ktor-client-ios</artifactId>
            <version>${ktor.version}</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>
The Js engine, uses the fetch API internally. And have no additional configuration.
val client = HttpClient(Js) {
}
You can also call the JsClient() function to get the Js engine singleton.
io.ktor.client.engine.js.Js
        
        
            in the artifact io.ktor:ktor-client-js:$ktor_version.
        
        
        
        
    dependencies {
    compile "io.ktor:ktor-client-js:$ktor_version"
}
dependencies {
    compile("io.ktor:ktor-client-js:$ktor_version")
}
<project>
    ...
    <dependencies>
        <dependency>
            <groupId>io.ktor</groupId>
            <artifactId>ktor-client-js</artifactId>
            <version>${ktor.version}</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>
There is a engine specific for testing described in its own page: MockEngine for testing.