Skip to content

Latest commit

 

History

History
818 lines (598 loc) · 34.9 KB

migrating-2.md

File metadata and controls

818 lines (598 loc) · 34.9 KB

This guide provides instructions on how to migrate your Ktor application from the 1.6.x version to 2.0.0.

Ktor Server {id="server"}

Server code is moved to the 'io.ktor.server.*' package {id="server-package"}

To unify and better distinguish the server and client APIs, server code is moved to the io.ktor.server.* package (KTOR-2865). This means that you need to update dependencies for and imports in your application, as shown below.

Dependencies {id="server-package-dependencies"}

Subsystem 1.6.x 2.0.0
Locations io.ktor:ktor-locations io.ktor:ktor-server-locations
Webjars io.ktor:ktor-webjars io.ktor:ktor-server-webjars
AutoHeadResponse io.ktor:ktor-server-core io.ktor:ktor-server-auto-head-response
StatusPages io.ktor:ktor-server-core io.ktor:ktor-server-status-pages
CallId io.ktor:ktor-server-core io.ktor:ktor-server-call-id
DoubleReceive io.ktor:ktor-server-core io.ktor:ktor-server-double-receive
HTML DSL io.ktor:ktor-html-builder io.ktor:ktor-server-html-builder
FreeMarker io.ktor:ktor-freemarker io.ktor:ktor-server-freemarker
Velocity io.ktor:ktor-velocity io.ktor:ktor-server-velocity
Mustache io.ktor:ktor-mustache io.ktor:ktor-server-mustache
Thymeleaf io.ktor:ktor-thymeleaf io.ktor:ktor-server-thymeleaf
Pebble io.ktor:ktor-pebble io.ktor:ktor-server-pebble
kotlinx.serialization io.ktor:ktor-serialization io.ktor:ktor-server-content-negotiation, io.ktor:ktor-serialization-kotlinx-json
Gson io.ktor:ktor-gson io.ktor:ktor-server-content-negotiation, io.ktor:ktor-serialization-gson
Jackson io.ktor:ktor-jackson io.ktor:ktor-server-content-negotiation, io.ktor:ktor-serialization-jackson
Authentication io.ktor:ktor-auth io.ktor:ktor-server-auth
JWT authentication io.ktor:ktor-auth-jwt io.ktor:ktor-server-auth-jwt
LDAP authentication io.ktor:ktor-auth-ldap io.ktor:ktor-server-auth-ldap
DataConversion io.ktor:ktor-server-core io.ktor:ktor-server-data-conversion
DefaultHeaders io.ktor:ktor-server-core io.ktor:ktor-server-default-headers
Compression io.ktor:ktor-server-core io.ktor:ktor-server-compression
CachingHeaders io.ktor:ktor-server-core io.ktor:ktor-server-caching-headers
ConditionalHeaders io.ktor:ktor-server-core io.ktor:ktor-server-conditional-headers
CORS io.ktor:ktor-server-core io.ktor:ktor-server-cors
Forwarded headers io.ktor:ktor-server-core io.ktor:ktor-server-forwarded-header
HSTS io.ktor:ktor-server-core io.ktor:ktor-server-hsts
HttpsRedirect io.ktor:ktor-server-core io.ktor:ktor-server-http-redirect
PartialContent io.ktor:ktor-server-core io.ktor:ktor-server-partial-content
WebSockets io.ktor:ktor-websockets io.ktor:ktor-server-websockets
CallLogging io.ktor:ktor-server-core io.ktor:ktor-server-call-logging
Micrometer metric io.ktor:ktor-metrics-micrometer io.ktor:ktor-server-metrics-micrometer
Dropwizard metrics io.ktor:ktor-metrics io.ktor:ktor-server-metrics

To add all plugins at once, you can use the io.ktor:ktor-server artifact.

Imports {id="server-package-imports"}

Subsystem 1.6.x 2.0.0
Application import io.ktor.application.* import io.ktor.server.application.*
Configuration import io.ktor.config.* import io.ktor.server.config.*
Routing import io.ktor.routing.* import io.ktor.server.routing.*
AutoHeadResponse import io.ktor.features.* import io.ktor.server.plugins.autohead.*
StatusPages import io.ktor.features.* import io.ktor.server.plugins.statuspages.*
CallId import io.ktor.features.* import io.ktor.server.plugins.callid.*
DoubleReceive import io.ktor.features.* import io.ktor.server.plugins.doublereceive.*
Requests import io.ktor.request.* import io.ktor.server.request.*
Responses import io.ktor.response.* import io.ktor.server.response.*
Plugins import io.ktor.features.* import io.ktor.server.plugins.*
Locations import io.ktor.locations.* import io.ktor.server.locations.*
Static content import io.ktor.http.content.* import io.ktor.server.http.content.*
HTML DSL import io.ktor.html.* import io.ktor.server.html.*
FreeMarker import io.ktor.freemarker.* import io.ktor.server.freemarker.*
Velocity import io.ktor.velocity.* import io.ktor.server.velocity.*
Mustache import io.ktor.mustache.* import io.ktor.server.mustache.*
Thymeleaf import io.ktor.thymeleaf.* import io.ktor.server.thymeleaf.*
Pebble import io.ktor.pebble.* import io.ktor.server.pebble.*
ContentNegotiation import io.ktor.features.* import io.ktor.server.plugins.contentnegotiation.*
kotlinx.serialization import io.ktor.serialization.* import io.ktor.serialization.kotlinx.json.*
Gson import io.ktor.gson.* import io.ktor.serialization.gson.*
Jackson import io.ktor.jackson.* import io.ktor.serialization.jackson.*
Authentication import io.ktor.auth.* import io.ktor.server.auth.*
JWT authentication import io.ktor.auth.jwt.* import io.ktor.server.auth.jwt.*
LDAP authentication import io.ktor.auth.ldap.* import io.ktor.server.auth.ldap.*
Sessions import io.ktor.sessions.* import io.ktor.server.sessions.*
DefaultHeaders import io.ktor.features.* import io.ktor.server.plugins.defaultheaders.*
Compression import io.ktor.features.* import io.ktor.server.plugins.compression.*
CachingHeaders import io.ktor.features.* import io.ktor.server.plugins.cachingheaders.*
ConditionalHeaders import io.ktor.features.* import io.ktor.server.plugins.conditionalheaders.*
CORS import io.ktor.features.* import io.ktor.server.plugins.cors.*
Forwarded headers import io.ktor.features.* import io.ktor.server.plugins.forwardedheaders.*
HSTS import io.ktor.features.* import io.ktor.server.plugins.hsts.*
HttpsRedirect import io.ktor.features.* import io.ktor.server.plugins.httpsredirect.*
PartialContent import io.ktor.features.* import io.ktor.server.plugins.partialcontent.*
WebSockets import io.ktor.websocket.* import io.ktor.server.websocket.*
CallLogging import io.ktor.features.* import io.ktor.server.plugins.callloging.*
Micrometer metric import io.ktor.metrics.micrometer.* import io.ktor.server.metrics.micrometer.*
Dropwizard metrics import io.ktor.metrics.dropwizard.* import io.ktor.server.metrics.dropwizard.*

WebSockets code is moved to the 'websockets' package {id="server-ws-package"}

WebSockets code is moved from http-cio to the websockets package. This requires updating imports as follows:

1.6.x 2.0.0
import io.ktor.http.cio.websocket.* import io.ktor.websocket.*

Note that this change also affects the client.

Feature is renamed to Plugin {id="feature-plugin"}

In Ktor 2.0.0, Feature is renamed to Plugin to better describe functionality that intercepts the request/response pipeline (KTOR-2326). This affects the entire Ktor API and requires updating your application as described below.

Imports {id="feature-plugin-imports"}

Installing any plugin requires updating imports and also depends on moving server code to the io.ktor.server.* package:

1.6.x 2.0.0
import io.ktor.features.* import io.ktor.server.plugins.*

Custom plugins {id="feature-plugin-custom"}

Renaming Feature to Plugin introduces the following changes for API related to custom plugins:

  • The ApplicationFeature interface is renamed to BaseApplicationPlugin.
  • The Features pipeline phase is renamed to Plugins.

Note that starting with v2.0.0, Ktor provides the new API for creating custom plugins.

Content negotiation and serialization {id="serialization"}

Content negotiation and serialization server API was refactored to reuse serialization libraries between the server and client. The main changes are:

  • ContentNegotiation is moved from ktor-server-core to a separate ktor-server-content-negotiation artifact.
  • Serialization libraries are moved from ktor-* to the ktor-serialization-* artifacts also used by the client.

You need to update dependencies for and imports in your application, as shown below.

Dependencies {id="dependencies-serialization"}

Subsystem 1.6.x 2.0.0
ContentNegotiation io.ktor:ktor-server-core io.ktor:ktor-server-content-negotiation
kotlinx.serialization io.ktor:ktor-serialization io.ktor:ktor-serialization-kotlinx-json
Gson io.ktor:ktor-gson io.ktor:ktor-serialization-gson
Jackson io.ktor:ktor-jackson io.ktor:ktor-serialization-jackson

Imports {id="imports-serialization"}

Subsystem 1.6.x 2.0.0
kotlinx.serialization import io.ktor.serialization.* import io.ktor.serialization.kotlinx.json.*
Gson import io.ktor.gson.* import io.ktor.serialization.gson.*
Jackson import io.ktor.jackson.* import io.ktor.serialization.jackson.*

Custom converters {id="serialization-custom-converter"}

Signatures of functions exposed by the ContentConverter interface are changed in the following way:

interface ContentConverter {
    suspend fun convertForSend(context: PipelineContext<Any, ApplicationCall>, contentType: ContentType, value: Any): Any?
    suspend fun convertForReceive(context: PipelineContext<ApplicationReceiveRequest, ApplicationCall>): Any?
}
interface ContentConverter {
    suspend fun serialize(contentType: ContentType, charset: Charset, typeInfo: TypeInfo, value: Any): OutgoingContent?
    suspend fun deserialize(charset: Charset, typeInfo: TypeInfo, content: ByteReadChannel): Any?
}

Testing API {id="testing-api"}

With v2.0.0, the Ktor server uses a new API for testing, which solves various issues described in KTOR-971. The main changes are:

  • The withTestApplication/withApplication functions are replaced with a new testApplication function.
  • Inside the testApplication function, you need to use the existing Ktor client instance to make requests to your server and verify the results.
  • To test specific functionalities (for example, cookies or WebSockets), you need to create a new client instance and install a corresponding plugin.

Let's take a look at several examples of migrating 1.6.x tests to 2.0.0:

Basic server test {id="basic-test"}

In the test below, the handleRequest function is replaced with the client.get request:

{src="snippets/engine-main/src/test/kotlin/EngineMainTest.kt" lines="18-26"}

{src="snippets/engine-main/src/test/kotlin/EngineMainTest.kt" lines="11-16"}

x-www-form-urlencoded {id="x-www-form-urlencoded"}

In the test below, the handleRequest function is replaced with the client.post request:

{src="snippets/post-form-parameters/src/test/kotlin/ApplicationTest.kt" lines="20-28"}

{src="snippets/post-form-parameters/src/test/kotlin/ApplicationTest.kt" lines="11-18"}

multipart/form-data {id="multipart-form-data"}

To build multipart/form-data in v2.0.0, you need to pass MultiPartFormDataContent to the client's setBody function:

{src="snippets/upload-file/src/test/kotlin/UploadFileTest.kt" lines="36-61"}

{src="snippets/upload-file/src/test/kotlin/UploadFileTest.kt" lines="15-34"}

JSON data {id="json-data"}

In v.1.6.x, you can serialize JSON data using the Json.encodeToString function provided by the kotlinx.serialization library. With v2.0.0, you need to create a new client instance and install the ContentNegotiation plugin that allows serializing/deserializing the content in a specific format:

{src="snippets/json-kotlinx/src/test/kotlin/ApplicationTest.kt" lines="46-55"}

{src="snippets/json-kotlinx/src/test/kotlin/ApplicationTest.kt" lines="31-44"}

Preserve cookies during testing {id="preserving-cookies"}

In v1.6.x, cookiesSession is used to preserve cookies between requests when testing. With v2.0.0, you need to create a new client instance and install the HttpCookies plugin:

{src="snippets/session-cookie-client/src/test/kotlin/ApplicationTest.kt" lines="29-46"}

{src="snippets/session-cookie-client/src/test/kotlin/ApplicationTest.kt" lines="12-27"}

WebSockets {id="testing-ws"}

In the old API, handleWebSocketConversation is used to test WebSocket conversations. With v2.0.0, you can test WebSocket conversations by using the WebSockets plugin provided by the client:

{src="snippets/server-websockets/src/test/kotlin/com/example/ModuleTest.kt" lines="28-40"}

{src="snippets/server-websockets/src/test/kotlin/com/example/ModuleTest.kt" lines="10-26"}

DoubleReceive {id="double-receive"}

With v2.0.0, the DoubleReceive plugin configuration introduces the cacheRawRequest property, which is opposite to receiveEntireContent:

  • In v1.6.x, the receiveEntireContent property is set to false by default.
  • In v2.0.0, cacheRawRequest is set to true by default. The receiveEntireContent property is removed.

Forwarded headers {id="forwarded-headers"}

In v2.0.0, the ForwardedHeaderSupport and XForwardedHeaderSupport plugins are renamed to ForwardedHeaders and XForwardedHeaders, respectively.

Caching headers {id="caching-headers"}

The options function used to define caching options now accepts the ApplicationCall as a lambda argument in addition to OutgoingContent:

install(CachingHeaders) {
    options { outgoingContent ->
        // ...
    }
}
install(CachingHeaders) {
    options { call, outgoingContent ->
        // ...
    }
}

Conditional headers {id="conditional-headers"}

The version function used to define a list of resource versions now accepts the ApplicationCall as a lambda argument in addition to OutgoingContent:

install(ConditionalHeaders) {
    version { outgoingContent ->
        // ... 
    }
}
install(ConditionalHeaders) {
    version { call, outgoingContent ->
        // ... 
    }
}

CORS {id="cors"}

Several functions used in CORS configuration are renamed:

  • host -> allowHost
  • header -> allowHeader
  • method -> allowMethod
install(CORS) {
    host("0.0.0.0:5000")
    header(HttpHeaders.ContentType)
    method(HttpMethod.Options)
}
install(CORS) {
    allowHost("0.0.0.0:5000")
    allowHeader(HttpHeaders.ContentType)
    allowMethod(HttpMethod.Options)
}

MicrometerMetrics {id="micrometer-metrics"}

In v1.6.x, the baseName property is used to specify the base name (prefix) of Ktor metrics used for monitoring HTTP requests. By default, it equals to ktor.http.server. With v2.0.0, baseName is replaced with metricName whose default value is ktor.http.server.requests.

Ktor Client {id="client"}

Requests and responses {id="request-response"}

In v2.0.0, API used to make requests and receive responses is updated to make it more consistent and discoverable (KTOR-29).

Request functions {id="request-overloads"}

Request functions with multiple parameters are deprecated. For example, the port and path parameters need to be replaced with a the url parameter exposed by HttpRequestBuilder:

client.get(port = 8080, path = "/customer/3")
client.get { url(port = 8080, path = "/customer/3") }

The HttpRequestBuilder also allows you to specify additional request parameters inside the request function lambda.

Request body {id="request-body"}

The HttpRequestBuilder.body property used to set the request body is replaced with the HttpRequestBuilder.setBody function:

client.post("http://localhost:8080/post") {
    body = "Body content"
}
client.post("http://localhost:8080/post") {
    setBody("Body content")
}

Responses {id="responses"}

With v2.0.0, request functions (such as get, post, put, submitForm, and so on) don't accept generic arguments for receiving an object of a specific type. Now all request functions return a HttpResponse object, which exposes the body function with a generic argument for receiving a specific type instance. You can also use bodyAsText or bodyAsChannel to receive content as a string or channel.

val httpResponse: HttpResponse = client.get("https://ktor.io/")
val stringBody: String = httpResponse.receive()
val byteArrayBody: ByteArray = httpResponse.receive()
val httpResponse: HttpResponse = client.get("https://ktor.io/")
val stringBody: String = httpResponse.body()
val byteArrayBody: ByteArray = httpResponse.body()

With the ContentNegotiation plugin installed, you can receive an arbitrary object as follows:

val customer: Customer = client.get("http://localhost:8080/customer/3")
val customer: Customer = client.get("http://localhost:8080/customer/3").body()

Streaming responses {id="streaming-response"}

Due to removing generic arguments from request functions, receiving a streaming response requires separate functions. To achieve this, functions with the prepare prefix are added, such as prepareGet or preparePost:

public suspend fun HttpClient.prepareGet(builder: HttpRequestBuilder): HttpStatement
public suspend fun HttpClient.preparePost(builder: HttpRequestBuilder): HttpStatement

The example below shows how to change your code in this case:

client.get<HttpStatement>("https://ktor.io/").execute { httpResponse ->
    val channel: ByteReadChannel = httpResponse.receive()
    while (!channel.isClosedForRead) {
        // Read data
    }
}
client.prepareGet("https://ktor.io/").execute { httpResponse ->
    val channel: ByteReadChannel = httpResponse.body()
    while (!channel.isClosedForRead) {
        // Read data
    }
}

You can find the full example here: .

Response validation {id="response-validation"}

With v2.0.0, the expectSuccess property used for response validation is set to false by default. This requires the following changes in your code:

Content negotiation and serialization {id="serialization-client"}

The Ktor client now supports content negotiation and shares serialization libraries with the Ktor server. The main changes are:

  • JsonFeature is deprecated in favor of ContentNegotiation, which can be found in the ktor-client-content-negotiation artifact.
  • Serialization libraries are moved from ktor-client-* to the ktor-serialization-* artifacts.

You need to update dependencies for and imports in your client code, as shown below.

Dependencies {id="imports-dependencies-client"}

Subsystem 1.6.x 2.0.0
ContentNegotiation n/a io.ktor:ktor-client-content-negotiation
kotlinx.serialization io.ktor:ktor-client-serialization io.ktor:ktor-serialization-kotlinx-json
Gson io.ktor:ktor-client-gson io.ktor:ktor-serialization-gson
Jackson io.ktor:ktor-client-jackson io.ktor:ktor-serialization-jackson

Imports {id="imports-serialization-client"}

Subsystem 1.6.x 2.0.0
ContentNegotiation n/a import io.ktor.client.plugins.contentnegotiation.*
kotlinx.serialization import io.ktor.client.features.json.* import io.ktor.serialization.kotlinx.json.*
Gson import io.ktor.client.features.json.* import io.ktor.serialization.gson.*
Jackson import io.ktor.client.features.json.* import io.ktor.serialization.jackson.*

Bearer authentication

The refreshTokens function now uses the RefreshTokenParams instance as lambda receiver (this) instead of the HttpResponse lambda argument (it):

bearer {
    refreshTokens {  // it: HttpResponse
        // ...
    }
}
bearer {
    refreshTokens { // this: RefreshTokenParams
        // ...
    }
}

RefreshTokenParams exposes the following properties:

  • response to access response parameters;
  • client to make a request to refresh tokens;
  • oldTokens to access tokens obtained using loadTokens.

HttpSend {id="http-send"}

The API of the HttpSend plugin is changed as follows:

client[HttpSend].intercept { originalCall, request ->
    if (originalCall.something()) {
        val newCall = execute(request)
        // ...
    }
}
client.plugin(HttpSend).intercept { request ->
    val originalCall = execute(request)
    if (originalCall.something()) {
        val newCall = execute(request)
        // ...
    }
}

Note that with v2.0.0 indexed access is not available for accessing plugins. Use the HttpClient.plugin function instead.

The HttpClient.get(plugin: HttpClientPlugin) function is removed {id="client-get"}

With the 2.0.0 version, the HttpClient.get function accepting a client plugin is removed. Use the HttpClient.plugin function instead.

client.get(HttpSend).intercept { ... }
// or
client[HttpSend].intercept { ... }
client.plugin(HttpSend).intercept {     ... }

Feature is renamed to Plugin {id="feature-plugin-client"}

As for the Ktor server, Feature is renamed to Plugin in the client API. This might affect your application, as described below.

Imports {id="feature-plugin-imports-client"}

Update imports for installing plugins:

Subsystem 1.6.x 2.0.0
  • Default request
  • User agent
  • Charsets
  • Response validation
  • Timeout
  • HttpCache
  • HttpSend
  • import io.ktor.client.features.* import io.ktor.client.plugins.*
    Authentication import io.ktor.client.features.auth.* import io.ktor.client.features.auth.providers.* import io.ktor.client.plugins.auth.* import io.ktor.client.plugins.auth.providers.*
    Cookies import io.ktor.client.features.cookies.* import io.ktor.client.plugins.cookies.*
    Logging import io.ktor.client.features.logging.* import io.ktor.client.plugins.logging.*
    WebSockets import io.ktor.client.features.websocket.* import io.ktor.client.plugins.websocket.*
    Content encoding import io.ktor.client.features.compression.* import io.ktor.client.plugins.compression.*

    Custom plugins {id="feature-plugin-custom-client"}

    The HttpClientFeature interface is renamed to HttpClientPlugin.

    The 'Ios' engine is renamed to 'Darwin' {id="darwin"}

    Given that the Ios engine targets not only iOS but other operating systems, including macOS, or tvOS, in v2.0.0, it is renamed to Darwin. This causes the following changes:

    • The io.ktor:ktor-client-ios artifact is renamed to io.ktor:ktor-client-darwin.
    • To create the HttpClient instance, you need to pass the Darwin class as an argument.
    • The IosClientEngineConfig configuration class is renamed to DarwinClientEngineConfig.

    To learn how to configure the Darwin engine, see the section.

    WebSockets code is moved to the 'websockets' package {id="client-ws-package"}

    WebSockets code is moved from http-cio to the websockets package. This requires updating imports as follows:

    1.6.x 2.0.0
    import io.ktor.http.cio.websocket.* import io.ktor.websocket.*