OkHttpとは
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
でキャッシュをどう扱うかを指定します。
expires_in(1.hour, public: true) # => Cache-Control: max-age=3600, public
expires_now # => Cache-Control: no-cache
これらを受け取った場合にOkHttpがどういう挙動をするかを見てみます。
HttpEngine というクラスがOkHttpのコアになっています。Cache-Controlヘッダはparseされて CacheControl クラスになり、CacheStrategy というクラスから参照されて、実際にどのようにキャッシュするかが決められます。
OkHttpではリクエストのurlをキーにして、ハッシュでレスポンスをキャッシュしています。キャッシュすると判定されたレスポンスはメモリとファイルにLRUで書き込まれます。細かい条件はありますが、大まかには
- GETメソッド
- リクエストとレスポンスが共にno-storeでない
- max-ageが指定されている
上記の場合でキャッシュが保存/利用されます。また、GET以外の操作が行われた場合にはキャッシュを破棄するということをしています。
リクエスト
キャッシュを有効にするには OkHttpClient#setCache
メソッドに Cache クラスを渡します。そのときに書き込み先やキャッシュサイズの上限を指定することができます。
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を使わないで独自のキャッシュレイヤーを実装した方が良い?うーむ。