Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KTOR-6501 Add client KDocs for config, engine, and ObservableContent #4476

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,47 @@ package io.ktor.client
import io.ktor.client.engine.*
import io.ktor.client.plugins.*
import io.ktor.util.*
import io.ktor.util.collections.*
import io.ktor.utils.io.*
import kotlin.collections.set

/**
* A mutable [HttpClient] configuration.
* A mutable [HttpClient] configuration used to adjust settings, install plugins and interceptors.
*
* This class is available as block in [HttpClient] constructor or [HttpClient.config] builder:
* ```kotlin
* val client = HttpClient { // HttpClientConfig<Engine>()
* // Configure engine settings
* engine { // HttpClientEngineConfig
* threadsCount = 4
* pipelining = true
* }
*
* // Install and configure plugins
* install(ContentNegotiation) {
* json()
* }
*
* // Configure default request parameters
* defaultRequest {
* url("https://api.example.com")
* header("X-Custom-Header", "value")
* }
*
* // Configure client-wide settings
* expectSuccess = true
* followRedirects = true
* }
* ```
* ## Configuring [HttpClientEngine]
*
* If the engine is specified explicitly, engine-specific properties will be available in the engine block:
* ```kotlin
* val client = HttpClient(CIO) { // HttpClientConfig<CIOEngineConfig>.() -> Unit
* engine { // CIOEngineConfig.() -> Unit
* // engine specific properties
* }
* }
* ```
*
* Learn more about the client's configuration from
* [Creating and configuring a client](https://ktor.io/docs/create-client.html).
*/
Expand All @@ -25,7 +60,15 @@ public class HttpClientConfig<T : HttpClientEngineConfig> {
internal var engineConfig: T.() -> Unit = {}

/**
* Allows you to configure engine parameters.
* Builder for configuring engine-specific settings in [HttpClientEngineConfig]
* (like dispatcher, threads count, proxy, etc.)
*
* ```kotlin
* val client = HttpClient(CIO) { // HttpClientConfig<CIOEngineConfig>
* engine { // CIOEngineConfig.() -> Unit
* proxy = ProxyBuilder.http("proxy.example.com", 8080)
* }
* ```
*
* You can learn more from [Engines](https://ktor.io/docs/http-client-engines.html).
*/
Expand All @@ -40,17 +83,34 @@ public class HttpClientConfig<T : HttpClientEngineConfig> {
/**
* Specifies whether the client redirects to URLs provided in the `Location` header.
* You can disable redirections by setting this property to `false`.
*
* For the advanced redirection configuration, use [HttpRedirect] plugin.
*/
public var followRedirects: Boolean = true

/**
* Uses [defaultTransformers] to automatically handle simple [ContentType].
* Enable body transformations for many common types like [String], [ByteArray], [ByteReadChannel], etc.
* These transformations are applied to the request and response bodies.
*
* The transformers will be used when the response body is received with a type:
* ```kotlin
* val client = HttpClient()
* val bytes = client.get("https://ktor.io")
* .body<ByteArray>()
* ```
*
* The flag is enabled by default.
* You might want to disable it if you want to write your own transformers or handle body manually.
*
* Check [defaultTransformers] documentation for more details.
*/
public var useDefaultTransformers: Boolean = true

/**
* Terminates [HttpClient.receivePipeline] if the status code is not successful (>=300).
* Learn more from [Response validation](https://ktor.io/docs/response-validation.html).
*
* Please check [HttpCallValidator] documentation to learn more details.
*/
public var expectSuccess: Boolean = false

Expand All @@ -66,6 +126,18 @@ public class HttpClientConfig<T : HttpClientEngineConfig> {

/**
* Installs the specified [plugin] and optionally configures it using the [configure] block.
*
* ```kotlin
* val client = HttpClient {
* install(ContentNegotiation) {
* // configuration block
* json()
* }
* }
* ```
*
* If the plugin is already installed, the Configuration block will be applied to the existing configuration class.
*
* Learn more from [Plugins](https://ktor.io/docs/http-client-plugins.html).
*/
public fun <TBuilder : Any, TPlugin : Any> install(
Expand Down Expand Up @@ -95,6 +167,8 @@ public class HttpClientConfig<T : HttpClientEngineConfig> {
/**
* Installs an interceptor defined by [block].
* The [key] parameter is used as a unique name, that also prevents installing duplicated interceptors.
*
* If the [key] is already used, the new interceptor will replace the old one.
*/
public fun install(key: String, block: HttpClient.() -> Unit) {
customInterceptors[key] = block
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,30 @@ import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.util.date.*
import io.ktor.utils.io.*
import kotlinx.io.*
import kotlin.coroutines.*
import kotlinx.io.readByteArray
import kotlin.coroutines.CoroutineContext


/**
* Saves the entire content of this [HttpClientCall] to memory and returns a new [HttpClientCall]
* with the content cached in memory.
* This can be particularly useful for caching, debugging,
* or processing responses without relying on the original network stream.
*
* By caching the content, this function simplifies the management of the [HttpResponse] lifecycle.
* It releases the network connection and other resources associated with the original [HttpResponse],
* ensuring they are no longer required to be explicitly closed.
*
* This behavior is automatically applied to non-streaming [HttpResponse] instances.
* For streaming responses, this function allows you to convert them into a memory-based representation.
*
* @return A new [HttpClientCall] instance with all its content stored in memory.
*/
@OptIn(InternalAPI::class)
public suspend fun HttpClientCall.save(): HttpClientCall {
val responseBody = response.rawContent.readRemaining().readByteArray()
return SavedHttpCall(client, request, response, responseBody)
}

internal class SavedHttpCall(
client: HttpClient,
Expand Down Expand Up @@ -60,14 +82,3 @@ internal class SavedHttpResponse(
@OptIn(InternalAPI::class)
override val rawContent: ByteReadChannel get() = ByteReadChannel(body)
}

/**
* Fetch data for [HttpClientCall] and close the origin.
*/

@OptIn(InternalAPI::class)
public suspend fun HttpClientCall.save(): HttpClientCall {
val responseBody = response.rawContent.readRemaining().readByteArray()

return SavedHttpCall(client, request, response, responseBody)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,19 @@ package io.ktor.client.call
import io.ktor.http.*
import io.ktor.http.content.*

/**
* Exception thrown when the engine does not support the content type of the HTTP request body.
* For instance, some engines do not support upgrade requests.
*/
public class UnsupportedContentTypeException(content: OutgoingContent) :
IllegalStateException("Failed to write body: ${content::class}")

@Suppress("KDocMissingDocumentation", "UNUSED")
@Deprecated(
"This exception is deprecated, use UnsupportedContentTypeException instead.",
replaceWith = ReplaceWith("UnsupportedContentTypeException(content)"),
level = DeprecationLevel.WARNING
)
public class UnsupportedUpgradeProtocolException(
url: Url
) : IllegalArgumentException("Unsupported upgrade protocol exception: $url")
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,28 @@
package io.ktor.client.content

import io.ktor.client.call.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.utils.*
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.util.*
import io.ktor.utils.io.*
import kotlinx.coroutines.*
import kotlin.coroutines.*
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlin.coroutines.CoroutineContext

/**
* Callback that can be registered to listen for upload/download progress.
* @param bytesSentTotal number of transmitted bytes.
* @param contentLength body size. Can be null if the size is unknown.
*
* This class is used for callbacks in [HttpRequestBuilder.onDownload] and [HttpRequestBuilder.onUpload].
*/
public fun interface ProgressListener {
/**
* Invokes every time some data is flushed through the [ByteReadChannel].
*
* @param bytesSentTotal number of transmitted bytes.
* @param contentLength body size. Can be null if the size is unknown.
*/
public suspend fun onProgress(bytesSentTotal: Long, contentLength: Long?)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,85 @@ import io.ktor.util.*
import io.ktor.utils.io.*
import io.ktor.utils.io.core.*
import kotlinx.coroutines.*
import kotlin.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext

internal val CALL_COROUTINE = CoroutineName("call-context")
internal val CLIENT_CONFIG = AttributeKey<HttpClientConfig<*>>("client-config")

/**
* Serves as the base interface for an [HttpClient]'s engine.
*
* An `HttpClientEngine` represents the underlying network implementation that
* performs HTTP requests and handles responses.
* Developers can implement this interface to create custom engines for use with [HttpClient].
*
* This interface provides a set of properties and methods that define the
* contract for configuring, executing, and managing HTTP requests within the engine.
*
* See also [HttpClientEngineBase] for a base implementation that handles common engine functionality.
*/
public interface HttpClientEngine : CoroutineScope, Closeable {
/**
* Specifies [CoroutineDispatcher] for I/O operations.
* Specifies the [CoroutineDispatcher] for I/O operations in the engine.
*
* This dispatcher is used for all network-related operations, such as
* sending requests and receiving responses.
* By default, it should be optimized for I/O tasks.
*
* Example:
* ```kotlin
* override val dispatcher: CoroutineDispatcher = Dispatchers.IO
* ```
*/
public val dispatcher: CoroutineDispatcher

/**
* Provides access to an engine's configuration.
* Provides access to the engine's configuration via [HttpClientEngineConfig].
*
* The [config] object stores user-defined parameters or settings that control
* how the engine operates. When creating a custom engine, this property
* should return the specific configuration implementation.
*
* Example:
* ```kotlin
* override val config: HttpClientEngineConfig = CustomEngineConfig()
* ```
*/
public val config: HttpClientEngineConfig

/**
* Set of supported engine extensions.
* Specifies the set of capabilities supported by this HTTP client engine.
*
* Capabilities provide a mechanism for plugins and other components to
* determine whether the engine supports specific features such as timeouts,
* WebSocket communication, HTTP/2, HTTP/3, or other advanced networking
* capabilities. This allows seamless integration of features based on the
* engine's functionality.
*
* Each capability is represented as an instance of [HttpClientEngineCapability],
* which can carry additional metadata or configurations for the capability.
*
* Example:
* ```kotlin
* override val supportedCapabilities: Set<HttpClientEngineCapability<*>> = setOf(
* WebSocketCapability,
* Http2Capability,
* TimeoutCapability
* )
* ```
*
* **Usage in Plugins**:
* Plugins can check if the engine supports a specific capability before
* applying behavior:
* ```kotlin
* if (engine.supportedCapabilities.contains(WebSocketCapability)) {
* // Configure WebSocket-specific settings
* }
* ```
*
* When implementing a custom engine, ensure this property accurately reflects
* the engine's abilities to avoid unexpected plugin behavior or runtime errors.
*/
public val supportedCapabilities: Set<HttpClientEngineCapability<*>>
get() = emptySet()
Expand All @@ -42,13 +100,26 @@ public interface HttpClientEngine : CoroutineScope, Closeable {
get() = !(coroutineContext[Job]?.isActive ?: false)

/**
* Creates a new [HttpClientCall] specific for this engine, using a request [data].
* Executes an HTTP request and produces an HTTP response.
*
* This function is responsible for converting [HttpRequestData], which
* contains all the request details, into [HttpResponseData], which encapsulates
* the response information such as headers, status code, and body.
*
* @param data The [HttpRequestData] representing the request to be executed.
* @return A [HttpResponseData] object containing the server's response.
*/
@InternalAPI
public suspend fun execute(data: HttpRequestData): HttpResponseData

/**
* Installs the engine to [HttpClient].
* Installs the engine into an [HttpClient].
*
* This method is called when the engine is being set up within an `HttpClient`.
* Use it to register interceptors, validate configuration, or prepare the engine
* for use with the client.
*
* @param client The [HttpClient] instance to which the engine is being installed.
*/
@InternalAPI
public fun install(client: HttpClient) {
Expand Down Expand Up @@ -107,16 +178,6 @@ public interface HttpClientEngine : CoroutineScope, Closeable {
}
}

/**
* A factory of [HttpClientEngine] with a specific [T] of [HttpClientEngineConfig].
*/
public interface HttpClientEngineFactory<out T : HttpClientEngineConfig> {
/**
* Creates a new [HttpClientEngine] optionally specifying a [block] configuring [T].
*/
public fun create(block: T.() -> Unit = {}): HttpClientEngine
}

/**
* Creates a new [HttpClientEngineFactory] based on this one
* with further configurations from the [nested] block.
Expand Down
Loading