Retrofit ETAG和缓存

16

如何给 Retrofit+OkHttp 添加缓存和ETAG/If-None-Match支持的适当解释是什么?我正在努力为两个项目添加Etag支持,开始怀疑可能与HTTP头有关,另一个项目已经正确设置了一切,但缓存仍然不能按预期工作。

以下是我的尝试结果。结果表明,在应用程序的同一实例中缓存似乎正在工作,但一旦重新启动 - 所有内容都会再次加载得很慢。 此外,在我的日志中,我没有看到请求中添加If-None-Match,所以我认为服务器不知道ETag并且仍然完全重新计算响应。

下面是一些代码示例:

public class RetrofitHttpClient extends UrlConnectionClient
{

    private OkUrlFactory generateDefaultOkUrlFactory()
    {
        OkHttpClient client = new com.squareup.okhttp.OkHttpClient();

        try
        {
            Cache responseCache = new Cache(baseContext.getCacheDir(), SIZE_OF_CACHE);
            client.setCache(responseCache);
        }
        catch (Exception e)
        {
            Logger.log(this, e, "Unable to set http cache");
        }

        client.setConnectTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS);
        client.setReadTimeout(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS);
        return new OkUrlFactory(client);
    }

    private final OkUrlFactory factory;

    public RetrofitHttpClient()
    {
        factory = generateDefaultOkUrlFactory();
    }

    @Override
    protected HttpURLConnection openConnection(retrofit.client.Request request) throws IOException
    {
        return factory.open(new URL(request.getUrl()));
    }
}

使用完整的日志级别和自定义标签创建Rest适配器:

restAdapter = new RestAdapter.Builder()
        .setClient(new RetrofitHttpClient())
        .setEndpoint(Config.BASE_URL)
        .setRequestInterceptor(new SignatureSetter())
        .setConverter(new JacksonConverter(JsonHelper.getObjectMapper()))
        .setLogLevel(RestAdapter.LogLevel.FULL)
        .setLog(new AndroidLog("=NETWORK="))
        .build();

我在应用程序的第一个屏幕上有一个长请求供测试使用。 当我打开应用程序时,完成该请求需要7秒钟。如果我暂停并恢复应用程序-相同的请求只需要250毫秒,显然命中缓存。如果我完全关闭应用程序并重新启动-它再次需要7秒钟。

更新: 如建议所述,我已经使用自定义的Retrofit构建并附加了LoggingInterceptor。下面是我收到的内容。

Received response for *** in 449,3ms
Date: Wed, 07 Jan 2015 09:02:23 GMT
Server: Apache
X-Powered-By: PHP/5.4.31
Access-Control-Allow-Credentials: true
Pragma:
Cache-Control: public, max-age=3600
X-Frame-Options: SAMEORIGIN
Etag: "hLxLRYztkinJAB453nRV7ncBSuU=-gzip"
Last-Modified: Wed, 24 Dec 2014 13:09:04 GMT
Vary: Accept-Encoding
Content-Encoding: gzip
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/json; charset=UTF-8
OkHttp-Selected-Protocol: http/1.1
OkHttp-Sent-Millis: 1420621288104
OkHttp-Received-Millis: 1420621288554


Sending request **** on Connection{****:80, proxy=DIRECT@ hostAddress=**** cipherSuite=none protocol=http/1.1}
Accept: application/json;
Host: ****
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/2.2.0

Response is equal to described above

正如您所看到的,下一个请求中没有出现If-None-Match头。


服务器响应中是否已经正确实现了“Cache-Control”? - Andrei Catinean
是的。正如我所提到的,我有2个项目。在一个项目中,服务器具有Cache-Control:max-age = 0,private,must-revalidate。那看起来不正确,我知道。但另一个服务器具有正确的Cache-Control设置,允许缓存一段时间。 - AAverin
你是否完全消耗了响应体?如果你没有读取整个响应体,它将无法被缓存。(你还需要在最后调用 close()。) - Jesse Wilson
服务器响应一个JSON,我使用JacksonConverter将其转换为模型。因此,我认为转换应该消耗整个主体以生成有效的模型,而这些内容是Retrofit内部的。 - AAverin
3个回答

6

我看到这个问题一直受到关注,由于没有一个真正的答案可以选择,所以我提供了我的调查结果,并暂时关闭了该线程。

在GitHub上的retrofit和okhttp线程中进行的一些讨论和调查的最终结果是,OkHttp可能存在一个问题,可能会阻止设置传出请求的If-None-Match标记。

据说这个问题已经在OkHttp 2.3中得到修复,我在这里使用“据说”是因为我还没有测试它是否真的有效。测试很困难,因为我正在使用Retrofit,而且Retrofit本身必须更新为使用新版本的OkHttp并添加一些新的拦截器支持,以便能够调试OkHttp设置的所有标头。相关线程在此处:https://github.com/square/okhttp/issues/831

我不确定之后是否更新了Retrofit。希望已经更新,因此有很大的机会已经修复了该问题,Etag应该正常工作-只需确保您拥有最新版本的Retrofit和OkHttp。

我会在有时间的时候尝试自己测试一切。


我正在尝试在2020年使用If-None-Match和Etags。这似乎仍然存在问题。 - Jamie

1
使用OkHttp拦截器可以帮助您诊断应用程序中传入和传出的标头。拦截器文档提供了一个拦截器的代码示例,可记录网络上的请求和响应标头。您可以直接使用此示例。
class LoggingInterceptor implements Interceptor {
  @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();

    long t1 = System.nanoTime();
    logger.info(String.format("Sending request %s on %s%n%s",
        request.url(), chain.connection(), request.headers()));

    Response response = chain.proceed(request);

    long t2 = System.nanoTime();
    logger.info(String.format("Received response for %s in %.1fms%n%s",
        response.request().url(), (t2 - t1) / 1e6d, response.headers()));

    return response;
  }
}

要连接到Retrofit,您需要获取Retrofit的预发布快照。截至2015年1月,当前正在使用的Retrofit版本不参与OkHttp的拦截器。很快会有一个版本可以实现拦截器,但目前还没有准备好。

Retrofit日志记录设置为FULL会显示传入/传出请求的标头,但我没有看到If-None-Match。它不显示所有标头吗?如果是这样,为什么? - AAverin
1
Retrofit并不知道由OkHttp添加的头部(包括“Cache-Control”头部)。 详见:https://github.com/square/okhttp/wiki/Calls - Jesse Wilson
1
@AAvern 我使用 okhttp 缓存,而非手动添加 ETag,但仍然返回 200 而不是 304。我发现 /data/data/package_name/cache/HttpCache 包含缓存,但仍未返回 304。 - Crossle Song
@JesseWilson,我已经按照您的建议,在Retrofit客户端中插入了一个LogginInterceptor。我已经附上了日志,没有出现If-None-Match。 - AAverin
1
OkHttp支持ETags的一个缺点是,它从不“刷新”缓存的过期时间。因此,一旦过期,除非更新,否则它将永远不会使用缓存。这只会在资源已更新(ETags不同且返回200响应而不是304)时发生。这意味着,只要继续获得304响应,OkHttp客户端将继续为每个后续请求访问网络。HTTP规范对客户端如何处理此场景模糊不清,因此我认为这不是一个错误。但如果我不断地访问网络,那么它确实会使缓存失去意义。 - Steven Pena
显示剩余5条评论

0
我曾经遇到过类似的问题:即使服务器发送相同的ETAG,OkHttp也从未命中缓存。
我的问题在于SIZE_OF_CACHE。我定义了一个非常小的大小。
尝试增加它(像10 * 1024 * 1024这样的大小对我有用) 此外,您可以探索/data/data//files/cache,看看那里是否实际存储了什么东西。

SIZE_OF_CACHE = 10 * 1024 * 1024 - 那是我现在拥有的,但它仍然不能按预期工作。我可以尝试增加它,但最可能不是问题所在。 - AAverin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接