사용자 지정 클라이언트 플러그인
코드 예시: client-custom-plugin
v2.2.0부터 Ktor는 사용자 지정 클라이언트 플러그인을 생성하기 위한 새로운 API를 제공합니다. 일반적으로 이 API는 파이프라인, 페이즈 등 내부 Ktor 개념에 대한 이해를 요구하지 않습니다. 대신 onRequest
, onResponse
등과 같은 핸들러 세트를 사용하여 요청 및 응답 처리의 다양한 단계에 접근할 수 있습니다.
첫 번째 플러그인 생성 및 설치
이 섹션에서는 각 요청에 사용자 지정 헤더를 추가하는 첫 번째 플러그인을 생성하고 설치하는 방법을 보여줍니다.
플러그인을 생성하려면 createClientPlugin 함수를 호출하고 플러그인 이름을 인수로 전달합니다.
kotlinpackage com.example.plugins import io.ktor.client.plugins.api.* val CustomHeaderPlugin = createClientPlugin("CustomHeaderPlugin") { // Configure the plugin ... }
이 함수는 플러그인 설치에 사용될
ClientPlugin
인스턴스를 반환합니다.각 요청에 사용자 지정 헤더를 추가하려면 요청 파라미터에 접근할 수 있는
onRequest
핸들러를 사용할 수 있습니다.kotlinpackage com.example.plugins import io.ktor.client.plugins.api.* val CustomHeaderPlugin = createClientPlugin("CustomHeaderPlugin") { onRequest { request, _ -> request.headers.append("X-Custom-Header", "Default value") } }
플러그인을 설치하려면 생성된
ClientPlugin
인스턴스를 클라이언트의 구성 블록 내install
함수에 전달합니다.kotlinimport com.example.plugins.* val client = HttpClient(CIO) { install(CustomHeaderPlugin) }
전체 예시는 다음에서 찾을 수 있습니다: CustomHeader.kt. 다음 섹션에서는 플러그인 구성을 제공하고 요청 및 응답을 처리하는 방법을 살펴보겠습니다.
플러그인 구성 제공
이전 섹션에서는 미리 정의된 사용자 지정 헤더를 각 응답에 추가하는 플러그인을 생성하는 방법을 보여주었습니다. 이 플러그인을 더 유용하게 만들고 어떤 사용자 지정 헤더 이름과 값이라도 전달할 수 있는 구성을 제공해 보겠습니다.
먼저 구성 클래스를 정의해야 합니다.
kotlinclass CustomHeaderPluginConfig { var headerName: String = "X-Custom-Header" var headerValue: String = "Default value" }
플러그인에서 이 구성을 사용하려면 구성 클래스 참조를
createApplicationPlugin
에 전달합니다.kotlinimport 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) } }
플러그인 구성 필드는 변경 가능하므로, 이를 로컬 변수에 저장하는 것이 좋습니다.
마지막으로 다음과 같이 플러그인을 설치하고 구성할 수 있습니다.
kotlinval client = HttpClient(CIO) { install(CustomHeaderConfigurablePlugin) { headerName = "X-Custom-Header" headerValue = "Hello, world!" } }
전체 예시는 다음에서 찾을 수 있습니다: CustomHeaderConfigurable.kt.
요청 및 응답 처리
사용자 지정 플러그인은 전용 핸들러 세트를 사용하여 요청 및 응답 처리의 다양한 단계에 접근할 수 있도록 합니다. 예를 들면 다음과 같습니다.
onRequest
및onResponse
는 각각 요청과 응답을 처리할 수 있도록 합니다.transformRequestBody
및transformResponseBody
는 요청 및 응답 본문에 필요한 변환을 적용하는 데 사용될 수 있습니다.
또한 호출의 다른 단계를 처리하는 데 유용할 수 있는 특정 훅(hook)을 호출할 수 있도록 하는 on(...)
핸들러도 있습니다. 아래 표는 모든 핸들러가 실행되는 순서대로 나열합니다.
핸들러 | 설명 |
onRequest | 이 핸들러는 각 HTTP 요청 에 대해 실행되며 요청을 수정할 수 있도록 합니다. 요청을 보내고 요청 URL, HTTP 메서드, 헤더, 요청 본문과 같은 다양한 요청 매개변수를 지정하는 방법을 알아보세요. 예시: 사용자 지정 헤더 |
transformRequestBody | 요청 본문을 변환할 수 있도록 합니다. 이 핸들러에서는 본문을 OutgoingContent (예: 예시: 데이터 변환 |
onResponse | 이 핸들러는 수신되는 각 HTTP 응답 에 대해 실행되며, 응답을 다양한 방식으로 검사할 수 있도록 합니다: 응답 로깅, 쿠키 저장 등. 요청을 보내고 요청 URL, HTTP 메서드, 헤더, 요청 본문과 같은 다양한 요청 매개변수를 지정하는 방법을 알아보세요. |
transformResponseBody | 응답 본문을 변환할 수 있도록 합니다. 이 핸들러는 각 예시: 데이터 변환 |
onClose | 이 플러그인에 의해 할당된 리소스를 정리할 수 있도록 합니다. 이 핸들러는 클라이언트가 닫힐 때 호출됩니다. |
핸들러 | 설명 |
on(SetupRequest) | SetupRequest 훅은 요청 처리에서 가장 먼저 실행됩니다. |
onRequest | 이 핸들러는 각 HTTP 요청 에 대해 실행되며 요청을 수정할 수 있도록 합니다. 요청을 보내고 요청 URL, HTTP 메서드, 헤더, 요청 본문과 같은 다양한 요청 매개변수를 지정하는 방법을 알아보세요. 예시: 사용자 지정 헤더 |
transformRequestBody | 요청 본문을 변환할 수 있도록 합니다. 이 핸들러에서는 본문을 OutgoingContent (예: 예시: 데이터 변환 |
on(Send) |
예시: 인증 |
on(SendingRequest) |
Console |
onResponse | 이 핸들러는 수신되는 각 HTTP 응답 에 대해 실행되며, 응답을 다양한 방식으로 검사할 수 있도록 합니다: 응답 로깅, 쿠키 저장 등. 요청을 보내고 요청 URL, HTTP 메서드, 헤더, 요청 본문과 같은 다양한 요청 매개변수를 지정하는 방법을 알아보세요. |
transformResponseBody | 응답 본문을 변환할 수 있도록 합니다. 이 핸들러는 각 예시: 데이터 변환 |
onClose | 이 플러그인에 의해 할당된 리소스를 정리할 수 있도록 합니다. 이 핸들러는 클라이언트가 닫힐 때 호출됩니다. |
호출 상태 공유
사용자 지정 플러그인을 사용하면 호출과 관련된 모든 값을 공유하여 이 호출을 처리하는 모든 핸들러 내에서 해당 값에 접근할 수 있습니다. 이 값은 call.attributes
컬렉션에 고유 키를 가진 속성으로 저장됩니다. 다음 예시는 속성을 사용하여 요청을 보내는 시점과 응답을 받는 시점 사이의 시간을 계산하는 방법을 보여줍니다.
import io.ktor.client.plugins.api.*
import io.ktor.util.*
val ResponseTimePlugin = createClientPlugin("ResponseTimePlugin") {
val onCallTimeKey = AttributeKey<Long>("onCallTimeKey")
on(SendingRequest) { request, content ->
val onCallTime = System.currentTimeMillis()
request.attributes.put(onCallTimeKey, onCallTime)
}
onResponse { response ->
val onCallTime = response.call.attributes[onCallTimeKey]
val onCallReceiveTime = System.currentTimeMillis()
println("Read response delay (ms): ${onCallReceiveTime - onCallTime}")
}
}
전체 예시는 다음에서 찾을 수 있습니다: ResponseTime.kt.
클라이언트 구성 접근
HttpClient
인스턴스를 반환하는 client
속성을 사용하여 클라이언트 구성에 접근할 수 있습니다. 다음 예시는 클라이언트가 사용하는 프록시 주소를 얻는 방법을 보여줍니다.
import io.ktor.client.plugins.api.*
val SimplePlugin = createClientPlugin("SimplePlugin") {
val proxyAddress = client.engineConfig.proxy?.address()
println("Proxy address: $proxyAddress")
}
예시
아래 코드 샘플은 사용자 지정 플러그인의 몇 가지 예시를 보여줍니다. 결과 프로젝트는 다음에서 찾을 수 있습니다: client-custom-plugin.
사용자 지정 헤더
각 요청에 사용자 지정 헤더를 추가하는 플러그인을 생성하는 방법을 보여줍니다.
package com.example.plugins
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)
}
}
class CustomHeaderPluginConfig {
var headerName: String = "X-Custom-Header"
var headerValue: String = "Default value"
}
헤더 로깅
요청 및 응답 헤더를 로깅하는 플러그인을 생성하는 방법을 보여줍니다.
package com.example.plugins
import io.ktor.client.plugins.api.*
val LoggingHeadersPlugin = createClientPlugin("LoggingHeadersPlugin") {
on(SendingRequest) { request, content ->
println("Request headers:")
request.headers.entries().forEach { entry ->
printHeader(entry)
}
}
onResponse { response ->
println("Response headers:")
response.headers.entries().forEach { entry ->
printHeader(entry)
}
}
}
private fun printHeader(entry: Map.Entry<String, List<String>>) {
var headerString = entry.key + ": "
entry.value.forEach { headerValue ->
headerString += "${headerValue};"
}
println("-> $headerString")
}
응답 시간
요청을 보내는 시점과 응답을 받는 시점 사이의 시간을 측정하는 플러그인을 생성하는 방법을 보여줍니다.
package com.example.plugins
import io.ktor.client.plugins.api.*
import io.ktor.util.*
val ResponseTimePlugin = createClientPlugin("ResponseTimePlugin") {
val onCallTimeKey = AttributeKey<Long>("onCallTimeKey")
on(SendingRequest) { request, content ->
val onCallTime = System.currentTimeMillis()
request.attributes.put(onCallTimeKey, onCallTime)
}
onResponse { response ->
val onCallTime = response.call.attributes[onCallTimeKey]
val onCallReceiveTime = System.currentTimeMillis()
println("Read response delay (ms): ${onCallReceiveTime - onCallTime}")
}
}
데이터 변환
transformRequestBody
및 transformResponseBody
훅을 사용하여 요청 및 응답 본문을 변환하는 방법을 보여줍니다.
package com.example.plugins
import com.example.model.*
import io.ktor.client.plugins.api.*
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.utils.io.*
val DataTransformationPlugin = createClientPlugin("DataTransformationPlugin") {
transformRequestBody { request, content, bodyType ->
if (bodyType?.type == User::class) {
val user = content as User
TextContent(text="${user.name};${user.age}", contentType = ContentType.Text.Plain)
} else {
null
}
}
transformResponseBody { response, content, requestedType ->
if (requestedType.type == User::class) {
val receivedContent = content.readUTF8Line()!!.split(";")
User(receivedContent[0], receivedContent[1].toInt())
} else {
content
}
}
}
package com.example
import com.example.model.*
import com.example.plugins.*
import com.example.server.*
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.*
fun main() {
startServer()
runBlocking {
val client = HttpClient(CIO) {
install(DataTransformationPlugin)
}
val bodyAsText = client.post("http://0.0.0.0:8080/post-data") {
setBody(User("John", 42))
}.bodyAsText()
val user = client.get("http://0.0.0.0:8080/get-data").body<User>()
println("Userinfo: $bodyAsText")
println("Username: ${user.name}, age: ${user.age}")
}
}
package com.example.model
data class User(val name: String, val age: Int)
전체 예시는 다음에서 찾을 수 있습니다: client-custom-plugin-data-transformation.
인증
서버로부터 무단 응답이 수신된 경우 on(Send)
훅을 사용하여 Authorization
헤더에 베어러 토큰을 추가하는 방법을 보여주는 샘플 Ktor 프로젝트입니다.
package com.example.plugins
import io.ktor.client.plugins.api.*
import io.ktor.http.*
val AuthPlugin = createClientPlugin("AuthPlugin", ::AuthPluginConfig) {
val token = pluginConfig.token
on(Send) { request ->
val originalCall = proceed(request)
originalCall.response.run { // this: HttpResponse
if(status == HttpStatusCode.Unauthorized && headers["WWW-Authenticate"]!!.contains("Bearer")) {
request.headers.append("Authorization", "Bearer $token")
proceed(request)
} else {
originalCall
}
}
}
}
class AuthPluginConfig {
var token: String = ""
}
package com.example
import com.example.plugins.*
import com.example.server.*
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.*
fun main() {
startServer()
runBlocking {
val client = HttpClient(CIO) {
install(AuthPlugin) {
token = "abc123"
}
}
val response = client.get("http://0.0.0.0:8080/")
println(response.bodyAsText())
}
}
전체 예시는 다음에서 찾을 수 있습니다: client-custom-plugin-auth.