Ktor
Building Asynchronous Servers and Clients in Kotlin
Ktor 2.2.1 (and 2.2.0) released
Important: Yesterday we released 2.2.0 to Maven Central, but immediately after we noticed a critical issue of AtomicFu that needed addressing, and as a consequence shortly after we pushed 2.2.1. As such, if you are using 2.2.0, we strongly advise moving to 2.2.1.
We’ve released Ktor 2.2.0/2.2.1, which introduces many new features and fixes. Moreover, with the latest 2022.3 IntelliJ IDEA version, the Ktor plugin lets you generate OpenAPI documentation for server-side Ktor applications using the dedicated intention action.
For guidance about migrating your Ktor project to the new 2.2.0 version, see these instructions.
If you’re new to Ktor, create your first application today!
Generating OpenAPI
To generate an OpenAPI specification for a Ktor application, place the caret at the embeddedServer
function call, press Alt+Enter, and choose Generate OpenAPI documentation for current module. IntelliJ IDEA will generate the paths for all application endpoints defined using either string path patterns or type-safe routes.
You can learn more about this feature from the documentation: Ktor | OpenAPI.
Note: The following 2 known issues in the OpenAPI generator will be fixed in the next IntelliJ IDEA patch release: KTOR-5194 and KTOR-5195.
Server
Serving OpenAPI
Ktor 2.2.0 allows you to generate and serve Swagger UI for your project based on the existing OpenAPI specification. For example, if your project contains the OpenAPI specification at resources/openapi/documentation.yaml, you can serve Swagger UI for this specification by calling swaggerUI
with the following parameters:
routing { swaggerUI(path = "swagger", swaggerFile = "openapi/documentation.yaml") }
Then you can run the application, open the http://0.0.0.0:8080/swagger page, and interact with the API resources using the generated Swagger UI.
To learn more about how to set up this plugin, see the Swagger UI topic. Similarly, you can render a web page with OpenAPI documentation using the OpenAPI plugin.
Rate limiting
In this release, we’ve added a new RateLimit plugin, which allows you to set rate limiting for incoming requests. For example, you can enable rate limiting globally as shown below:
install(RateLimit) { global { rateLimiter(limit = 5, refillPeriod = 60.seconds) } }
You can also register a rate-limiting provider with the specified name:
install(RateLimit) { register(RateLimitName("protected")) { rateLimiter(limit = 30, refillPeriod = 60.seconds) } }
And you can apply the provider to a specific route:
routing { rateLimit(RateLimitName("protected")) { get("/protected-api") { val requestsLeft = call.response.headers["X-RateLimit-Remaining"] call.respondText("Welcome to protected API! $requestsLeft requests left.") } } }
You can learn more from the Rate limiting help topic.
Authentication
Bearer authentication
As you might know, the Ktor server already supports the Bearer authentication scheme as part of OAuth or JWT. With this release, you can also provide custom logic for authorizing bearer tokens using the dedicated bearer authentication provider, which allows you to check the token sent by the client:
install(Authentication) { bearer("auth-bearer") { realm = "Access to the '/' path" authenticate { tokenCredential -> if (tokenCredential.token == "abc123") { UserIdPrincipal("jetbrains") } else { null } } } }
We’d like to thank our community contributor oharaandrew314 for their contribution in this area.
For more details, see the Bearer authentication help topic.
Nested authentication improvements
The Authentication plugin allows you to define the authorization scope for resources by using the authenticate
function. In older versions of Ktor, if you had several nested calls of the authenticate method for different providers, the client had to provide authentication data for any provider. With v2.2.0, you can define a strategy for resolving nested authentication providers. This might be useful if the client must provide authentication data for all registered providers. In the code snippet below, only a user that passes session authentication can try to access the /admin
route using basic authentication:
routing { authenticate("auth-session", strategy = AuthenticationStrategy.Required) { get("/hello") { // ... } authenticate("auth-basic", strategy = AuthenticationStrategy.Required) { get("/admin") { // ... } } } }
We are planning to make the AuthenticationStrategy.Required
strategy the default in the future to make the behavior of nested authentication providers clearer.
Digest authentication
We’ve added the ability to map the credentials to a custom principal for the Digest provider. In the example below, validate
returns a CustomPrincipal
instance in case of successful authentication.
fun Application.main() { install(Authentication) { digest("auth-digest") { // ... validate { credentials -> if (credentials.userName.isNotEmpty()) { CustomPrincipal(credentials.userName, credentials.realm) } else { null } } } } }
Serialization
With this release, we’ve added the ProtoBuf serializer for serializing/deserializing data objects. To start using ProtoBuf, add the ktor-serialization-kotlinx-protobuf
dependency to the build script and install the serializer as follows:
import io.ktor.server.plugins.contentnegotiation.* import io.ktor.serialization.kotlinx.protobuf.* install(ContentNegotiation) { protobuf() }
You can also use the ProtoBuf serializer with WebSockets:
import kotlinx.serialization.protobuf.* install(WebSockets) { contentConverter = KotlinxWebsocketSerializationConverter(ProtoBuf) }
Custom plugins configuration
Ktor 2.2.0 lets you configure custom plugins in a configuration file. For example, the application.yaml configuration file contains a configuration for a custom plugin in the http.custom_header
group:
http: custom_header: header_name: X-Another-Custom-Header header_value: Some value
You can access the configuration values as follows:
class CustomHeaderConfiguration(config: ApplicationConfig) { var headerName: String = config.tryGetString("header_name") ?: "Custom-Header-Name" var headerValue: String = config.tryGetString("header_value") ?: "Default value" }
Finally, set the configurationPath
parameter to http.custom_header when creating a plugin:
val CustomHeaderPluginConfigurable = createApplicationPlugin( name = "CustomHeaderPluginConfigurable", configurationPath = "http.custom_header", createConfiguration = ::CustomHeaderConfiguration ) { // … }
For the complete code example, visit the Configuration in a file help section.
Native server
We’ve made the following improvements to the Native server in this release:
- The Native server now supports the Sessions plugin.
- You can change the logging level on the Native server by using the KTOR_LOG_LEVEL environment variable. Enabling route tracing in this way can help you understand why some routes are not being executed.
TLS and certificates
Generating certificates
With this release, the buildKeyStore method provides more flexibility for generating self-signed certificates. For instance, you can now specify which domains are allowed in a test certificate:
val keyStore = buildKeyStore { certificate("sampleAlias") { password = "foobar" domains = listOf("127.0.0.1", "0.0.0.0", "localhost") } }
We’d like to thank joffrey-bion for contributing to this improvement.
Configuring TLS versions
The enabledProtocols
property available inside sslConnector now allows you to block the use of TLS 1.0 and 1.1.
val environment = applicationEngineEnvironment { // ... sslConnector( // ... enabledProtocols = listOf("TLSv1.3", "TLSv1.2") } }
We’d like to thank joreilly for contributing to this improvement.
Obtaining connection details
This release also brings improvements to resolving connection details such as hostname and port. For example, before v2.2.0, you could get the hostname or port to which the request was made using the RequestConnectionPoint.host
and RequestConnectionPoint.port
properties:
get("/hello") { val originHost = call.request.origin.host val originPort = call.request.origin.port }
With this release, these properties have been deprecated and replaced with serverHost
and serverPort
. We’ve also added the localHost
/localPort
properties that return the hostname or port on which the request was received. Refer to the Original request information section to learn more.
Other improvements
This release also adds other improvements for TLS and certificates, including test server lifecycle management, logging debug information for routing and other plugins, and other bug fixes and features.
Client
New custom plugins API
In addition to simplifying the extensibility API for the Ktor server, we’ve implemented a new API for creating custom client plugins. Instead of intercepting pipelines, you can now access different stages of handling requests and responses by using a set of handlers, such as onRequest
, onResponse
, and so on.
The code snippet below shows how to create a plugin that adds a predefined custom header to each request:
import io.ktor.client.plugins.api.* val CustomHeaderPlugin = createClientPlugin("CustomHeaderPlugin") { onRequest { request, _ -> request.headers.append("X-Custom-Header", "Default value") } }
To learn more, see the Custom plugins topic in our documentation.
Persistent HttpCache
We’ve implemented the ability to implement persistent caching for the Ktor client. To do this, you can use the CacheStorage
interface or its FileStorage
implementation on JVM. The code snippet below shows how to do the latter:
val client = HttpClient(CIO) { install(HttpCache) { val cacheFile = Files.createDirectories(Paths.get("build/cache")).toFile() publicStorage(FileStorage(cacheFile)) } }
Note that the publicStorage
and privateStorage
properties have been deprecated in favor of the respective methods.
Serialization
The ProtoBuf serializer is also available for the Ktor client. The following snippet shows how to enable ProtoBuf for the ContentNegotiation
plugin:
import io.ktor.client.plugins.contentnegotiation.* import io.ktor.serialization.kotlinx.protobuf.* val client = HttpClient(CIO) { install(ContentNegotiation) { protobuf() } }
The serializer is also available for WebSockets.
Native windows HTTP client
This release adds the WinHttp
native engine that targets Windows-based operating systems and supports WebSockets. To learn how to configure it, see the WinHttp doc section.
Sample updates
As you might know, the ktor-samples repository contains a collection of ready-to-use samples for Ktor. We’ve migrated these samples to the latest version of Ktor API, in particular, replaced the Location
plugin usages to Resources and updated tests to a new testing API implemented in v2.0.0. You can browse through these samples to understand different ways to accomplish things in Ktor.
Docs infrastructure
The final thing we want to mention is the latest changes we’ve made to our documentation infrastructure. Ktor documentation lives in a separate GitHub repository: https://github.com/ktorio/ktor-documentation.
To build our documentation, we now use Writerside, a new tool from JetBrains for creating and publishing docs. It uses the docs-as-code approach and provides automatic quality checks, natural language assistance, live preview, and other useful features. If you want to try this tool, feel free to enroll in the Writerside Early Access Program at https://jb.gg/writerside.
Happy developing!
The Ktor team