클라이언트가 서버에게 작업을 요청하면 서버는 그에 맞는 응답을 돌려준다. 이때 클라이언트가 요청한 작업은 정상적으로 처리될 수도, 다양한 이유로 인해 실패할 수 있다. 이 응답을 크게 5가지로 분류할 수 있다.

  • 성공했음을 알려주는 응답
  • 성공했고, 요청했던 데이터가 담겨있는 응답
  • 리다이렉트
  • 클라이언트 에러
  • 서버 에러

이러한 상태에 맞는 코드를 응답으로 내려줌으로써, 클라이언트는 상태 코드에 따라 적절한 처리를 수행할 수 있다. 가장 일반적으로 따르게 되는 상태 코드 표준은 다음과 같다.

  • 200번대 : 응답 성공
  • 400번대 : 잘못된 요청
  • 500번대 : 서버에 문제가 생김
  • 그 외 다양한 코드 정보는 링크에서 확인이 가능하다.

API 별로 응답 코드 처리를 다르게 해야하는 경우도 있지만, 500번대의 코드는 모든 API에 일괄적으로 적용해야하는 로직이 존재하는 경우가 흔하다. (서비스 장애 안내 페이지 띄우기, 점검 중 메세지 띄우고 앱 종료시키기 등)

Retrofit을 사용해 서버와 통신할 때, 상태코드 처리를 일괄 처리하는 방법은 무엇일까?

응답을 처리하는 함수 만들기

1) 상태 코드를 인자로 받는 함수 만들기

fun CoroutineScope.handleResponseCode(responseCode: Int) =
  launch {
      when (responseCode) {
          in 400..499 -> {
              // 400번대 실패 처리
          }
          in 500..599 -> {
              // 500번대 실패 처리
          }
          else -> {

          }
      }
  }

그리고 서버와 통신후 응답이 돌아오는 모든 코드에서 이 함수를 사용한다.

launch {
	val response = service.getMyInfo()
	dealResponseCode(response.code())
}

2) api call 자체를 인자로 받는 함수를 만들기

  suspend fun <T> safeApiCall(
      apiCall: Response<T>,
      dispatcher: CoroutineDispatcher = Dispatchers.IO,
  ): Result<T?> {
      return withContext(dispatcher) {
          try {
              val result = apiCall

              if (result.isSuccessful) {
                  return@withContext Result.success(result.body())
              }
              when (result.code()){
                  in 400..499 -> {

                  }
                  in 500..599 -> {

                  }
              }
              Result.failure(Throwable(result.message()))
          } catch (throwable: Throwable) {
              Result.failure(throwable)
          }
      }
  }

요청할 call을 인자로 넣고, 응답으로 받은 Result에 isSuccess, isFailuse, getOrNull() 등 Result의 프로퍼티나 메서드를 이용해 추가적인 처리를 할수 있다.

val result = safeApiCall(service.getInfo())

장점

  • 응답을 처리하는 부분의 코드를 봤을 때 응답 코드를 처리하는 로직이 적용되어 있다는 것을 직관적으로 알 수 있다.
  • 특정 API에서 예외적인 로직을 적용할 수 있다.

단점

  • 실수로 함수 적용을 빼먹으면 테스트 해보기 전까지는 공통 처리가 안되어있다는 사실을 알 수 없다.
  • 모든 응답마다 이 함수를 1회 더 호출해야 한다면 이 또한 중복 코드에 해당된다.

OkHttpClient에 Interceptor 적용

스택오버플로우나 블로그를 찾아보면 대다수가 답으로 제시하는 방법이다. Okhttp는 실제 서버 통신이 일어나기 직전, 직후에 요청과 응답을 가로채서 특정 작업을 한 후에 다시 원래 흐름으로 돌려놓는 Interceptor 기능을 제공한다.


Retrofit 객체를 생성할 때 client() 메서드로 Interceptor가 설정된 OkHttpClient를 추가하면 된다.

val client = OkHttpClient.Builder()
    .addInterceptor {
            val request = it.request().newBuilder().build()
            it.proceed(request).apply {
                when (code) {
                    // 응답 코드 처리
                }
            }
        }
    .build()

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

장점

  • 중복코드 없이 모든 응답에 일괄 처리가 가능하다.

단점

  • 모든 요청과 응답에 일괄 적용되기 때문에, 특정 1~2개의 API에서만 다른 동작을 하게 만들고 싶다면 사용할 수 없다.
  • 해당 코드는 Network 관련 객체를 생성하는 역할을 맡고 있는데, 만약 일괄적으로 적용하려는 로직이 Android Context를 필요로 한다면 단일 책임 원칙을 어기게 된다.


정답은 없는 문제라 더 좋은 방법을 알고 있다면 댓글로 나에게 알려주길(?) 바란다.