본문 바로가기
Android/Retrofit

[Android] OkHttp Interceptor 정리

by 태크민 2025. 3. 6.

okHttpInterceptor란?

OkHttp에서 제공하는 Interceptor는 요청 및 응답을 가로채고 조작할 수 있는 기능을 제공합니다.

 

주로 두 가지 종류의 인터셉터가 있습니다.

  • Application Interceptor
  • Network Interceptor. 


Application Interceptor

애플리케이션 수준의 요청과 응답을 처리하는 인터셉터입니다. 네트워크 요청이 실제로 전송되기 전에 가로채고 수정할 수 있습니다.

  • 애플리케이션 수준에서 요청/응답을 가로챔.
  • 오직 한 번만 실행됨 (네트워크 요청이 실패해도 다시 실행되지 않음).

 

1. 헤더 수정 및 추가: 요청 헤더를 추가하거나 수정할 수 있습니다

class CommonHeaderInterceptor() :Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()

        // 요청 헤더 추가
        val modifiedRequest = originalRequest.newBuilder()
            .addHeader("Authorization", "Bearer my-secret-token")
            .addHeader("User-Agent", "MyApp Android")
            .build()

        println("🚀 Application Interceptor - Request: ${modifiedRequest.url}")

        return chain.proceed(modifiedRequest)
    }
}

val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(CommonHeaderInterceptor()) // Application Interceptor 추가
    .build()

 

2. accessToken 갱신: 요청을 보내기 전에 유효한 token을 확인하고, 응답에서 인증 오류를 처리하여 token을 갱신한 후 요청을 재시도할 수 있습니다.

class AuthInterceptor(private val tokenManager: TokenManager) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()
        
        // 현재 Access Token 가져오기
        val accessToken = tokenManager.getAccessToken()

        // 요청에 Access Token 추가
        request = request.newBuilder()
            .addHeader("Authorization", "Bearer $accessToken")
            .build()

        val response = chain.proceed(request)

        // 401 Unauthorized 발생하면 Access Token 갱신 후 재시도
        if (response.code == 401) {
            synchronized(this) {
                val newAccessToken = tokenManager.refreshAccessToken()
                if (newAccessToken != null) {
                    tokenManager.saveAccessToken(newAccessToken)

                    // 새로운 Access Token을 포함하여 요청 재시도
                    val newRequest = request.newBuilder()
                        .addHeader("Authorization", "Bearer $newAccessToken")
                        .build()
                    return chain.proceed(newRequest)
                }
            }
        }

        return response
    }
}

val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(AuthInterceptor(tokenManager)) // Application Interceptor 추가
    .build()

 

3. 로컬 캐시 적용: 네트워크가 이용 불가할 때, 네트워크 요청 없이 캐시된 데이터를 반환할 수 있습니다.

class OfflineCacheInterceptor(private val context: Context) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()

        if (!isNetworkAvailable(context)) { // 네트워크 연결 확인
            val maxStale = 60 * 60 * 24 * 7 // 7일 (604800초)
            request = request.newBuilder()
                .header("Cache-Control", "public, only-if-cached, max-stale=$maxStale")
                .build()
        }

        return chain.proceed(request)
    }

    // 네트워크 연결 여부 확인 함수
    private fun isNetworkAvailable(context: Context): Boolean {
        val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val activeNetwork = connectivityManager.activeNetworkInfo
        return activeNetwork != null && activeNetwork.isConnected
    }
}

val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(OfflineCacheInterceptor(tokenManager)) // Application Interceptor 추가
    .build()
  • 네트워크 연결 확인
    • isNetworkAvailable(context) 함수를 사용하여 네트워크 연결 여부를 확인
    • 네트워크가 없다면(Offline 모드), 기존 캐시된 응답을 사용하도록 요청을 변경
  • 오프라인 상태에서 캐시된 응답 사용
    • only-if-cached: 서버로 요청을 보내지 않고 로컬 캐시에서만 데이터를 찾음.
    • max-stale=604800 (7일): 캐시된 데이터가 7일까지 오래되어도 허용.

4. 요청 재시도: 요청 실패 시 지정된 횟수만큼 재시도할 수 있습니다.

class RetryInterceptor(private val maxRetry: Int = 3) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        var response: Response? = null
        var retryCount = 0

        while (response == null && retryCount < maxRetry) {
            try {
                response = chain.proceed(request)
            } catch (e: IOException) {
                retryCount++
                if (retryCount == maxRetry) {
                    throw e
                }
            }
        }

        return response!!
    }
}

 


Network Interceptor

네트워크 수준에서 요청과 응답을 처리하는 인터셉터입니다. 요청이 서버에 도달하고 응답이 클라이언트에 도달하기 전후에 가로챌 수 있습니다.

  • 네트워크 요청을 보내기 직전에 실행됨.
  • 실제 네트워크 요청이 수행될 때마다 실행됨 (리트라이 요청이 있으면 다시 실행됨).
  • 원격 서버에서 반환된 응답을 가로채고 헤더를 추가할 수 있음.

1. 응답 캐시: 실제 네트워크 요청이 서버에 전송된 후 응답을 받기 때문에, 서버에서 제공하는 응답의 캐시 정책(Cache-Control)을 가로채고 수정하여 클라이언트가 응답을 얼마나 오랫동안 캐시해야 하는지를 조정할 수 있습니다. 이를 통해 애플리케이션의 성능을 최적화할 수 있습니다.

class CacheInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val response = chain.proceed(chain.request())

        // 응답에 캐시 헤더 추가
        return response.newBuilder()
            .header("Cache-Control", "public, max-age=3600") // 1시간 동안 캐시 유지
            .build()
    }
}

val cacheSize = (10 * 1024 * 1024).toLong() // 10MB 캐시 저장
val cache = Cache(File(context.cacheDir, "http_cache"), cacheSize)
val okHttpClient = OkHttpClient.Builder()
    .cache(cache) // 캐시 적용
    .addNetworkInterceptor(CacheInterceptor()) // 응답 캐시 적용
    .build()

 

2. 로깅: 네트워크 요청 및 응답에 대한 로깅을 수행하여 디버깅을 용이하게 합니다. 예를 들어, 성공적인 응답이나 오류에 대한 정보를 기록할 수 있습니다.

class LoggingInterceptor : Interceptor {
    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()

        println("Sending request: ${request.url} on ${chain.connection()} with headers ${request.headers}")

        val response = chain.proceed(request)

        println("Received response for ${response.request.url} with headers ${response.headers}")

        return response
    }
}
val okHttpClient = OkHttpClient.Builder()
    .addNetworkInterceptor(LoggingInterceptor()) // Application Interceptor 추가
    .build()

 

3. 네트워크 성능 분석: 요청을 보내고 응답을 받을 때 까지 걸린 시간 측정

class NetworkSpeedInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val startTime = System.nanoTime()

        val response = chain.proceed(request)

        val endTime = System.nanoTime()
        val durationMs = (endTime - startTime) / 1e6 // 밀리초 변환

        println("🌐 Network Request: ${request.url} took ${durationMs}ms")

        return response
    }
}

val okHttpClient = OkHttpClient.Builder()
    .addNetworkInterceptor(NetworkSpeedInterceptor()) // Application Interceptor 추가
    .build()

 

 

끝.


참고자료

https://velog.io/@heetaeheo/OkHttp-Interceptors

 

OkHttp - Interceptors

오늘은 Android에서 OkHttp - Interceptors에 대해 알아보는 글을 작성하려고 합니다. Interceptors는 네트워크 요청과 응답을 관리하고 조작하는 역할을 합니다. OkHttp Interceptors 란? Interceptors는 OkHttp의 핵심

velog.io

https://velog.io/@ows3090/Android-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-OkHttp-Interceptor-%ED%99%9C%EC%9A%A9

 

[Android] 프로젝트로 배우는 OkHttp Interceptor 활용

대부분 프로젝트를 진행하면 서버에서 사용자 인증을 JWT 사용하여 처리하는 경우가 많을 것 같습니다. 서버에서 전달해준 Token을 받게 되면 로컬 저장소에 저장한 후 API 요청 시에 HTTP Header에 추

velog.io