1.6.x에서 2.0.x로 마이그레이션하기
이 가이드는 Ktor 애플리케이션을 1.6.x 버전에서 2.0.x 버전으로 마이그레이션하는 방법을 안내합니다.
Ktor 서버
서버 코드가 'io.ktor.server.*' 패키지로 이동되었습니다
서버 및 클라이언트 API를 통합하고 더 잘 구분하기 위해 서버 코드가 io.ktor.server.*
패키지로 이동되었습니다 (KTOR-2865). 이는 애플리케이션의 의존성과 임포트를 아래와 같이 업데이트해야 함을 의미합니다.
의존성
하위 시스템 | 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 |
Sessions | io.ktor:ktor-server-core | io.ktor:ktor-server-sessions |
모든 플러그인을 한 번에 추가하려면
io.ktor:ktor-server
아티팩트를 사용할 수 있습니다.
임포트
하위 시스템 | 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 코드가 'websockets' 패키지로 이동되었습니다
WebSockets 코드가 http-cio
에서 websockets
패키지로 이동되었습니다. 이에 따라 임포트를 다음과 같이 업데이트해야 합니다:
1.6.x | 2.0.0 |
---|---|
import io.ktor.http.cio.websocket.* | import io.ktor.websocket.* |
참고로 이 변경사항은 클라이언트에도 영향을 미칩니다.
Feature가 Plugin으로 이름이 변경되었습니다
Ktor 2.0.0에서 _Feature_가 _Plugin_으로 이름이 변경되었습니다. 이는 요청/응답 파이프라인을 가로채는 기능을 더 잘 설명하기 위함입니다 (KTOR-2326). 이는 전체 Ktor API에 영향을 미치며, 아래 설명된 대로 애플리케이션을 업데이트해야 합니다.
임포트
플러그인 설치에는 임포트 업데이트가 필요하며, 또한 서버 코드를 io.ktor.server.*
패키지로 이동하는 것과 관련이 있습니다:
1.6.x | 2.0.0 |
---|---|
import io.ktor.features.* | import io.ktor.server.plugins.* |
커스텀 플러그인
Feature를 Plugin으로 이름 변경은 커스텀 플러그인 관련 API에 다음과 같은 변경 사항을 도입합니다:
ApplicationFeature
인터페이스가BaseApplicationPlugin
으로 이름이 변경되었습니다.Features
파이프라인 단계가Plugins
로 이름이 변경되었습니다.
참고로 v2.0.0부터 Ktor는 커스텀 플러그인 생성을 위한 새로운 API를 제공합니다. 일반적으로 이 API는 파이프라인, 단계 등 내부 Ktor 개념에 대한 이해를 요구하지 않습니다. 대신,
onCall
,onCallReceive
,onCallRespond
등 다양한 핸들러를 사용하여 요청 및 응답 처리의 여러 단계에 액세스할 수 있습니다. 파이프라인 단계가 새로운 API 핸들러에 어떻게 매핑되는지는 다음 섹션에서 확인할 수 있습니다: 파이프라인 단계와 새로운 API 핸들러 매핑.
콘텐츠 협상 및 직렬화
콘텐츠 협상 및 직렬화 서버 API는 서버와 클라이언트 간의 직렬화 라이브러리를 재사용하도록 리팩토링되었습니다. 주요 변경사항은 다음과 같습니다:
ContentNegotiation
이ktor-server-core
에서 별도의ktor-server-content-negotiation
아티팩트로 이동되었습니다.- 직렬화 라이브러리가
ktor-*
에서 클라이언트에서도 사용되는ktor-serialization-*
아티팩트로 이동되었습니다.
애플리케이션의 의존성과 임포트를 아래와 같이 업데이트해야 합니다.
의존성
하위 시스템 | 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 |
임포트
하위 시스템 | 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.* |
커스텀 컨버터
ContentConverter 인터페이스에 의해 노출되는 함수의 시그니처가 다음과 같이 변경되었습니다:
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?
}
테스팅 API
v2.0.0부터 Ktor 서버는 테스팅을 위한 새로운 API를 사용하며, 이는 KTOR-971에 설명된 다양한 문제를 해결합니다. 주요 변경사항은 다음과 같습니다:
withTestApplication
/withApplication
함수가 새로운testApplication
함수로 대체되었습니다.testApplication
함수 내에서 기존 Ktor 클라이언트 인스턴스를 사용하여 서버에 요청하고 결과를 확인할 수 있습니다.- 특정 기능(예: 쿠키 또는 WebSockets)을 테스트하려면 새로운 클라이언트 인스턴스를 생성하고 해당 플러그인을 설치해야 합니다.
1.6.x 테스트를 2.0.0으로 마이그레이션하는 몇 가지 예시를 살펴보겠습니다:
기본 서버 테스트
아래 테스트에서 handleRequest
함수는 client.get
요청으로 대체되었습니다:
@Test
fun testRootLegacyApi() {
withTestApplication(Application::module) {
handleRequest(HttpMethod.Get, "/").apply {
assertEquals(HttpStatusCode.OK, response.status())
assertEquals("Hello, world!", response.content)
}
}
}
@Test
fun testRoot() = testApplication {
val response = client.get("/")
assertEquals(HttpStatusCode.OK, response.status)
assertEquals("Hello, world!", response.bodyAsText())
}
x-www-form-urlencoded
아래 테스트에서 handleRequest
함수는 client.post
요청으로 대체되었습니다:
@Test
fun testPostLegacyApi() = withTestApplication(Application::main) {
with(handleRequest(HttpMethod.Post, "/signup"){
addHeader(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString())
setBody(listOf("username" to "JetBrains", "email" to "[email protected]", "password" to "foobar", "confirmation" to "foobar").formUrlEncode())
}) {
assertEquals("The 'JetBrains' account is created", response.content)
}
}
@Test
fun testPost() = testApplication {
val response = client.post("/signup") {
header(HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString())
setBody(listOf("username" to "JetBrains", "email" to "[email protected]", "password" to "foobar", "confirmation" to "foobar").formUrlEncode())
}
assertEquals("The 'JetBrains' account is created", response.bodyAsText())
}
multipart/form-data
v2.0.0에서 multipart/form-data
를 구성하려면 클라이언트의 setBody
함수에 MultiPartFormDataContent
를 전달해야 합니다:
@Test
fun testUploadLegacyApi() = withTestApplication(Application::main) {
with(handleRequest(HttpMethod.Post, "/upload"){
val boundary = "WebAppBoundary"
val fileBytes = File("ktor_logo.png").readBytes()
addHeader(HttpHeaders.ContentType, ContentType.MultiPart.FormData.withParameter("boundary", boundary).toString())
setBody(boundary, listOf(
PartData.FormItem("Ktor logo", { }, headersOf(
HttpHeaders.ContentDisposition,
ContentDisposition.Inline
.withParameter(ContentDisposition.Parameters.Name, "description")
.toString()
)),
PartData.FileItem({ fileBytes.inputStream().asInput() }, {}, headersOf(
HttpHeaders.ContentDisposition,
ContentDisposition.File
.withParameter(ContentDisposition.Parameters.Name, "image")
.withParameter(ContentDisposition.Parameters.FileName, "ktor_logo.png")
.toString()
))
))
}) {
assertEquals("Ktor logo is uploaded to 'uploads/ktor_logo.png'", response.content)
}
}
@Test
fun testUpload() = testApplication {
val boundary = "WebAppBoundary"
val response = client.post("/upload") {
setBody(
MultiPartFormDataContent(
formData {
append("description", "Ktor logo")
append("image", File("ktor_logo.png").readBytes(), Headers.build {
append(HttpHeaders.ContentType, "image/png")
append(HttpHeaders.ContentDisposition, "filename=\"ktor_logo.png\"")
})
},
boundary,
ContentType.MultiPart.FormData.withParameter("boundary", boundary)
)
)
}
assertEquals("Ktor logo is uploaded to 'uploads/ktor_logo.png'", response.bodyAsText())
}
JSON 데이터
v1.6.x에서는 kotlinx.serialization
라이브러리에서 제공하는 Json.encodeToString
함수를 사용하여 JSON 데이터를 직렬화할 수 있습니다. v2.0.0부터는 새로운 클라이언트 인스턴스를 생성하고 특정 형식으로 콘텐츠를 직렬화/역직렬화할 수 있는 ContentNegotiation 플러그인을 설치해야 합니다:
@Test
fun testPostCustomerLegacyApi() = withTestApplication(Application::main) {
with(handleRequest(HttpMethod.Post, "/customer"){
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
setBody(Json.encodeToString(Customer(3, "Jet", "Brains")))
}) {
assertEquals("Customer stored correctly", response.content)
assertEquals(HttpStatusCode.Created, response.status())
}
}
@Test
fun testPostCustomer() = testApplication {
val client = createClient {
install(ContentNegotiation) {
json()
}
}
val response = client.post("/customer") {
contentType(ContentType.Application.Json)
setBody(Customer(3, "Jet", "Brains"))
}
assertEquals("Customer stored correctly", response.bodyAsText())
assertEquals(HttpStatusCode.Created, response.status)
}
테스트 중 쿠키 보존
v1.6.x에서는 테스트 시 요청 간에 쿠키를 보존하기 위해 cookiesSession
이 사용됩니다. v2.0.0부터는 새로운 클라이언트 인스턴스를 생성하고 HttpCookies 플러그인을 설치해야 합니다:
@Test
fun testRequestsLegacyApi() = withTestApplication(Application::main) {
fun doRequestAndCheckResponse(path: String, expected: String) {
handleRequest(HttpMethod.Get, path).apply {
assertEquals(expected, response.content)
}
}
cookiesSession {
handleRequest(HttpMethod.Get, "/login") {}.apply {}
doRequestAndCheckResponse("/user", "Session ID is 123abc. Reload count is 0.")
doRequestAndCheckResponse("/user", "Session ID is 123abc. Reload count is 1.")
doRequestAndCheckResponse("/user", "Session ID is 123abc. Reload count is 2.")
handleRequest(HttpMethod.Get, "/logout").apply {}
doRequestAndCheckResponse("/user", "Session doesn't exist or is expired.")
}
}
@Test
fun testRequests() = testApplication {
val client = createClient {
install(HttpCookies)
}
val loginResponse = client.get("/login")
val response1 = client.get("/user")
assertEquals("Session ID is 123abc. Reload count is 1.", response1.bodyAsText())
val response2 = client.get("/user")
assertEquals("Session ID is 123abc. Reload count is 2.", response2.bodyAsText())
val response3 = client.get("/user")
assertEquals("Session ID is 123abc. Reload count is 3.", response3.bodyAsText())
val logoutResponse = client.get("/logout")
assertEquals("Session doesn't exist or is expired.", logoutResponse.bodyAsText())
}
WebSockets
이전 API에서는 WebSocket 대화를 테스트하기 위해 handleWebSocketConversation
이 사용됩니다. v2.0.0부터는 클라이언트가 제공하는 WebSockets 플러그인을 사용하여 WebSocket 대화를 테스트할 수 있습니다:
@Test
fun testConversationLegacyApi() {
withTestApplication(Application::module) {
handleWebSocketConversation("/echo") { incoming, outgoing ->
val greetingText = (incoming.receive() as Frame.Text).readText()
assertEquals("Please enter your name", greetingText)
outgoing.send(Frame.Text("JetBrains"))
val responseText = (incoming.receive() as Frame.Text).readText()
assertEquals("Hi, JetBrains!", responseText)
}
}
}
@Test
fun testConversation() {
testApplication {
val client = createClient {
install(WebSockets)
}
client.webSocket("/echo") {
val greetingText = (incoming.receive() as? Frame.Text)?.readText() ?: ""
assertEquals("Please enter your name", greetingText)
send(Frame.Text("JetBrains"))
val responseText = (incoming.receive() as Frame.Text).readText()
assertEquals("Hi, JetBrains!", responseText)
}
}
}
DoubleReceive
v2.0.0부터 DoubleReceive 플러그인 설정은 receiveEntireContent
와 반대되는 cacheRawRequest
속성을 도입합니다:
- v1.6.x에서는
receiveEntireContent
속성이 기본적으로false
로 설정됩니다. - v2.0.0에서는
cacheRawRequest
가 기본적으로true
로 설정됩니다.receiveEntireContent
속성은 제거되었습니다.
전달된 헤더
v2.0.0에서 ForwardedHeaderSupport
및 XForwardedHeaderSupport
플러그인은 각각 ForwardedHeaders 및 XForwardedHeaders
로 이름이 변경되었습니다.
캐싱 헤더
캐싱 옵션을 정의하는 데 사용되는 options 함수는 이제 OutgoingContent
외에도 람다 인수로 ApplicationCall
을 받습니다:
install(CachingHeaders) {
options { outgoingContent ->
// ...
}
}
install(CachingHeaders) {
options { call, outgoingContent ->
// ...
}
}
조건부 헤더
리소스 버전 목록을 정의하는 데 사용되는 version 함수는 이제 OutgoingContent
외에도 람다 인수로 ApplicationCall
을 받습니다:
install(ConditionalHeaders) {
version { outgoingContent ->
// ...
}
}
install(ConditionalHeaders) {
version { call, outgoingContent ->
// ...
}
}
CORS
CORS 설정에 사용되는 몇 가지 함수 이름이 변경되었습니다:
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
v1.6.x에서는 baseName
속성이 HTTP 요청 모니터링에 사용되는 Ktor 메트릭의 기본 이름(접두사)을 지정하는 데 사용됩니다. 기본적으로 ktor.http.server
와 같습니다. v2.0.0부터 baseName
은 기본값이 ktor.http.server.requests
인 metricName
으로 대체되었습니다.
Ktor 클라이언트
요청 및 응답
v2.0.0에서는 요청을 하고 응답을 받는 데 사용되는 API가 일관성과 검색 용이성(discoverability)을 높이기 위해 업데이트되었습니다 (KTOR-29).
요청 함수
여러 매개변수를 가진 요청 함수는 더 이상 사용되지 않습니다. 예를 들어, port
및 path
매개변수는 HttpRequestBuilder에 노출된 url
매개변수로 대체되어야 합니다:
client.get(port = 8080, path = "/customer/3")
client.get { url(port = 8080, path = "/customer/3") }
HttpRequestBuilder
는 또한 요청 함수 람다 내에서 추가 요청 매개변수를 지정할 수 있도록 합니다.
요청 본문
요청 본문을 설정하는 데 사용되는 HttpRequestBuilder.body
속성이 HttpRequestBuilder.setBody
함수로 대체되었습니다:
client.post("http://localhost:8080/post") {
body = "Body content"
}
client.post("http://localhost:8080/post") {
setBody("Body content")
}
응답
v2.0.0부터 get
, post
, put
, submitForm 등 요청 함수는 특정 타입의 객체를 받는 제네릭 인수를 허용하지 않습니다. 이제 모든 요청 함수는 HttpResponse
객체를 반환하며, 이는 특정 타입 인스턴스를 받기 위한 제네릭 인수가 있는 body
함수를 노출합니다. bodyAsText
또는 bodyAsChannel
을 사용하여 콘텐츠를 문자열 또는 채널로 받을 수도 있습니다.
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()
ContentNegotiation 플러그인이 설치된 경우, 다음과 같이 임의의 객체를 받을 수 있습니다:
val customer: Customer = client.get("http://localhost:8080/customer/3")
val customer: Customer = client.get("http://localhost:8080/customer/3").body()
스트리밍 응답
요청 함수에서 제네릭 인수 제거로 인해 스트리밍 응답을 받으려면 별도의 함수가 필요합니다. 이를 위해 prepareGet
또는 preparePost
와 같이 prepare
접두사가 붙은 함수가 추가되었습니다:
public suspend fun HttpClient.prepareGet(builder: HttpRequestBuilder): HttpStatement
public suspend fun HttpClient.preparePost(builder: HttpRequestBuilder): HttpStatement
아래 예시는 이 경우 코드를 변경하는 방법을 보여줍니다:
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
}
}
전체 예시는 여기에서 찾을 수 있습니다: 데이터 스트리밍.
응답 유효성 검사
v2.0.0부터 응답 유효성 검사에 사용되는 expectSuccess
속성은 기본적으로 false
로 설정됩니다. 이는 코드에 다음과 같은 변경 사항을 요구합니다:
- 기본 유효성 검사를 활성화하고 2xx가 아닌 응답에 대해 예외를 발생시키려면
expectSuccess
속성을true
로 설정하세요. handleResponseExceptionWithRequest
를 사용하여 2xx가 아닌 예외를 처리하는 경우에도expectSuccess
를 명시적으로 활성화해야 합니다.
HttpResponseValidator
handleResponseException 함수는 예외에 추가 정보를 제공하기 위해 HttpRequest
에 대한 접근을 추가하는 handleResponseExceptionWithRequest
로 대체되었습니다:
HttpResponseValidator {
handleResponseException { exception ->
// ...
}
}
HttpResponseValidator {
handleResponseExceptionWithRequest { exception, request ->
// ...
}
}
콘텐츠 협상 및 직렬화
Ktor 클라이언트는 이제 콘텐츠 협상을 지원하며 Ktor 서버와 직렬화 라이브러리를 공유합니다. 주요 변경사항은 다음과 같습니다:
JsonFeature
는ktor-client-content-negotiation
아티팩트에서 찾을 수 있는ContentNegotiation
으로 인해 더 이상 사용되지 않습니다.- 직렬화 라이브러리가
ktor-client-*
에서ktor-serialization-*
아티팩트로 이동되었습니다.
클라이언트 코드의 의존성과 임포트를 아래와 같이 업데이트해야 합니다.
의존성
하위 시스템 | 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 |
임포트
하위 시스템 | 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 인증
refreshTokens 함수는 이제 HttpResponse
람다 인수(it
) 대신 RefreshTokenParams
인스턴스를 람다 수신자(this
)로 사용합니다:
bearer {
refreshTokens { // it: HttpResponse
// ...
}
}
bearer {
refreshTokens { // this: RefreshTokenParams
// ...
}
}
RefreshTokenParams
는 다음 속성을 노출합니다:
response
: 응답 매개변수에 접근하기 위함;client
: 토큰을 새로 고치기 위한 요청을 하기 위함;oldTokens
:loadTokens
를 사용하여 얻은 토큰에 접근하기 위함.
HttpSend
HttpSend 플러그인의 API는 다음과 같이 변경되었습니다:
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)
// ...
}
}
참고로 v2.0.0에서는 플러그인에 접근하기 위한 인덱스 접근이 지원되지 않습니다. 대신 HttpClient.plugin 함수를 사용하세요.
HttpClient.get(plugin: HttpClientPlugin) 함수가 제거되었습니다
2.0.0 버전부터 클라이언트 플러그인을 받는 HttpClient.get
함수는 제거되었습니다. 대신 HttpClient.plugin
함수를 사용하세요.
client.get(HttpSend).intercept { ... }
// or
client[HttpSend].intercept { ... }
client.plugin(HttpSend).intercept { ... }
Feature가 Plugin으로 이름이 변경되었습니다
Ktor 서버와 마찬가지로 클라이언트 API에서도 _Feature_가 _Plugin_으로 이름이 변경되었습니다. 이는 아래 설명된 대로 애플리케이션에 영향을 미칠 수 있습니다.
임포트
플러그인 설치를 위한 임포트를 업데이트하세요:
하위 시스템 | 1.6.x | 2.0.0 |
| `import io.ktor.client.features.*` | `import io.ktor.client.plugins.*` |
인증 The Auth plugin handles authentication and authorization in your client application. | ` 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.* ` |
쿠키 The HttpCookies plugin handles cookies automatically and keep them between calls in a storage. | `import io.ktor.client.features.cookies.*` | `import io.ktor.client.plugins.cookies.*` |
로깅 Required dependencies: io.ktor:ktor-client-logging Code example: %example_name% | `import io.ktor.client.features.logging.*` | `import io.ktor.client.plugins.logging.*` |
WebSockets The Websockets plugin allows you to create a multi-way communication session between a server and a client. | `import io.ktor.client.features.websocket.*` | `import io.ktor.client.plugins.websocket.*` |
콘텐츠 인코딩 The ContentEncoding plugin allows you to enable specified compression algorithms (such as 'gzip' and 'deflate') and configure their settings. | `import io.ktor.client.features.compression.*` | `import io.ktor.client.plugins.compression.*` |
커스텀 플러그인
HttpClientFeature
인터페이스가 HttpClientPlugin
으로 이름이 변경되었습니다.
네이티브 타겟을 위한 새로운 메모리 모델
v2.0.0부터 네이티브 타겟에서 Ktor 클라이언트를 사용하려면 새로운 Kotlin/Native 메모리 모델을 활성화해야 합니다: 새로운 MM 활성화.
v2.2.0부터 새로운 Kotlin/Native 메모리 모델은 기본적으로 활성화됩니다.
'Ios' 엔진이 'Darwin'으로 이름이 변경되었습니다
v2.0.0에서 Ios
엔진이 iOS뿐만 아니라 macOS 또는 tvOS를 포함한 다른 운영 체제를 타겟팅하기 때문에 Darwin
으로 이름이 변경되었습니다. 이로 인해 다음과 같은 변경 사항이 발생합니다:
io.ktor:ktor-client-ios
아티팩트가io.ktor:ktor-client-darwin
으로 이름이 변경되었습니다.HttpClient
인스턴스를 생성하려면Darwin
클래스를 인수로 전달해야 합니다.IosClientEngineConfig
구성 클래스가DarwinClientEngineConfig
로 이름이 변경되었습니다.
Darwin
엔진을 구성하는 방법을 알아보려면 Darwin 섹션을 참조하세요.
WebSockets 코드가 'websockets' 패키지로 이동되었습니다
WebSockets 코드가 http-cio
에서 websockets
패키지로 이동되었습니다. 이에 따라 임포트를 다음과 같이 업데이트해야 합니다:
1.6.x | 2.0.0 |
---|---|
import io.ktor.http.cio.websocket.* | import io.ktor.websocket.* |
기본 요청
DefaultRequest 플러그인은 HttpRequestBuilder
대신 DefaultRequestBuilder
구성 클래스를 사용합니다:
val client = HttpClient(CIO) {
defaultRequest {
// this: HttpRequestBuilder
}
}
val client = HttpClient(CIO) {
defaultRequest {
// this: DefaultRequestBuilder
}
}