3

We're seeing some behaviour where we aren't caching responses in OkHttp, and end up hitting the server every time. However, the response has an Expires time in the future, so ideally it would be cached.

Here's a simple example of headers we're seeing in the response (request was sent and response was received at Sat, 16 Jan 2021 00:40:36 GMT):

date: Sat, 16 Jan 2021 00:40:36 GMT
age: 6
expires: Sat, 16 Jan 2021 00:40:40 GMT
last-modified: Sat, 16 Jan 2021 00:40:30 GMT

From what I've seen from looking at the CacheStrategy, the problem is that it adds together date + age to see if it's past the expiry time. In this case, 00:40:36 + 6 = 00:40:42 > 00:40:40, so it ends up not being added to the cache.

So I think ideally, either the response date would be equal to last-modified (in this case Sat, 16 Jan 2021 00:40:30 GMT), or we'd need to have a custom CacheStrategy to use last-modified instead of date for these calculations.

If anyone has any insights into whether I'm making any bad assumptions, or if one of the above options is preferable, please let me know. I've looked at some of the specs for date/age headers and it's a bit unclear to me what they should be in this scenario.

I've also found it a bit difficult to debug the caching behaviour in OkHttp, right now I've just been using conditional breakpoints to try to trace it, but if anyone has a better idea I'd appreciate that as well.

Vikram
  • 51,313
  • 11
  • 93
  • 122
jaregier
  • 121
  • 1
  • 3
  • 2
    Have you considered using a network interceptor to add synthetic cache headers to the response? Alternately consider adding a max-stale cache control header to the request. – Jesse Wilson Jan 16 '21 at 18:37
  • Consider including cache logging output. https://square.github.io/okhttp/caching/ https://square.github.io/okhttp/events/ You can show the cache events, or output the types cache objects or relevant raw headers. I'd like to understand if something is missing there that would make it easier to reason about. – Yuri Schimke Jan 17 '21 at 09:37
  • In terms of headers, we aren't sending up any cache control headers in our request, we do send up an etag, but ideally we don't even hit the network if a previous response is still valid. Aside from the 4 headers in my example, the only other relevant one we get back in the response is `cache-control: public, stale-while-revalidate=1, stale-if-error=86400`. – jaregier Jan 18 '21 at 16:48
  • Sending up extra cache control headers in our request wouldn't be ideal; in our case, we actually want different levels of caching throughout the day. So the same endpoint might return responses that are valid for 2 hours, but then later return responses that are valid for only 10 seconds. Ideally this is driven by the server. We've tried doing a network interceptor to remove the `age` header from the response, which seemed to cache the way we expect, but if that's the case, I'm surprised to find little discussion on this topic – jaregier Jan 18 '21 at 16:54
  • For us, this problem arises when we hit cloudflare with our requests. Basically, cloudflare caches a response from the origin server, appends the `age` header in the response, but always seems to override the `date` header with the current time. If we skip cloudflare and hit our origin server directly, caching seems to work the way we expect. But yeah, I'm not an expert on HTTP protocols so I'm not sure if it's a problem with cloudflare, or okhttp, or if it's something we need to add custom logic for (eg. by removing age header, or maybe adding a max-age to the cache control in the response – jaregier Jan 18 '21 at 17:00
  • Does this answer your question? [How to cache okHTTP response from Web server?](https://stackoverflow.com/questions/49453564/how-to-cache-okhttp-response-from-web-server) – Martin Zeitler Jan 28 '21 at 11:34

2 Answers2

2

Replace the expires header with a Cache-Control header that sets a max-age directive:

Cache-Control: max-age=86400

This will cause OkHttp to cache the response for 24 hours regardless of when it was serve. The expires header was problematic because CloudFlare treated it as a specific expiration time, not a duration.

Jesse Wilson
  • 39,078
  • 8
  • 121
  • 128
1

I would recommend to try using the "Cache-Control" header with a max-age of your choosing.

The main reason I do this is because this is also shown in an example from the official documentation, see: https://square.github.io/okhttp/interceptors/#rewriting-responses

.header("Cache-Control", "max-age=60")

The preferred way to do this is obviously on the back-end. If you cannot modify the back-end, then I guess an interceptor would be a second option. Do note that is a last option that is discouraged.

In addition to your backend, I would take a look also the options that cloudflare itself provides: https://support.cloudflare.com/hc/en-us/articles/360021806811-Getting-Started-with-Cloudflare-Caching

Menelaos
  • 23,508
  • 18
  • 90
  • 155
  • Also note, that OkHttp3 cache may not work with POST requests (see from:stackoverflow.com/a/42547537/1688441 – Menelaos Jan 25 '21 at 19:19