okhttpのCacheControl.Builder.maxStale()
okhttpのCacheControl.Builder.maxStale()の挙動を勘違いしてた。
たとえばこんなコードを書いたとする。
val CACHE_CONTROL = CacheControl.Builder() .maxAge(5, TimeUnit.MINUTES) .maxStale( Integer.MAX_VALUE, TimeUnit.SECONDS) .build() val call = okhttpClient.newCall( Request.Builder() .cacheControl(CACHE_CONTROL) .url(url) )
maxStale()で指定した値がどのように利用されるか追ってみる。
- maxStale()で指定した値は CacheControl.maxStaleSeconds() で参照できる。
- okhttp内部でmaxStaleSeconds()を呼び出しているのはCacheStrategy.Factory.getCandidate() だ。
- getCandidate() は CacheInterceptor.intercept() から CacheStrategy.Factory.get() 経由で呼び出される。
- RealCall.getResponseWithInterceptorChain() が CacheInterceptor を生成する。
CacheStrategy.Factory.getCandidate()は以下の条件が揃うとネットワークアクセスなしでキャッシュレスポンスを返す。
- リクエストのCacheControlのmustRevalidate() が偽
- リクエストのCacheControlのnoCache()が偽
- リクエストのCacheControlのmaxStaleSeconds()が-1ではない
- ageMillis + minFreshMillis < freshMillis + maxStaleMillis
つまり冒頭のコードは、意図せずに「一度キャッシュしたら Integer.MAX_VALUE 秒の間はネットワークアクセスなしでそのキャッシュを使う」という指定になるのだった。
やりたかったことは「ネットワークアクセスを行いエラーになったらキャッシュ済みのレスポンスを利用する」だったのだが、これを行う方法は
- 事前にCacheControl.FORCE_CACHEを使ってキャッシュ済みレスポンス(A)を読んでおく
- maxStale()なしでネットワークアクセスを行う
- ネットワークアクセスに失敗したら(A)を利用する
という手順のようだった。