OkHttp Interceptors with Retrofit


OkHttp Interceptors with Retrofit
Interceptors 是一個簡單的攔截器,可以方便監控、重寫 request 也可以針對 response 進行加工
例如我們需要看 okhttp 的 log 我們最簡單就是使用 HttpLoggingInterceptor
https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY

val client = new OkHttpClient.Builder()
    .addInterceptor(httpLoggingInterceptor)
    .build()

val retrofit = new Retrofit.Builder()
    .client(client)
    .baseUrl(BASE_URL)
    .build()
這樣我們跑起來就可以在 logcat 上面看到我們丟出去的 request 和 response

在 square 給的官方文件有一張圖,可以看到 Interceptors 其實是在夾在 request 與 response 中間的一個 interface

source: https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor

那問題來了我們如何自訂義一個 Interceptor 呢?
官方給的 sample code 翻成 kotlin 如下
class LoggingInterceptor: Interceptor {

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

        val t1 = System.nanoTime()
        logger.info(String.format("Sending request %s on %s%n%s",
        request.url(), chain.connection(), request.headers()))
        
        response = chain.proceed(request)

        val t2 = System.nanoTime()
        logger.info(String.format("Received response for %s in %.1fms%n%s",
        response.request().url(), (t2 - t1) / 1e6d, response.headers()))

        return response
    }
}
可以看到 Interceptor 核心其實就是 intercept 這個 fun 而已,簡單明瞭
當 intercept 被 call 時此時 request 並沒有被真正的提出去,我們可以用 chain.request() 取得當前的 request
如果我們需要真正將 request 丟出並取得 response 可以使用 chain.proceed(request) 將 request 跑完變成 response
因此我們可以針對 request 或者 response 進行一些加工

實際上 Interceptor 是真的滿實用的,例如很多 api 都會採用 OAuth2 那邊不了就會遇到 token 會過期的問題,我們可以大概簡單寫一下
class InvalidTokenInterceptor: Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val response = chain.proceed(request)
        // check request has auth token
        if (response.request.header("Authorization") == null) {
            return response
        }
        // check response token invalid
        if (!response.isSuccessful && response.code == 401) {
            // token invalid
            val newToken = refreshToken() ?: return response
            // retry api
            response.close()
            var requestBuilder = request.newBuilder()
            if (request.header("Authorization") != null) {
                requestBuilder = requestBuilder
                    .removeHeader("Authorization")
                    .addHeader("Authorization", "Bearer $newToken")
            }
            val newRequest = requestBuilder.build()
            return chain.proceed(newRequest)
        }
        return response
    }

    fun refreshToken(): String? {
        val call = service.refreshToken()
        val response = call.execute()
        if (response.isSuccessful && response.body() != null) {
            return userEntity.accessToken
        }
        return null
    }
}

可以看到在 intercept 中我先是將 request 跑完取得一個 response,然後判斷這個 request 的 header 有沒有 Authorization, 如果沒有那就直接回傳當前的 response 就好結束,若 header有 Authorization 那我就進行判斷,在假設 token invalid 的狀況下我可以先 refresh token 然後重新用舊的 request 產生一個新的 request build 再將剛剛取得的 token 重新塞入 header,最後再將新的 request 跑完變成新的 response 回傳完成這一套自動 refresh token 的機制

上面範例 intercept 中我們可以將動作拆解成以下七個步驟
1. 先 chain.proceed(request) 跑完取得一個 response
2. 然後判斷這個 request 的 header 有沒有 Authorization,, 如果沒有那就直接 return 當前的 response 就好結束
3. 若 header有 Authorization 那我就進行判斷,當前 token 是否 invalid,若否則也直接 return 當前的 response 就好
4. 在 token invalid 的狀況下我們可以先丟一次 refresh token api 已取得新的 token 字串
5. 然後重新用舊的 request 產生一個新的 request build
6. 再將剛剛取得的新 token 字串重新塞入 header
7. 最後再將新的 request 跑完變成新的 response 回傳
就醬感謝各位看官

WRITTEN BY
Aki

熱愛寫code的開發者,專注於 Android 手機 Native App 開發,對於 IOS 也有涉略。閒暇之餘也學習 JavaScript 等前端框架