본문 바로가기
Android

[Android] OkHttp Interceptor로 서버 Response 변형하기

by 너츠너츠 2022. 12. 22.

문제 배경

대부분 어플리케이션을 만들 때 서버와 API통신을 하며 json을 주고받고 있습니다. 클라이언트와 서버 사이의 API 데이터에는 서버 통신이 잘 되었는지, 응답이 잘 갔는지, 잘못된 형태로 보내진 건 아닌지에 대해 확인하기 위해 resultCode, message, data와 같은 형식으로 제공되며 data는 정말 다양한 형식으로 올 수 있습니다.

 

 

예전의 저는 api 데이터를 받기 위해 항상 아래와 같은 data class를 매번 생성해주곤 했습니다. 

data class ServerResponse {
   val resultCode: String,
   val message: String,
   val data: ServerData
}

data class ServerData {
   val id: Long,
   val dataName: String,
   val dataType: String
}

 

이런 코드를 수정하기 위해 저는 Generic을 사용하여 매번 response를 생성하는 부분을 해결하였지만

Service 내에서 내부 Generic을 선언해주기 위해 <>가 늘어나는 것을 볼 수 있었습니다.

data class ServerResponse<T> {
   val resultCode: String,
   val message: String,
   val data: T
}

interface NetworkService {
   
   @GET("/test")
   fun getData(): Call<ServerResponse<ServerData>>
}

"OkHttp에서 Interceptor는 Call들을 모니터링하고 다시 쓰고 재시도할 수 있는 강력한 메커니즘입니다. 여기 발신 요청과 수신 응답을 기록하는 간단한 인터셉트가 있습니다." 라고 설명해주고 있으며 아래 그림과 같은 형태로 동작하고 있습니다. 저는 <>가 늘어나는 과정을 최소화하기 위해 Interceptor를 설계해봤습니다.

 

OkHttp 동작과정

 

Interceptor 설계 (해당 코드들은 아래 Github 링크에 있습니다)

1. retrofit builder에 client를 추가해준다.

object RetrofitClient {
    private const val baseUrl = "http://10.0.2.2:3001"

    private val client: OkHttpClient = OkHttpClient.Builder()
        .addNetworkInterceptor(CommonNetworkInterceptor())
        .build()

    private val retrofit = Retrofit.Builder()
        .baseUrl(baseUrl)
        .client(client)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    // ... 추가 내용 생략
}

 

2. CommonNetworkInterceptor 생성

class CommonNetworkInterceptor : Interceptor {
   override fun intercept(chain: Interceptor.Chain): Response {
      // ...
   }
}

 

3. AccessToken를 헤더에 붙여주기

var request = chain.request()

/**
* 1) Common Header with API Access Token
*    API Access Token을 헤더에 추가하는 역할을 합니다
*/
val accessToken = MyApplication.spUtil.getString(ACCESS_TOKEN, null)
accessToken?.let {
    request = request.newBuilder()
          .addHeader(headerTokenKey, accessToken).build()
}

 

4. 해당 request를 통해 response 받기

/**
 * 2) General Response from Server (Unwrapping data)
 */
  val response = chain.proceed(request)

 

5. json의 형태가 resultCode, message, data일 때와 아닐 때를 나눠서 구분해줍니다

/**
* 3) Parse body to json,
* if body is in the form of [ResponseWrapper], save the message and result separately
*/
val responseJson = response.extractResponseJson()
val message = if (responseJson.has(messageKey)) responseJson[messageKey].toString() else ""
val dataPayload = if (responseJson.has(dataKey)) responseJson[dataKey] else responseJson


} // intercept func end


private fun Response.extractResponseJson(): JSONObject {
   val jsonString: String = this.body?.string() ?: "{}"
   return try {
        JSONObject(jsonString)
   } catch(exception: Exception) {
        Log.d("UnboxingInterceptor", "서버 응답이 json이 아님: $jsonString")
        throw java.lang.Exception()
   }
}

 

6. 해당 json을 다시 조합해서 return 해줍니다

return response.newBuilder()
         .message(message)
         .body(dataPayload.toString().toResponseBody())
         .build()

 

 

<예시 코드>

 

GitHub - JGeun/Custom-OkHttp-Interceptor: Transform into the response you want with Android Okhttp Interceptor

Transform into the response you want with Android Okhttp Interceptor - GitHub - JGeun/Custom-OkHttp-Interceptor: Transform into the response you want with Android Okhttp Interceptor

github.com

 

<참고자료>

https://medium.com/mj-studio/%EC%84%9C%EB%B2%84-%EC%9D%91%EB%8B%B5-cherry-pick-okhttp-interceptor-eaafa476dc4d

 

https://modelmaker.tistory.com/entry/Android-Okhttp-Interceptor%EB%A1%9C-%EC%9B%90%ED%95%98%EB%8A%94-%EC%9D%91%EB%8B%B5%EC%9C%BC%EB%A1%9C-%EB%B3%80%ED%98%95%ED%95%98%EA%B8%B0%EC%99%80-Interceptor-Test

반응형

댓글