본문 바로가기

Android

서버의 응답에서 원하는 부분만 필터링하기

대다수의 애플리케이션에서 네트워크를 사용하는 것은 필수가 되었습니다.

 

이 때 REST API를 통해 주고 받는 데이터는 주로 JSON 타입을 사용합니다.

그리고 안드로이드에서 서버 통신을 위해 가장 많이 사용하는 네트워크 라이브러리는 Retrofit, OkHttp, Gson 조합일 것입니다.

 

라이브러리 공식 소개 문서

Retrofit : https://square.github.io/retrofit/

OkHttp : https://square.github.io/okhttp/

Gson : https://sites.google.com/site/gson/gson-user-guide#TOC-Overview

What is Retrofit?

간략히 소개하면,

  • Retrofit
    • 타입-세이프하게 HTTP 네트워크 응답을 처리할 수 있습니다.
    • 어노테이션 기반 인터페이스 작성으로 네트워크를 정의할 수 있습니다.
    • 내부적으로 생성되는 구현 코드는 OkHttp를 사용해서 동작합니다.
  • OkHttp
    • HTTP 통신을 편리하게 하기 위한 기능들을 제공합니다.
  • Gson
    • Json 자료형을 편리하게 조작할 수 있도록 기능을 제공합니다.

즉, 개발자는 Retrofit을 사용해서 네트워크 명세를 작성(인터페이스)하고 Retrofit은 작성된 명세에 맞게 구현된 코드를 auto-generate 합니다. 그리고 실제 HTTP 통신 수행을 위해서는 내부에서 OkHttp를 사용합니다.

 

위에서 Retrofit은 Type-safe하다는 것을 언급했습니다. Json 타입으로 받은 응답을 우리가 원하는 클래스의 타입으로 변환하여 반환하는 것까지가 Retrofit의 역할입니다. 이때 타입이 정확하지 않다면 오류가 발생하게 됩니다.

 

OkHttp Interceptors

OkHttp는 HTTP 통신 라이브러리입니다.

  • 데이터 요청 시 흐름 : Application -> OkHttp -> Server
  • 데이터 응답 시 흐름 : Server -> OkHttp -> Application

네트워크 요청/응답 시에 OkHttp라는 라이브러리를 거쳐서 HTTP 통신이 진행됩니다.

 

여기서 OkHttpInterceptor는 중간에서 네트워크를 가로채는 것을 말합니다.

네트워크 내용을 가로채서 monitor, rewrite, and retry calls을 할 때 사용됩니다.

OkHttp - Interceptors Introductions

 

OPEN API Response 분석하기

위 그림은 PostMan을 이용해서 한국환경공단의 OPEN API 응답을 출력한 결과입니다. 한국환경공단을 비롯해 많은 정부 OPEN API들은 위와 같은 고정된 응답형태를 사용합니다.

 


데이터 형태를 그림으로 표현하면 오른쪽과 같은 중첩형태를 가지고 있습니다. 여기서 우리가 사용하고 싶은 데이터노란색으로 표현된 Items 부분입니다.

 

네트워크의 비동기 호출에도 이미 복잡한 함수들의 호출이 사용되는데 중첩된 자료를 열어보려면 추가적으로 많은 boilerplate 코드들을 작성해야합니다.

fun networkExample() {
    val response = apiService.getForecast(date)
    if (response.isSuccessful) {
        if (response.body()?.data != null) {
            // 사용할 데이터가 존재하는 경우
        } else {
            // 네트워크 실패 - 응답은 성공, 바디에 내용은 없는 경우
        }
    } else {
        // 네트워크 실패 - 응답에서 실패
    }
}

이와 같은 지저분한 if문을 네트워크 요청이 있을 때마다 작성해야합니다.

 

인터셉터를 사용해서 필요한 응답만 걸러내기

앞서 설명했듯이 한국환경공단의 네트워크 응답은 항상 같은 응답 형식을 가지고 있습니다. 그리고 우리가 필요한 데이터에 접근하려면 많은 포장지를 벗겨내야 합니다.

 

그래서 네트워크 응답 사이에 인터셉터를 추가하여 응답 데이터를 조작하고 필요한 데이터만 추출하는 과정을 추가합니다.

class AirKoreaResponseFilteringInterceptor: Interceptor {

    override fun intercept(chain. Interceptor.Chain): Response {
        // 기존의 요청을 확인합니다.
        val request = chain.request()

        try {
            val response = chain.proceed(request)
            if (!response.isSuccessful) {
                throw IOException("Network not successful")
            }

            val jsonString = response.body?.string()
            val bodyFiltered = filteringData(jsonString)

            return response.newBuilder()
                .message(response.message)
                .body(bodyFiltered.toResponseBody())
                .build()
        } catch (ie: IOException) {
            // 네트워크가 실패하거나 타입이 일치하지 않아 변환에 실패한 경우
        }
    }

    private fun filteringData(input: String) : String {
        // 데이터를 필터링하여 반환합니다.
        // 1. 입력된 JsonString을 다시 객체화 시키는 과정
        val typeToken = object: TypeToken<GeneralResponse<*>>() {}.type
        val result: GeneralResponse<*> = gson.fromJson(input, typeToken)

        // 응답에서 header 값 확인하기
        val header = result.response.header

        if (header.resultCode != "00") 
            throw IOException("Network Failed")

        // 2. 필요한 부분만 꺼내서 다시 JsonString으로 만드는 과정
        val body = result.response.body
        val contents = gson.toJson(body.items)    // Items만 꺼낸다.

        return contents
    }
}

위 인터셉터에서 하는 일은

  • Appliation에게 최종응답을 전달하기 전에 응답을 가로챕니다.
  • 응답의 JsonString에서 필요한 데이터만 필터링합니다.
  • 그리고 다시 Response로 포장해서 Application에게 전달합니다.

앱에서는 인터셉터가 적용된 클라이언트를 사용하면 필터링 된 결과물을 받을 수 있습니다.

인터셉터 적용하기

사용하고자 하는 네트워크 API Service에 위에서 만든 인터셉터를 적용해줍니다.

interface AirKoreaApi {
    // 한국환경공단에 예보정보를 요청하는 메소드
    @GET("apiservice/Forecast")
    suspend fun getForecast(date: String) : Response<List<Forecast>>
}

위와 같이 네트워크를 인터페이스에 정의하고 "Retrofit.Builder()"를 통해서 네트워크 동작을 구현하면 HTTP 네트워크를 편리하게 구현할 수 있습니다.

 

그리고 위에서 등록한 AirKoreaResponseFilteringInterceptor를 거쳐서 필요한 데이터만 가진 응답을 반환할 것입니다.

// 인터셉터 적용 후 네트워크 응답 받기
fun networkExample() {
    val response = apiService.getForecast(date)
    if (response.isSuccessful) {
        val forecast: List<Forecast> = response.body()    // Items만 필터링한 body의 결과물
    }
}

 

참고자료