Custom plugins
Starting with v2.2.0, Ktor provides a new API for creating custom client plugins. In general, this API doesn't require an understanding of internal Ktor concepts, such as pipelines, phases, and so on. Instead, you have access to different stages of handling requests and responses using a set of handlers, such as onRequest
, onResponse
, and so on.
Create and install your first plugin
In this section, we'll demonstrate how to create and install your first plugin that adds a custom header to each request:
To create a plugin, call the createClientPlugin function and pass a plugin name as an argument:
package com.example.plugins import io.ktor.client.plugins.api.* val CustomHeaderPlugin = createClientPlugin("CustomHeaderPlugin") { // Configure the plugin ... }This function returns the
ClientPlugin
instance that will be used to install the plugin.To append a custom header to each request, you can use the
onRequest
handler, which provides access to request parameters:package com.example.plugins import io.ktor.client.plugins.api.* val CustomHeaderPlugin = createClientPlugin("CustomHeaderPlugin") { onRequest { request, _ -> request.headers.append("X-Custom-Header", "Default value") } }To install the plugin, pass the created
ClientPlugin
instance to theinstall
function inside the client's configuration block:import com.example.plugins.* val client = HttpClient(CIO) { install(CustomHeaderPlugin) }
You can find the full example here: CustomHeader.kt. In the following sections, we'll look at how to provide a plugin configuration and handle requests and responses.
Provide plugin configuration
The previous section demonstrates how to create a plugin that appends a predefined custom header to each response. Let's make this plugin more useful and provide a configuration for passing any custom header name and value:
First, you need to define a configuration class:
class CustomHeaderPluginConfig { var headerName: String = "X-Custom-Header" var headerValue: String = "Default value" }To use this configuration in a plugin, pass a configuration class reference to
createApplicationPlugin
:import io.ktor.client.plugins.api.* val CustomHeaderConfigurablePlugin = createClientPlugin("CustomHeaderConfigurablePlugin", ::CustomHeaderPluginConfig) { val headerName = pluginConfig.headerName val headerValue = pluginConfig.headerValue onRequest { request, _ -> request.headers.append(headerName, headerValue) } }Given that plugin configuration fields are mutable, saving them in local variables is recommended.
Finally, you can install and configure the plugin as follows:
val client = HttpClient(CIO) { install(CustomHeaderConfigurablePlugin) { headerName = "X-Custom-Header" headerValue = "Hello, world!" } }
Handle requests and responses
Custom plugins provide access to different stages of handling requests and responses using a set of dedicated handlers, for example:
onRequest
andonResponse
allow you to handle requests and responses, respectively.transformRequestBody
andtransformResponseBody
can be used to apply necessary transformations to request and response bodies.
There is also the on(...)
handler that allows you to invoke specific hooks that might be useful to handle other stages of a call. The tables below list all handlers in the order they are executed:
Handler | Description |
---|---|
| This handler is executed for each HTTP request and allows you to modify it. Example: Custom header |
| Allows you to transform a request body. In this handler, you need to serialize the body into OutgoingContent (for example, Example: Data transformation |
| This handler is executed for each incoming HTTP response and allows you to inspect it in various ways: log a response, save cookies, and so on. Examples: Logging headers, Response time |
| Allows you to transform a response body. This handler is invoked for each Example: Data transformation |
| Allows you to clean resources allocated by this plugin. This handler is called when the client is closed. |
Handler | Description |
---|---|
| The |
| This handler is executed for each HTTP request and allows you to modify it. Example: Custom header |
| Allows you to transform a request body. In this handler, you need to serialize the body into OutgoingContent (for example, Example: Data transformation |
| The Example: Authentication |
| The
--> onRequest
--> on(Send)
--> on(SendingRequest)
<-- onResponse
--> on(SendingRequest)
<-- onResponse
Examples: Logging headers, Response time |
| This handler is executed for each incoming HTTP response and allows you to inspect it in various ways: log a response, save cookies, and so on. Examples: Logging headers, Response time |
| Allows you to transform a response body. This handler is invoked for each Example: Data transformation |
| Allows you to clean resources allocated by this plugin. This handler is called when the client is closed. |
Share call state
Custom plugins allow you to share any value related to a call so that you can access this value inside any handler processing this call. This value is stored as an attribute with a unique key in the call.attributes
collection. The example below demonstrates how to use attributes to calculate the time between sending a request and receiving a response:
You can find the full example here: ResponseTime.kt.
Access client configuration
You can access your client configuration using the client
property, which returns the HttpClient instance. The example below shows how to get the proxy address used by the client:
Examples
The code samples below demonstrate several examples of custom plugins. You can find the resulting project here: client-custom-plugin.
Custom header
Shows how to create a plugin that adds a custom header to each request:
Logging headers
Demonstrates how to create a plugin that logs request and response headers:
Response time
Shows how to create a plugin that measures the time between sending a request and receiving a response:
Data transformation
Shows how to transform request and response bodies using the transformRequestBody
and transformResponseBody
hooks:
You can find the full example here: client-custom-plugin-data-transformation.
Authentication
A sample Ktor project showing how to use the on(Send)
hook to add a bearer token to the Authorization
header if an unauthorized response is received from the server:
You can find the full example here: client-custom-plugin-auth.