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.
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.
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);
}
});
Related
I saw this question asked so many times but mine is with different case. Please dont mark it as duplicate.
Following is my client for retrofit. Which works perfectly fine when we're using apk. But as soon as we convert it to MDX for citrix/secure hub we are facing this end of stream error.
I have also tried this with volley but am getting same error. As you can see that I have tried all the interceptor and all for retrofit.
Already tried following interceptor.
addHeader("Connection", "close")
retryOnConnectionFailure(true)
So my question is what is happening exactly? Why is it working in apk and not on MDX.
public static Retrofit getClient() {
//Basic Auth
String authToken = null;
if (!TextUtils.isEmpty(AppConfig.username) &&
!TextUtils.isEmpty(AppConfig.password)) {
authToken = Credentials.basic((String) AppConfig.username, (String) AppConfig.password);
}
//Create a new Interceptor.
final String finalAuthToken = authToken;
Interceptor headerAuthorizationInterceptor = new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
okhttp3.Request request = chain.request();
Headers headers = request.headers().newBuilder()
// .add("Authorization", finalAuthToken)
.add("Connection", "close").build();
request = request.newBuilder().headers(headers).build();
return chain.proceed(request);
}
};
//TO be added in milliseconds
List < Protocol > protos = new ArrayList < > ();
protos.add(Protocol.HTTP_2);
protos.add(Protocol.HTTP_1_1);
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.protocols(protos)
.retryOnConnectionFailure(true) //FIRST TRY : Added this line after getting Unexpected end of stream error.
.connectTimeout(180, TimeUnit.SECONDS)
.readTimeout(180, TimeUnit.SECONDS)
.writeTimeout(180, TimeUnit.SECONDS)
//.addInterceptor(new GzipRequestInterceptor())
// THIRD TRY : Added this new interceptor for end of stream error
/*.addInterceptor(new Interceptor() {
#NonNull
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.addHeader("Connection", "close")
.addHeader("Transfer-Encoding", "chunked")
//.addHeader("Accept-Encoding", "gzip")
.build();
return chain.proceed(request);
}
})*/
//.addInterceptor(headerAuthorizationInterceptor) // SECOND TRY : Added this line after getting Unexpected end of stream error. fot connection close
//ABOVE LINE is Next try would be adding this line as
// this headerInterceptor we have added .add("Connection","close")
// may be we need to remove the authorization from that headerInterceptor
.build();
return new Retrofit.Builder()
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.baseUrl(AppConfig.mainURLDev3forRetrofit2)
.build(); }
I'm trying to setup an HTTP cache using Retrofit (2.1.0) and OkHttp (3.3.1). I have seen many posts related to this topic, but none of them helped.
I wrote some unit tests to see how the cache works. It works just fine, but once integrated in my app, the magic ends. I will first show you my implementation and then explain some of my investigation.
First, here is my Retrofit instantiation :
OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient client = httpBuilder
.addNetworkInterceptor(INTERCEPTOR_RESPONSE_SET_CACHE)
.addNetworkInterceptor(INTERCEPTOR_REQUEST_ADD_CHECKSUM)
.addInterceptor(loggingInterceptor)
.cache(cacheHttpClient).build();
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.baseUrl(BASE_URL)
.build();
Here is the interceptor adding a header to set cache control:
private final Interceptor INTERCEPTOR_RESPONSE_SET_CACHE = new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
response = response.newBuilder()
.header("Cache-Control", "max-age=600") //+ Integer.toString(3600 * 5)
.build();
return response;
}
};
The last interceptor adds 2 URL parameters:
private static final Interceptor INTERCEPTOR_REQUEST_ADD_CHECKSUM = new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
HttpUrl url = chain.request().url();
url = url.newBuilder().addQueryParameter("rd", "random1").addQueryParameter("chk","check1").build();
Request request = chain.request().newBuilder().url(url).build();
return chain.proceed(request);
}
};
Finally, the single method of my service :
#Headers("Cache-Control: public, max-stale=500")
#GET("/get_data")
Call<DataResponse> getData(#Query("year") int year, #Query("month") int month, #Query("day") int day);
About my investigation, I setup an interceptor logger (app side, not network) to see what is happening. I can see lines such as "Cache-Control: public, max-stale=500" in my logs. This means (at least to me) that the header should give an opportunity to the OkHttp client to check the cache.
The cache itself seems to be correctly initialised. When I create it, I force the initialisation and log all the urls present in the cache. Here is how it is implemented:
File httpCacheDirectory = new File(getCacheDir(), "responses");
httpCacheDirectory.getParentFile().mkdirs();
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(httpCacheDirectory, cacheSize);
try {
cache.initialize();
Iterator<String> iterator = cache.urls();
Log.i(TAG, "URLs in cacheHttpClient : ");
while (iterator.hasNext()) {
Log.i(TAG, iterator.next());
}
} catch (IOException e) {
e.printStackTrace();
Log.i(TAG, "CACHE NOT INIT");
}
When I launch my app with Wifi available, I get the expected responses. Then I kill my app, disable Wifi and relaunch the app. I expect the cache to serve data at this moment. But it fails and I can only see OkHttp printed lines in logs :
HTTP FAILED: java.net.UnknownHostException: Unable to resolve host
"my-domain.com": No address associated with hostname
Last thing, in RFC 2616, one can read :
max-stale : Indicates that the client is willing to accept a response
that has exceeded its expiration time. If max-stale is assigned a
value, then the client is willing to accept a response that has
exceeded its expiration time by no more than the specified number of
seconds. If no value is assigned to max-stale, then the client is
willing to accept a stale response of any age.
When I don't specify an value, it actually works (I get a response even when the Wifi is down). For now this is the only way I found to make it "work". So maybe I just misunderstand the cache-control directive !?
At this point I'm really confused. I really would like to be able to use OkHttp cache system, but somehow I'm missing something.
Thank you for reading all that text !
Use this method to create cached okkhttpclient
private OkHttpClient createCachedClient(final Context context) {
File httpCacheDirectory = new File(context.getCacheDir(), "cache_file");
Cache cache = new Cache(httpCacheDirectory, 20 * 1024 * 1024);
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setCache(cache);
okHttpClient.interceptors().add(
new Interceptor() {
#Override
public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
String cacheHeaderValue = isOnline(context)
? "public, max-age=2419200"
: "public, only-if-cached, max-stale=2419200" ;
Request request = originalRequest.newBuilder().build();
com.squareup.okhttp.Response response = chain.proceed(request);
return response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", cacheHeaderValue)
.build();
}
}
);
okHttpClient.networkInterceptors().add(
new Interceptor() {
#Override
public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
String cacheHeaderValue = isOnline(context)
? "public, max-age=2419200"
: "public, only-if-cached, max-stale=2419200" ;
Request request = originalRequest.newBuilder().build();
com.squareup.okhttp.Response response = chain.proceed(request);
return response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", cacheHeaderValue)
.build();
}
}
);
return okHttpClient;
}
private boolean isOnline(Context context) {
ConnectivityManager connectivity = (ConnectivityManager) _context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity != null) {
NetworkInfo[] info = connectivity.getAllNetworkInfo();
if (info != null)
for (int i = 0; i < info.length; i++)
if (info[i].getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
return false;
}
Call createCachedClient() method to create OkHttpClient add this client to retrofit
OkHttpClient okHttpClient = createCachedClient(MainActivity.this);
Retrofit retrofit=new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(API)
.addConverterFactory(GsonConverterFactory
.create()).build();
Add this permission to manifest
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
If internet is available first time it will call the service and cache the request,next time onwards upto 2419200 milliseconds it will use cache to give response.it won't hit server upto 2419200 milliseconds even if device if offline.
I'm just learning Retrofit and OKHttp, now I have an issue.
Every request in my app is POST, just like this:
#FormUrlEncoded
#POST("some url")
Observable<Result> getData(#Field("id") String id);
In every POST, there are two same params. So in a most simple way, I can add two more #Field in every method, for example, #Field("token"),#Field("account"). But I think there must be a smart way.
Then I thought OkHttpClient may solve this.
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RequestBody body = new FormBody.Builder().add("account", "me")
.add("token", "123456").build();
request = request.newBuilder().post(body).build();
return chain.proceed(request);
}
}).build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("some base url")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
Or
HttpUrl url = request.url().newBuilder()
.setEncodedQueryParameter("account", "me")
.setEncodedQueryParameter("token", "123456")
.build();
The first method just replace all Field to these two.
The second method just add these two as GET parameters, not POST.
Now I have absolutely no idea how to make this work.
OK...Finally I find a way to do this. But I'm not sure this is the best way.
Here is the code:
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
FormBody.Builder bodyBuilder = new FormBody.Builder();
FormBody b = (FormBody) request.body();
for (int i=0;i<b.size();i++) {
bodyBuilder.addEncoded(b.name(i),b.value(i));
}
bodyBuilder.addEncoded("account", "me").add("token", "123456");
request = request.newBuilder().post(bodyBuilder.build()).build();
return chain.proceed(request);
}
}).build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://some url)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
I get all the #Field from retrofit, then add every key-value params to a new RequestBody, same as these two default params. Now every POST request has "account" and "token".
If there is a better way to do this, please let me know.
You can do that by adding a new request interceptor to the OkHttpClient. Intercept the actual request and get the HttpUrl. The http url is required to add query parameters since it will change the previously generated request url by appending the query parameter name and its value.
OkHttpClient.Builder httpClient =
new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
HttpUrl originalHttpUrl = original.url();
HttpUrl url = originalHttpUrl.newBuilder()
.addQueryParameter("apikey", "your-actual-api-key")
.build();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder()
.url(url);
Request request = requestBuilder.build();
return chain.proceed(request);
}
});
I'm using Retrofit library version 2 with OkHttpClient.
I want to get some header from all responses.
I found one solution with OkClient:
public class InterceptingOkClient extends OkClient{
public InterceptingOkClient()
{
}
public InterceptingOkClient(OkHttpClient client)
{
super(client);
}
#Override
public Response execute(Request request) throws IOException
{
Response response = super.execute(request);
for (Header header : response.getHeaders())
{
// do something with header
}
return response;
}
}
But how i can do this if i'm using OkHttpClient?
Yes, this is old question.. but still found to answer because myself too was searching similar one.
okHttpClient.interceptors().add(new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder()
.header("Authorization", "auth-value"); // <-- this is the important line, to add new header - replaces value with same header name.
Request request = requestBuilder.build();
Response response = chain.proceed(request);
Headers allHeaders = response.headers();
String headerValue = allHeaders.get("headerName");
return response;
}
});
Hope, this helps!
P.S: no error handled.
You can use the logging interceptor for that. Add it as an interceptor to your OkHttpClient builder while building the client, set the log level and voila! You will have all the information regarding the request as well as the response.
Here's how you can add the interceptor -
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
okHttpBuilder.addInterceptor(loggingInterceptor);
client = okHttpBuilder.build();
There are four options when it comes to what you want to Log - NONE,BASIC,HEADERS, and BODY.
Now build the the retrofit instance with the above defined client and you will have all the data you need.
I want to Retrofit with OkHttp uses cache when is no Internet.
I prepare OkHttpClient like this:
RestAdapter.Builder builder= new RestAdapter.Builder()
.setRequestInterceptor(new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
request.addHeader("Accept", "application/json;versions=1");
if (MyApplicationUtils.isNetworkAvaliable(context)) {
int maxAge = 60; // read from cache for 1 minute
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);
}
}
});
and setting cache like this:
Cache cache = null;
try {
cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024);
} catch (IOException e) {
Log.e("OKHttp", "Could not create http cache", e);
}
OkHttpClient okHttpClient = new OkHttpClient();
if (cache != null) {
okHttpClient.setCache(cache);
}
and I checked on rooted device, that in cache directory are saving files with the "Response headers" and Gzip files.
But I don't get the correct answer from retrofit cache in offline, although in GZip file is coded my correct answer. So how can I make Retrofit can read GZip file and how can he know which file it should be (because I have a few files there with other responses) ?
I have simlar problem in my company :)
The problem was on server side. In serwer response i have:
Pragma: no-cache
So when i removed this everything starts working. Before i removed it i get all the time such exceptions: 504 Unsatisfiable Request (only-if-cached)
Ok so how implementation on my side looks.
OkHttpClient okHttpClient = new OkHttpClient();
File httpCacheDirectory = new File(appContext.getCacheDir(), "responses");
Cache cache = new Cache(httpCacheDirectory, maxSizeInBytes);
okHttpClient.setCache(cache);
OkClient okClient = new OkClient(okHttpClient);
RestAdapter.Builder builder = new RestAdapter.Builder();
builder.setEndpoint(endpoint);
builder.setClient(okClient);
If you have problems in testing on which side is problem (server or app). You can use such feauture to set headers received from server.
private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.removeHeader("Pragma")
.header("Cache-Control",
String.format("max-age=%d", 60))
.build();
}
};
and simply add it:
okHttpClient.networkInterceptors().add(REWRITE_CACHE_CONTROL_INTERCEPTOR);
Thanks to that as you can see i was able to remove Pragma: no-cache header for test time.
Also i suggest you to read about Cache-Control header:
max-age,max-stale
Other usefull links:
List of HTTP header fields
Cache controll
Another sample code