OkHttpとCache-Control

# OkHttpとは

[OkHttp](http://square.github.io/okhttp/) はSquareが開発しているHTTPクライアントのライブラリで、HTTP、HTTPS、HTTP/2、SPDY、WebSocketに対応していて、同期/非同期のAPIを提供しています。
OkHttpはデフォルトでGZIPに対応していたり、コネクションプールやキャッシュの制御をしたり、シンプルで効率良く通信できることを売りにしています。また、HttpURLConnectionやApache HttpClientからマイグレーションしやすいように、それらと同じインタフェースを提供するモジュールも用意されています。

今回はOkHttpとCache-Controlについて書きます。

# Cache-Controlとは

HTTP/1.1で定められているリソースをどのようにキャッシュするかの取り決めです。リクエストとレスポンスが取りうる値は以下のようになっています。

```
cache-request-directive =
           "no-cache"
         | "no-store"
         | "max-age" "=" delta-seconds
         | "max-stale" [ "=" delta-seconds ]
         | "min-fresh" "=" delta-seconds
         | "no-transform"
         | "only-if-cached"
         | cache-extension
```

```
cache-response-directive =
           "public"
         | "private" [ "=" <"> 1#field-name <"> ]
         | "no-cache" [ "=" <"> 1#field-name <"> ]
         | "no-store"
         | "no-transform"
         | "must-revalidate"
         | "proxy-revalidate"
         | "max-age" "=" delta-seconds
         | "s-maxage" "=" delta-seconds
         | cache-extension
```

たくさんのディレクティブが出てきましたが、OkHttpではno-cache、no-store、max-age、only-if-cachedが重要な働きをしています。

- no-store
このヘッダは、サーバから返されてくるデータをキャッシュに保存しないという指示です。

- no-cache
キャッシュが保存されている場合でもサーバに対して、現在でもキャッシュが有効か問い合わせる指示です。

- max-age
キャッシュの有効な時間を表します。

- only-if-cached
キャッシュからのみデータを取得するという指示です。

# OkHttpのCache-Controlの振る舞い

## レスポンス

Railsでは `expires_in` や `expires_now` でキャッシュをどう扱うかを指定します。

```ruby
expires_in(1.hour, public: true) # => Cache-Control: max-age=3600, public
expires_now # => Cache-Control: no-cache
```

これらを受け取った場合にOkHttpがどういう挙動をするかを見てみます。

[HttpEngine](https://github.com/square/okhttp/blob/master/okhttp%2Fsrc%2Fmain%2Fjava%2Fcom%2Fsquareup%2Fokhttp%2Finternal%2Fhttp%2FHttpEngine.java) というクラスがOkHttpのコアになっています。Cache-Controlヘッダはparseされて [CacheControl](https://github.com/square/okhttp/blob/master/okhttp%2Fsrc%2Fmain%2Fjava%2Fcom%2Fsquareup%2Fokhttp%2FCacheControl.java) クラスになり、[CacheStrategy](https://github.com/square/okhttp/blob/master/okhttp%2Fsrc%2Fmain%2Fjava%2Fcom%2Fsquareup%2Fokhttp%2Finternal%2Fhttp%2FCacheStrategy.java) というクラスから参照されて、実際にどのようにキャッシュするかが決められます。

OkHttpではリクエストのurlをキーにして、ハッシュでレスポンスをキャッシュしています。キャッシュすると判定されたレスポンスはメモリとファイルにLRUで書き込まれます。細かい条件はありますが、大まかには

- GETメソッド
- リクエストとレスポンスが共にno-storeでない
- max-ageが指定されている

上記の場合でキャッシュが保存/利用されます。また、GET以外の操作が行われた場合にはキャッシュを破棄するということをしています。

## リクエスト

キャッシュを有効にするには `OkHttpClient#setCache` メソッドに [Cache](https://github.com/square/okhttp/blob/master/okhttp%2Fsrc%2Fmain%2Fjava%2Fcom%2Fsquareup%2Fokhttp%2FCache.java) クラスを渡します。そのときに書き込み先やキャッシュサイズの上限を指定することができます。

```java
File cacheDir = new File(context.getCacheDir(), CACHE_FILE_NAME);
Cache cache = new Cache(cacheDir, MAX_CACHE_SIZE);
okHttpClient.setCache(cache);
```

特に何も指定しないと、キャッシュが有効だった場合にはキャッシュのデータを返します。only-if-cachedを指定するとキャッシュしか見に行かなくなるので、オフライン時に指定するとよりバッテリーに優しくなります。
キャッシュを見て欲しくない場合もあると思いますが、その場合にはno-cacheを付けるとネットワークからのみデータを取得するようになります。
`Cache#getRequestCount` や `Cache#getNetworkCount` や `Cache#getHitCount` メソッドがあるので、実際にどれくらいキャッシュを見ているか確認することができます。

# 所感

Cache-Controlはお手軽だけどアプリだとprivateのデータもキャッシュしたいし、結局独自でキャッシュを持たないといけないなくなる気がして、もう一層キャッシュするレイヤーを挟むよりは、Cache-Controlを使わないで独自のキャッシュレイヤーを実装した方が良い？うーむ。