Developing Myself Everyday
article thumbnail

사진: UnsplashAlina Grubnyak

 

안드로이드 개발을 하다 보면 HTTP 요청을 해야 하는 경우가 있습니다. HTTP 요청을 처리할 수 있는 가장 흔하게 사용되는 라이브러리로는 Retrofit2이 있습니다.

 

Retrofit2은 클라이언트와 서버사이에 비동기적인 네트워크 요청 처리를 해주는 라이브러리입니다. 인터페이스에 어노테이션을 사용해서 생각보다 간편하게 구현할 수 있습니다.

 

다만, Retrofit2은 기본적으로 자바 라이브러리입니다. 현재 안드로이드에서 사용하는 라이브러리들은 코틀린 멀티 플랫폼을 위해 코틀린만으로 구성된 라이브러리로 만들어지고 있는 추세입니다. 그렇기에 Retrofit2를 계속해서 사용하는 것에도 제한 사항이 발생하였습니다.

 

이러한 제한 사항을 해결하기 위해 코틀린만으로 구성된 라이브러리가 탄생했습니다. 바로 Ktor입니다. 이번 게시글에서는 Ktor에 대해 알아보고, 이를 사용해서 네트워크 통신을 진행해보려고 합니다.


 

 

 

 

Ktor란?


Ktor는 코틀린으로 비동기 클라이언트 및 서버 앱을 작성할 수 있게 하는 코틀린 프레임워크입니다.

 

주로 웹 애플리케이션 개발에 사용되지만, 클라이언트 측 HTTP 호출도 처리할 수 있는 클라이언트 모듈을 포함하고 있습니다. HTTP 통신에 사용되는 부분이 바로 이 모듈입니다.

 

Ktor를 사용해서 Ktor 프로젝트를 생성해 서버측 코드를 작성할 수도 있습니다. 그렇기 때문에 Ktor가 곧 HTTP 호출 라이브러리라는 말은 아닙니다.

 

이번 게시글에서 다룰 내용은 HTTP 통신과 관련된 부분입니다.

 

 

 

 

Ktor로 HTTP 통신하기


Ktor로 HTTP 통신하는 예제 코드는 아래의 유튜브에서 가져왔습니다.

 

 

요청, 응답 데이터 클래스 정의하기

HTTP 통신을 위해 모든 라이브러리에서 가장 먼저 해야 하는 일은 요청, 응답 데이터를 파싱 할 데이터 클래스를 정의하는 것입니다.

 

데이터 클래스를 정의하는 방식에는 차이가 없기에 가장 편리하다고 생각하는 `@Serializable`을 사용해서 데이터 클래스를 정의하겠습니다. 

 

 

PostResponse.kt

@Serializable
data class PostResponse(
    val body: String,
    val title: String,
    val id: Int,
    val userId: Int
)

 

 

PostRequest.kt

@Serializable
data class PostRequest(
    val body: String,
    val title: String,
    val userId: Int
)

 

 

인터페이스 정의

Retrofit2의 인터페이스를 정의할 때에는 어노테이션을 사용해서 요청하거나 Header를 넣곤 했습니다. Ktor에는 이러한 어노테이션이 없습니다. 대신 Ktor는 자체 인터페이스를 정의하고 이를 구현하는 클래스를 만들어야 합니다.

 

다만 이러한 작업은 필수적이지는 않습니다. 굳이 인터페이스를 정의하지 않고도 클래스를 정의할 수 있습니다. 

 

우리가 서버로부터 요청할 것은 Posts를 가져오거나 Post를 만드는 것입니다.

 

 

PostService.kt

interface PostsService {

    suspend fun getPosts(): List<PostResponse>

    suspend fun createPost(postRequest: PostRequest): PostResponse?
}

 

 

PostServiceImpl.kt

class PostsServiceImpl(
    private val client: HttpClient
) : PostsService {

    override suspend fun getPosts(): List<PostResponse> {
       TODO()
    }

    override suspend fun createPost(postRequest: PostRequest): PostResponse? {
        TODO()
    }
}

 

 

클라이언트 생성

PostsService의 구현체에서 HttpClient를 넣어줘야 합니다. 이를 위해 클라이언트를 정의합니다.

 

이 과정에서 일반적으로는 의존성 주입 라이브러리를 사용해서 주입해 주는 것이 좋습니다. 다만 여기에서는 간단하게 팩토리 메서드를 정의해서 HttpClient를 주입해 주도록 하겠습니다.

 

팩토리 메서드는 PostsService의 companion object에서 정의하였습니다.

 

PostService.kt

interface PostsService {

    suspend fun getPosts(): List<PostResponse>

    suspend fun createPost(postRequest: PostRequest): PostResponse?
    
    // 팩토리 메서드
    companion object {
        fun create(): PostsService {
            return PostsServiceImpl(
                client = // 클라이언트 주입
            )
        }
    }
}

 

 

이제부터는 주입해줄 클라이언트를 만들어야 합니다. 사실 이러한 클라이언트는 Retrofit2에서 정의해 주던 클라이언트와 크게 다르지 않습니다.

 

그럼 Refrofit2에서의 클라이언트 코드를 한번 보겠습니다. OkHttpClient를 정의하고 HTTP 요청에서의 Log를 확인하기 위해 로깅 인터셉터를 추가해 주었습니다.

@Provides
@Singleton
fun provideOkhttpClient(): OkHttpClient =
    OkHttpClient
        .Builder()
        .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
        .connectTimeout(60, TimeUnit.SECONDS)
        .readTimeout(60, TimeUnit.SECONDS)
        .writeTimeout(60, TimeUnit.SECONDS)
        .build()

 

 

Ktor 라이브러리에서 제공하는 `HttpClient`에서 안드로이드의 플랫폼 구성으로 객체를 생성합니다. 그 다음에 Ktor에서도 위의 과정과 비슷하지만 더욱 간단하게 로깅 인터셉터를 추가해줄 수 있습니다. 

interface PostsService {
	...
    
    companion object {
        fun create(): PostsService {
            return PostsServiceImpl(
                client = HttpClient(Android) {
                    // 로깅 인터셉터
                    install(Logging) {
                        level = LogLevel.Body
                    }
                    
                }
            )
        }
    }
}

 

 

그다음으로는 Json 직렬화를 활성화해야 합니다. Retrofit2에서는 직렬화하기 위한 컨버터 팩토리를 넣어주곤 했습니다. 아래의 코드에서 `asConverterFactory` 메서드가 내부에서 Serializer를 활용한 컨버터 팩토리를 제공하게 해 줬습니다.

@Provides
@Singleton
fun provideConverterFactory(json: Json): Converter.Factory =
    json.asConverterFactory("application/json".toMediaType())

 

 

Ktor에서는 JSON을 직렬화할 때 필요한 라이브러리를 넣어주기만 하면 됩니다. 여기서는 `KotlinSerializer`를 넣어주었습니다.

interface PostsService {
	...
    
    companion object {
        fun create(): PostsService {
            return PostsServiceImpl(
                client = HttpClient(Android) {
                    install(Logging) {
                        level = LogLevel.Body
                    }
                    // JSON 직렬화 라이브러리
                    install(JsonFeature) {
                        serializer = KotlinxSerializer()
                    }
                }
            )
        }
    }
}

 

 

 

HTTP 요청하기

지금까지 기본적인 설정을 마무리했으니 다시 위로 올라가서 이전에 작성하지 않았던 PostsServiceImpl를 가져와 보겠습니다. 이 클래스에서는 만들었던 클라이언트를 활용해서 실제로 서버와 통신을 하는 코드를 작성합니다. 

 

Retrofit2에서 HTTP 메서드를 어노테이션으로 사용했던 것과 달리, Ktor에서는 확장 함수를 통해 사용할 수 있습니다.

 

먼저 단순하게 특정 Url로부터 `GET`으로 정보를 가져오는 경우의 코드입니다.

class PostsServiceImpl(
    private val client: HttpClient
) : PostsService {

    override suspend fun getPosts(): List<PostResponse> {
       return try {
            client.get { url(/* Url 주소 */) }
        } catch(e: Exception) {
            println("Error: ${e.message}")
            emptyList()
        }
    }
    
	...
}

 

 

다음으로는 요청 Body와 함께 `POST` 요청을 보내는 예시입니다. 이 과정에서 `ContentType`을 Json 형식임을 명시해서 서버에 데이터가 Json 형식이라고 알립니다.

class PostsServiceImpl(
    private val client: HttpClient
) : PostsService {
    
	...
    
    override suspend fun createPost(postRequest: PostRequest): PostResponse? {
        return try {
            client.post<PostResponse> {
                url(/* Url 주소 */)
                contentType(ContentType.Application.Json)
                body = postRequest
            }
        } catch(e: Exception) {
            println("Error: ${e.message}")
            null
        }
    }
}

 

실제로 PostsServiceImpl를 이제 주입받거나 접근해서 사용하는 것은 그렇게 어렵지 않을 것이라 생각합니다.

 

 

 

 

마무리


이렇게 해서 Ktor에 대해 간단하게 알아보고, 사용법을 학습하였습니다. 아무래도 다른 HTTP 라이브러리인  Retrofit2과 비교를 하면서 볼 수밖에 없었습니다. Retrofit2과 비교했을 때 Ktor에서 느낀 점은 간단하다! 였습니다. 

 

Interface에서 모든 것을 정의했던 Retrofit2과 달리 구현체에서 메서드로 HTTP 메서드를 사용할 수 있다는 점은 더욱더 확장하거나 변화에 대응하기 쉽지 않나 생각합니다.

 

 

 

 

 

 

Reference

 

Ktor: Build Asynchronous Servers and Clients in Kotlin

Kotlin Server and Client Framework for microservices, HTTP APIs, and RESTful services

ktor.io

 

GitHub - philipplackner/KtorClientAndroid

Contribute to philipplackner/KtorClientAndroid development by creating an account on GitHub.

github.com

 

profile

Developing Myself Everyday

@배준형

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!