question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

OkHttp's cache doesn't save multiple response variants for the same target

See original GitHub issue

OkHttp’s Cache seems to replace an existing response entry if another comes from the same target but with different values for request’s Vary fields. For example consider this test case:

@Test void vary() throws Exception {
  var server = new MockWebServer();
  server.setDispatcher(new Dispatcher() {
    @Override public MockResponse dispatch(RecordedRequest recordedRequest) {
      var baseResponse = new MockResponse()
          .addHeader("Cache-Control", "max-age=69420")
          .addHeader("Vary", "Accept-Encoding");
      var body = "ZIP ME!";

      var encoding = recordedRequest.getHeader("Accept-Encoding");
      if ("gzip".equalsIgnoreCase(encoding)) {
        return baseResponse.addHeader("Content-Encoding", "gzip")
            .setBody(ZipUtils.gzip(body));
      } else if ("deflate".equalsIgnoreCase(encoding)) {
        return baseResponse.addHeader("Content-Encoding", "deflate")
            .setBody(ZipUtils.deflate(body));
      } else {
        return baseResponse.setBody(body);
      }
    }
  });
  server.start(8080);

  var client = new OkHttpClient.Builder()
      .cache(new Cache(Files.createTempDirectory("okhttp-cache").toFile(), 10 * 1024))
      .build();

  var gzipRequest = new Request.Builder()
      .url(server.url("/"))
      .header("Accept-Encoding", "gzip")
      .build();
  try (var response = client.newCall(gzipRequest).execute()) {
    assertEquals("ZIP ME!", ZipUtils.gunzip(response.body().bytes()));
    assertEquals(1, client.cache().networkCount());
    assertEquals(0, client.cache().hitCount());
  }

  // gzip response gets replaced as both requests have same key using by the cache (URL)
  var deflateRequest = new Request.Builder()
      .url(server.url("/"))
      .header("Accept-Encoding", "deflate")
      .build();
  try (var response = client.newCall(deflateRequest).execute()) {
    assertEquals("ZIP ME!", ZipUtils.inflate(response.body().bytes()));
    assertEquals(2, client.cache().networkCount());
    assertEquals(0, client.cache().hitCount());
  }

  try (var response = client.newCall(gzipRequest).execute()) {
    assertEquals("ZIP ME!", ZipUtils.gunzip(response.body().bytes()));
    // Expected: use previously saved response
    assertEquals(2, client.cache().networkCount()); // fails: expected 2 but was 3
    assertEquals(1, client.cache().hitCount());     // fails: expected 1 but was 0
  }
}

I don’t know if this is intentional or not but if I understand correctly, a cache is allowed to have multiple response entries per cache key, each with a secondary key represented by request’s selecting headers values (last paragraph of RFC 7234 sec 2). If this is indeed intentional, it’d be much appreciated to know reasons why.

Thanks in advance & great library!

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:2
  • Comments:11 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
swankjessecommented, Aug 6, 2020

Its because if we have a variant mismatch we treat it like a stale cache entry. The old variant is not used and the new one will clobber it.

1reaction
yschimkecommented, Aug 6, 2020

I suspect anyone who hit this probably found it quicker to append a fake “?variant=XML” on the URL than explain the bug and request a fix. It seems like a useful feature if done right, there might even be broader issues here like cache pollution we aren’t handling?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Cache - OkHttp - Square Open Source
Caches HTTP and HTTPS responses to the filesystem so they may be reused, saving time and bandwidth. Cache Optimization¶. To measure cache effectiveness,...
Read more >
A complete guide to OkHttp - LogRocket Blog
In this guide, you can learn the basics of OkHttp and how to build an imaginary to-do list application for Android with it....
Read more >
How to handle RESTful web Services using Retrofit, OkHttp ...
We are going to combine multiple libraries at once to get a working result. I am not going to talk about the native...
Read more >
Android Networking I: OkHttp, Volley and Gson - Medium
Reading how many things OkHttp does it's a good way to understand how hard is to do networking: Connection pooling, gziping, caching, recovers...
Read more >
Can Retrofit with OKHttp use cache data when offline
1) Create Interceptor: private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() { @Override public Response intercept(Chain chain) ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found