Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrofit + OkHTTP - response cache not working

I know there has been a lot of similar questions, but I have read them all and none of them really helped.

So, here is my problem:

I am using retrofit + okhttp to fetch some data from API and I'd like to cache them. Unfortunately, I don't have admin access to the API server so I can't modify headers returned by the server. (currently, server returns Cache-control: private)

So I decided to use okhttp header spoofing to insert appropriate cache headers. Sadly, no matter what I do, caching doesn't seem to work.

I initialise the api service like this:

int cacheSize = 10 * 1024 * 1024; // 10 MiB
File cacheFile = new File(context.getCacheDir(), "thumbs");
final Cache cache = new Cache(cacheFile, cacheSize);

OkHttpClient client = new OkHttpClient();
client.setCache(cache);
client.interceptors().add(new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Response originalResponse = chain.proceed(chain.request());
        return originalResponse.newBuilder()
                .removeHeader("Access-Control-Allow-Origin")
                .removeHeader("Vary")
                .removeHeader("Age")
                .removeHeader("Via")
                .removeHeader("C3-Request")
                .removeHeader("C3-Domain")
                .removeHeader("C3-Date")
                .removeHeader("C3-Hostname")
                .removeHeader("C3-Cache-Control")
                .removeHeader("X-Varnish-back")
                .removeHeader("X-Varnish")
                .removeHeader("X-Cache")
                .removeHeader("X-Cache-Hit")
                .removeHeader("X-Varnish-front")
                .removeHeader("Connection")
                .removeHeader("Accept-Ranges")
                .removeHeader("Transfer-Encoding")
                .header("Cache-Control", "public, max-age=60")
              //.header("Expires", "Mon, 27 Apr 2015 08:15:14 GMT")
                .build();
    }
});

RestAdapter restAdapter = new RestAdapter.Builder()
    .setEndpoint(API_ROOT)
    .setLogLevel(RestAdapter.LogLevel.HEADERS_AND_ARGS)
    .setClient(new OkClient(client))
    .setConverter(new SimpleXMLConverter(false))
    .setRequestInterceptor(new RequestInterceptor() {
        @Override
        public void intercept(RequestFacade request) {
            if (Network.isConnected(context)) {
                int maxAge = 60; // read from cache for 2 minutes
                request.addHeader("Cache-Control", "public, max-age=" + maxAge);
            } else {
                int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
                request.addHeader("Cache-Control",
                    "public, only-if-cached, max-stale=" + maxStale);
            }
        }
    })
    .build();
api = restAdapter.create(ApiService.class);

Of course, it's not necessary to remove all these headers, but I wanted to make the response as clean as possible to rule out some interference from these extra headers.

As you can see, I tried to also spoof Expires and Date header (I tried removing them, setting them so that there is exactly max-age differnece between them and also setting Expires far into future). I also experimented with various Cache-control values, but no luck.

I made sure the cacheFile exists, isDirectory and is writeable by the application.

These are the request and response headers as logged directly by retrofit:

Request:
Cache-Control: public, max-age=60
---> END HTTP (no body)

Response:
Date: Mon, 27 Apr 2015 08:41:10 GMT
Server: Apache/2.2.22 (Ubuntu)
Expires: Mon, 27 Apr 2015 08:46:10 GMT
Content-Type: text/xml; charset=UTF-8
OkHttp-Selected-Protocol: http/1.1
OkHttp-Sent-Millis: 1430124070000
OkHttp-Received-Millis: 1430124070040
Cache-Control: public, max-age=60
<--- END HTTP (-1-byte body)
<--- BODY: ...

And, finally one strange incident: At some point, the cache worked for a few minutes. I was getting reasonable hit counts, even offline requests returned cached values. (It happened while using the exact setting posted here) But when I restarted the app, everything was back to "normal" (constant hit count 0).

Co if anyone has any idea what could be the problem here, I'd be really glad for any help :)

like image 942
daemontus Avatar asked Apr 27 '15 08:04

daemontus


People also ask

Which is better retrofit or OkHttp?

OkHttp is a pure HTTP/SPDY client responsible for any low-level network operations, caching, requests and responses manipulation. In contrast, Retrofit is a high-level REST abstraction build on top of OkHttp. Retrofit is strongly coupled with OkHttp and makes intensive use of it.

How does OkHttp cache work?

Basically: The client will send out something like timestamp or Etag of the last request. The server can then check if there is some data has changed in during that period of time or not. If nothing has changed, the server can just give a special code (304 -not modified) without sending the whole same response again.

Does retrofit include OkHttp?

Retrofit is a type-safe REST client for Android, Java and Kotlin developed by Square. The library provides a powerful framework for authenticating and interacting with APIs and sending network requests with OkHttp. See this guide to understand how OkHttp works.

How do I add OkHttp to retrofit?

Solution 1: Sharing Default OkHttp Instance In order to make them share a single OkHttp instance, you can simply pass it explicitly on the builder: OkHttpClient okHttpClient = new OkHttpClient(); Retrofit retrofitApiV1 = new Retrofit. Builder() . baseUrl("https://futurestud.io/v1/") .


1 Answers

Use networkInterceptors() instead of interceptors(). That in combination with your strategy of removing any headers that are somewhat related to caching will work. That's the short answer.

When you use interceptors to change headers it does not make any adjustments before CacheStrategy.isCacheable() is called. It's worthwhile to look at the CacheStrategy and CacheControl classes to see how OKHttp handles cache-related headers. It's also worthwhile to do ctrl+f "cache" on http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

I am not sure if the networkInterceptors() and interceptors() documentation is just unclear or if there is a bug. Once I look into that more, I will update this answer.

like image 127
Brendan Weinstein Avatar answered Oct 13 '22 01:10

Brendan Weinstein