简体   繁体   English

改造 + OkHTTP - 响应缓存不起作用

[英]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.我正在使用改造 + okhttp 从 API 获取一些数据,我想缓存它们。 Unfortunately, I don't have admin access to the API server so I can't modify headers returned by the server.不幸的是,我没有 API 服务器的管理员访问权限,因此我无法修改服务器返回的标头。 (currently, server returns Cache-control: private) (目前,服务器返回 Cache-control: private)

So I decided to use okhttp header spoofing to insert appropriate cache headers.所以我决定使用okhttp头欺骗来插入适当的缓存头。 Sadly, no matter what I do, caching doesn't seem to work.可悲的是,无论我做什么,缓存似乎都不起作用。

I initialise the api service like this:我像这样初始化 api 服务:

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).如您所见,我还试图欺骗 Expires 和 Date 标头(我尝试删除它们,设置它们以便它们之间完全存在最大年龄差异,并将 Expires 设置到未来很远的时间)。 I also experimented with various Cache-control values, but no luck.我还尝试了各种缓存控制值,但没有运气。

I made sure the cacheFile exists, isDirectory and is writeable by the application.我确保 cacheFile 存在,isDirectory 并且可由应用程序写入。

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). (这是在使用此处发布的确切设置时发生的)但是当我重新启动应用程序时,一切都恢复到“正常”状态(持续命中计数为 0)。

Co if anyone has any idea what could be the problem here, I'd be really glad for any help :)如果有人知道这里可能有什么问题,我会很高兴得到任何帮助:)

Use networkInterceptors() instead of interceptors().使用networkInterceptors() 而不是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.当您使用拦截器更改标头时,它不会在调用 CacheStrategy.isCacheable() 之前进行任何调整。 It's worthwhile to look at the CacheStrategy and CacheControl classes to see how OKHttp handles cache-related headers.值得查看 CacheStrategy 和 CacheControl 类以了解 OKHttp 如何处理与缓存相关的标头。 It's also worthwhile to do ctrl+f "cache" on http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.htmlhttp://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html上做 ctrl+f "cache" 也是值得的

I am not sure if the networkInterceptors() and interceptors() documentation is just unclear or if there is a bug.我不确定networkInterceptors() 和interceptors() 文档是否不清楚或者是否存在错误。 Once I look into that more, I will update this answer.一旦我对此进行了更多研究,我将更新此答案。

这里还要补充一件事,除了 Brendan Weinstein 的回答只是为了确认 OkHttp3 缓存不适用于发布请求。

After a full day, I found that my offline caching was not working just because I was using POST in the API type.一整天后,我发现我的离线缓存无法正常工作,因为我在 API 类型中使用了POST The moment I changed it to GET , it worked!我将其更改为GET的那一刻,它起作用了!

@GET("/ws/audioInactive.php")
Call<List<GetAudioEntity>> getAudios();

My entire Retrofit class.我的整个改造课程。

import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.limnet.iatia.App;
import com.limnet.iatia.netio.entity.registration.APIInterfaceProviderIMPL;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

import okhttp3.Cache;
import okhttp3.CacheControl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RHTRetroClient {

    public static final String BASE_URL = "https://abc.pro";
    private static Retrofit retrofit = null;
    private static RHTRetroClient mInstance;

    private static final long cacheSize = 10 * 1024 * 1024; // 10 MB
    public static final String HEADER_CACHE_CONTROL = "Cache-Control";
    public static final String HEADER_PRAGMA = "Pragma";


    private RHTRetroClient() {
        Gson gson = new GsonBuilder()
                .setLenient()
                .create();
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();

        Cache cache = new Cache(new File(App.getAppContext().getCacheDir(), "soundbites"),cacheSize);

        OkHttpClient client = new OkHttpClient.Builder()
                .cache(cache)
                .addInterceptor(httpLoggingInterceptor()) // used if network off OR on
                .addNetworkInterceptor(networkInterceptor()) // only used when network is on
                .addInterceptor(offlineInterceptor())
                .build();

        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(client)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();
    }

    /**
     * This interceptor will be called both if the network is available and if the network is not available
     *
     * @return
     */
    private static Interceptor offlineInterceptor() {
        return new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Log.d("rht", "offline interceptor: called.");
                Request request = chain.request();

                // prevent caching when network is on. For that we use the "networkInterceptor"
                if (!App.hasNetwork()) {
                    CacheControl cacheControl = new CacheControl.Builder()
                            .maxStale(7, TimeUnit.DAYS)
                            .build();

                    request = request.newBuilder()
                            .removeHeader(HEADER_PRAGMA)
                            .removeHeader(HEADER_CACHE_CONTROL)
                            .cacheControl(cacheControl)
                            .build();
                }

                return chain.proceed(request);
            }
        };
    }

    /**
     * This interceptor will be called ONLY if the network is available
     *
     * @return
     */
    private static Interceptor networkInterceptor() {
        return new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Log.d("rht", "network interceptor: called.");

                Response response = chain.proceed(chain.request());

                CacheControl cacheControl = new CacheControl.Builder()
                        .maxAge(5, TimeUnit.SECONDS)
                        .build();

                return response.newBuilder()
                        .removeHeader(HEADER_PRAGMA)
                        .removeHeader(HEADER_CACHE_CONTROL)
                        .header(HEADER_CACHE_CONTROL, cacheControl.toString())
                        .build();
            }
        };
    }

    private static HttpLoggingInterceptor httpLoggingInterceptor() {
        HttpLoggingInterceptor httpLoggingInterceptor =
                new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
                    @Override
                    public void log(String message) {
                        Log.d("rht", "log: http log: " + message);
                    }
                });
        httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        return httpLoggingInterceptor;
    }

    public static synchronized RHTRetroClient getInstance() {
        if (mInstance == null) {
            mInstance = new RHTRetroClient();
        }
        return mInstance;
    }

    public APIInterfaceProviderIMPL getAPIInterfaceProvider() {
        return retrofit.create(APIInterfaceProviderIMPL.class);
    }

}

Check if there is a Pragma header in your response.检查您的响应中是否有 Pragma 标头。 Caching with max-age will not work if Pragma: no-cache header is present.如果存在Pragma: no-cache标头,则使用max-age缓存将不起作用。

If it does have Pragma header, remove it by doing the following in your Interceptor :如果它确实有Pragma标头,请通过在您的Interceptor执行以下操作来删除它:

override fun intercept(chain: Interceptor.Chain): Response {
    val cacheControl = CacheControl.Builder()
        .maxAge(1, TimeUnit.MINUTES)
        .build()

    return originalResponse.newBuilder()
        .header("Cache-Control", cacheControl.toString())
        .removeHeader("Pragma") // Caching doesnt work if this header is not removed
        .build()
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM