Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrofit + RxJava fails to cache responses, suspected response headers

I'm trying to configure cache with Retrofit 1.9.0 and OkHtttp 2.5.0.

Here is how I provide OkHttpClient for my RestAdapter:

@Provides
@Singleton
public OkHttpClient provideOkHttpClient() {
    OkHttpClient okHttpClient = new OkHttpClient();
    okHttpClient.setConnectTimeout(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
    okHttpClient.setReadTimeout(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
    okHttpClient.setWriteTimeout(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);

    File cacheDir = new File(context.getCacheDir(), "http");
    final Cache cache = new Cache(cacheDir, DISK_CACHE_SIZE_IN_BYTES);
    okHttpClient.setCache(cache);

    okHttpClient.interceptors().add(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {

            Response response = chain.proceed(request);
            Response finalResponse = response.newBuilder()
                    .header("Cache-Control", String.format("public, max-stale=%d", 604800))
                    .build();

            Log.d("OkHttp", finalResponse.toString());
            Log.d("OkHttp Headers", finalResponse.headers().toString());
            return finalResponse;
        }
    });

    return okHttpClient;
}

I did not forget to setClient on RestAdapter.Builder. Also made sure, that I'm actually using instance of RestAdapter with this client set.

Even checked if the files are created under "http" folder. They are.

However after I turn of WIFI and reload my screen I end up in OnError callback of Observable endpoint with this message:

retrofit.RetrofitError: failed to connect to /10.40.31.12 (port 8888) after 10000ms: connect failed: ENETUNREACH (Network is unreachable)

DISCLAIMER: I should probably mention that the final Observable is combined from 5 others, with flatMap and zip on the way.

like image 346
Jacek Kwiecień Avatar asked Sep 16 '15 12:09

Jacek Kwiecień


2 Answers

I think I have an answer. Short one is: "Cannot be done if server sends no-cache header in response".

If you want the longer one, details are below.

I've made a sample app comparing 2 backends. Lets call them Backend A, and Backend B. A was giving me troubles so I've decided to check on B.

A returns CacheControl = "no-cache, no-transform, max-age=0"

B returns Cache-Control = „public" response header

I did the same setup for both backends, just different urls.

    private void buildApi() {
        Gson gson = new GsonBuilder().create();

        OkHttpClient okHttpClient = new OkHttpClient();
        okHttpClient.setConnectTimeout(10, TimeUnit.SECONDS);

        File cacheDir = new File(getCacheDir(), "http");
        final Cache cache = new Cache(cacheDir, 1000000 * 10);
        okHttpClient.setCache(cache);

        okHttpClient.interceptors().add(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                Log.d("OkHttp REQUEST", request.toString());
                Log.d("OkHttp REQUEST Headers", request.headers().toString());

                Response response = chain.proceed(request);
                response = response.newBuilder()
                        .header("Cache-Control", String.format("public, max-age=%d, max-stale=%d", 60, RESPONSE_CACHE_LIFESPAN_IN_SECONDS))
                        .build();

                Log.d("OkHttp RESPONSE", response.toString());
                Log.d("OkHttp RESPONSE Headers", response.headers().toString());
                return response;
            }
        });

        RestAdapter.Builder builder = new RestAdapter.Builder()
                .setConverter(new StringGsonConverter(gson))
                .setClient(new OkClient(okHttpClient))
                .setRequestInterceptor(new RequestInterceptor() {
                    @Override
                    public void intercept(RequestFacade request) {

                        if (isNetworkAvailable()) {
                            request.addHeader("Cache-Control", "public, max-age=" + 60);
                        } else {
                            request.addHeader("Cache-Control", "public, only-if-cached, max-stale=" + RESPONSE_CACHE_LIFESPAN_IN_SECONDS);
                        }
                    }
                });

        builder.setEndpoint("http://this.is.under.vpn.so.wont.work.anyway/api");
        A_API = builder.build().create(AApi.class);

        builder.setEndpoint("http://collector-prod-server.elasticbeanstalk.com/api");
        B_API = builder.build().create(BApi.class);
    }

Did both calls, then disabled wifi. Cache worked fine for B, but A thrown 504 Unsatisfiable Request (only-if-cached)

It seems that overwritting headers won't help in that case.

like image 148
Jacek Kwiecień Avatar answered Oct 12 '22 06:10

Jacek Kwiecień


You should rewrite your Request instead of the Response. For reference, see the docs on rewriting requests. Note you, can also use the CacheControl class instead of building your own header if you want. Your interceptor should look something like --

okHttpClient.interceptors().add(new Interceptor() {
  @Override
  public Response intercept(Chain chain) throws IOException {

    Request request = chain.request();
    Request cachedRequest = request.newBuilder()
        .cacheControl(new CacheControl.Builder()
            .maxStale(7, TimeUnit.DAYS)
            .build())
        .build();

    return chain.proceed(cachedRequest);
  }
});
like image 41
iagreen Avatar answered Oct 12 '22 06:10

iagreen