Retrofit + OkHTTP - response cache not working - android

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. Unfortunately, I don't have admin access to the API server so I can't modify headers returned by the server. (currently, server returns Cache-control: private)
So I decided to use okhttp header spoofing to insert appropriate cache headers. Sadly, no matter what I do, caching doesn't seem to work.
I initialise the api service like this:
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). I also experimented with various Cache-control values, but no luck.
I made sure the cacheFile exists, isDirectory and is writeable by the application.
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).
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(). 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. It's worthwhile to look at the CacheStrategy and CacheControl classes to see how OKHttp handles cache-related headers. It's also worthwhile to do ctrl+f "cache" on http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
I am not sure if the networkInterceptors() and interceptors() documentation is just unclear or if there is a bug. Once I look into that more, I will update this answer.

One more thing to add here, Apart from Brendan Weinstein's answer just to confirm OkHttp3 cache will not work with post requests.

After a full day, I found that my offline caching was not working just because I was using POST in the API type. The moment I changed it to GET, it worked!
#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. Caching with max-age will not work if Pragma: no-cache header is present.
If it does have Pragma header, remove it by doing the following in your 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()
}

Related

Retrofit is returning cached response

I am using Retrofit in an Android application. When I hit an API with token to get user information it gives cached(previous) response. Whenever I logged out and log in again API gives previous user detail, I tested API in Postman it works fine there.
I have tried some solutions I searched but nothing is working.
Response header that I am getting is
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
Date: Mon, 08 Jan 2018 09:35:26 GMT
Below is ApiClient class
public class ApiClient {
public static final String BASE_URL = "http://XX.XXX.XXX.XX/api/";
private static Retrofit authRetrofit = null;
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.build();
}
return retrofit;
}
public static Retrofit getAuthorizeClient(final String token) {
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.addHeader("Authorization", "Bearer " + token)
.addHeader("Cache-control", "no-cache")
.method(original.method(), original.body())
//.cacheControl(CacheControl.FORCE_NETWORK)
.build();
return chain.proceed(request);
}
});
OkHttpClient client = httpClient.cache(null).build();
if (authRetrofit == null) {
authRetrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.client(client).build();
}
return authRetrofit;
}
}
In your httpClient interceptor, try adding cache control header:
.addHeader("Cache-control", "no-cache");
EDIT
Just noticed you are on Retrofit2
original.newBuilder().header("Cache-control", "no-cache");
Alternatively try adding it as an annotation to your API method:
#Headers("Cache-control: no-cache")
Response callApi();
According to Docs.
EDIT 2
Okay I suspect it's because of your if condition, your authRetrofit wouldn't be updated if condition failed. Try removing it
if (authRetrofit == null)
Help this helps.
Use Post request instead, I've faced the same problem, rectified by changing my GET method to post and send one random string as a Field.

HTTP FAILED: java.net.UnknownHostException: Unable to resolve host "www.reddit.com": No address associated with hostname

hello im trying to caching the GET request that im doing from reddit service. I tested with another api and works but i can't find the reason why i can't have control of the cache on this one. I can see how cache data goes up in app settings when i can fecth the json with internet on. But when i turn off internet connection ( airplane mode ) i get this error:
01-17 06:00:05.524 17363-17429/com.amirgb.reddittestapp.debug D/OkHttp: --> GET https://www.reddit.com/reddits.json http/1.1
01-17 06:00:05.524 17363-17429/com.amirgb.reddittestapp.debug D/OkHttp: --> END GET
01-17 06:00:05.531 17363-17429/com.amirgb.reddittestapp.debug D/OkHttp: <-- HTTP FAILED: java.net.UnknownHostException: Unable to resolve host "www.reddit.com": No address associated with hostname
Im testing on a moto g 4 plus api 23 i tried with https://itunes.apple.com/us/rss/topfreeapplications/limit=20/json service and cache does work, so maybe there is a difference in reddit header? .
My restclient class:
import android.content.Context;
import android.widget.ImageView;
import com.amirgb.reddittestapp.BuildConfig;
import com.amirgb.reddittestapp.R;
import com.amirgb.reddittestapp.RedditTestApplication;
import com.amirgb.reddittestapp.model.RedditResponse;
import com.amirgb.reddittestapp.util.NetWorkUtils;
import java.io.File;
import java.util.concurrent.TimeUnit;
import okhttp3.Cache;
import okhttp3.CacheControl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public final class RedditApiService {
private static IRedditApi mRedditApi;
private static OkHttpClient mClient;
/**
* Inits retrofit
*/
static {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
File httpCacheDirectory = new File(RedditTestApplication.getInstance().getCacheDir(), "reddit");
Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024);
mClient = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(chain -> {
Request request = chain.request();
if (NetWorkUtils.isNetworkAvailable(RedditTestApplication.getInstance())) {
request = request.newBuilder()
.cacheControl(new CacheControl.Builder()
.maxAge(0, TimeUnit.SECONDS)
.maxStale(365, TimeUnit.DAYS).build())
.build();
}
Response originalResponse = chain.proceed(request);
if (NetWorkUtils.isNetworkAvailable(RedditTestApplication.getInstance())) {
int maxAge = 60 * 60;
return originalResponse.newBuilder()
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
} else {
int maxStale = 60 * 60 * 24 * 28;
return originalResponse.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.build();
}
})
.connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.build();
Retrofit mRetrofit = new Retrofit.Builder()
.baseUrl(IRedditApi.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(mClient)
.build();
mRedditApi = mRetrofit.create(IRedditApi.class);
}
/**
* Get subjects from service api
*
* #param callback
*/
public static void getSubjects(Callback<RedditResponse> callback) {
Call<RedditResponse> call = mRedditApi.getSubjects();
call.enqueue(callback);
}
My networks util method:
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null &&
activeNetwork.isConnectedOrConnecting();
}
My retrofit interface:
public interface IRedditApi {
String BASE_URL = "https://www.reddit.com";
#GET("/reddits.json")
Call<RedditResponse> getSubjects();}
Reddit reponse header:
accept-ranges →bytes
access-control-allow-origin →*
access-control-expose-headers →X-Reddit-Tracking, X-Moose
cache-control →max-age=0, must-revalidate
content-encoding →gzip
content-length →79113
content-type →application/json; charset=UTF-8
date →Tue, 17 Jan 2017 09:16:52 GMT
status →200
strict-transport-security →max-age=15552000; includeSubDomains; preload
vary →accept-encoding
via →1.1 varnish
x-cache →MISS
x-cache-hits →0
x-content-type-options →nosniff
x-frame-options →SAMEORIGIN
x-moose →majestic
x-reddit-tracking →https://pixel.redditmedia.com/pixel/of_destiny.png?v=W9eORuZE%2BzXXBRZc%2BikbQ8aTR0a7BSLeoE9DDYvuCihjMAifTK%2Bdsr%2F0gX8rDES98kR5xK2vVWc%3D
x-served-by →cache-dfw1841-DFW
x-timer →S1484644612.225905,VS0,VE254
x-ua-compatible →IE=edge
x-xss-protection →1; mode=block

HTTP Caching with Retrofit 2.0.x

I'm trying to cache some responses in my app using Retrofit 2.0, but I'm missing something.
I installed a caching file as follows:
private static File httpCacheDir;
private static Cache cache;
try {
httpCacheDir = new File(getApplicationContext().getCacheDir(), "http");
httpCacheDir.setReadable(true);
long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
HttpResponseCache.install(httpCacheDir, httpCacheSize);
cache = new Cache(httpCacheDir, httpCacheSize);
Log.i("HTTP Caching", "HTTP response cache installation success");
} catch (IOException e) {
Log.i("HTTP Caching", "HTTP response cache installation failed:" + e);
}
public static Cache getCache() {
return cache;
}
which creates a file in /data/user/0/<PackageNmae>/cache/http
, then prepared a network interceptor as follows:
public class CachingControlInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// Add Cache Control only for GET methods
if (request.method().equals("GET")) {
if (ConnectivityUtil.checkConnectivity(getContext())) {
// 1 day
request.newBuilder()
.header("Cache-Control", "only-if-cached")
.build();
} else {
// 4 weeks stale
request.newBuilder()
.header("Cache-Control", "public, max-stale=2419200")
.build();
}
}
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.header("Cache-Control", "max-age=86400")
.build();
}
}
my Retrofit and OkHttpClient instance:
OkHttpClient client = new OkHttpClient();
client.setCache(getCache());
client.interceptors().add(new MainInterceptor());
client.interceptors().add(new LoggingInceptor());
client.networkInterceptors().add(new CachingControlInterceptor());
Retrofit restAdapter = new Retrofit.Builder()
.client(client)
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
productsService = restAdapter.create(ProductsService.class);
where ProductsService.class contains:
#Headers("Cache-Control: max-age=86400")
#GET("categories/")
Call<PagedResponse<Category>> listCategories();
and
Call<PagedResponse<Category>> call = getRestClient().getProductsService().listCategories();
call.enqueue(new GenericCallback<PagedResponse<Category>>() {
// whatever
// GenericCallback<T> implements Callback<T>
}
});
The question here is: How to make it access cached responses when device being offline?
Header of backend response are:
Allow → GET, HEAD, OPTIONS
Cache-Control → max-age=86400, must-revalidate
Connection → keep-alive
Content-Encoding → gzip
Content-Language → en
Content-Type → application/json; charset=utf-8
Date → Thu, 17 Dec 2015 09:42:49 GMT
Server → nginx
Transfer-Encoding → chunked
Vary → Accept-Encoding, Cookie, Accept-Language
X-Frame-Options → SAMEORIGIN
x-content-type-options → nosniff
x-xss-protection → 1; mode=block
Finally I get the answer.
Network Interceptor should be as follow:
public class CachingControlInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// Add Cache Control only for GET methods
if (request.method().equals("GET")) {
if (ConnectivityUtil.checkConnectivity(YaootaApplication.getContext())) {
// 1 day
request = request.newBuilder()
.header("Cache-Control", "only-if-cached")
.build();
} else {
// 4 weeks stale
request = request.newBuilder()
.header("Cache-Control", "public, max-stale=2419200")
.build();
}
}
Response originalResponse = chain.proceed(request);
return originalResponse.newBuilder()
.header("Cache-Control", "max-age=600")
.build();
}
}
then installing cache file is that simple
long SIZE_OF_CACHE = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(new File(context.getCacheDir(), "http"), SIZE_OF_CACHE);
OkHttpClient client = new OkHttpClient();
client.cache(cache);
client.networkInterceptors().add(new CachingControlInterceptor());
In your CachingControlInterceptor, you create new requests, but never actually use them. You call newBuilder and ignore the result, so the header modification is never actually sent any where. Try assigning those values to request and then instead of calling proceed on chain.request() call it on request.
public class CachingControlInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// Add Cache Control only for GET methods
if (request.method().equals("GET")) {
if (ConnectivityUtil.checkConnectivity(getContext())) {
// 1 day
request = request.newBuilder()
.header("Cache-Control", "only-if-cached")
.build();
} else {
// 4 weeks stale
request = request.newBuilder()
.header("Cache-Control", "public, max-stale=2419200")
.build();
}
}
Response originalResponse = chain.proceed(request);
return originalResponse.newBuilder()
.header("Cache-Control", "max-age=600")
.build();
}
}
you can also try:
public class CachingInterceptor implements Interceptor {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
request = new Request.Builder()
.cacheControl(new CacheControl.Builder()
.maxAge(1, TimeUnit.DAYS)
.minFresh(4, TimeUnit.HOURS)
.maxStale(8, TimeUnit.HOURS)
.build())
.url(request.url())
.build();
return chain.proceed(request);
}
}
I finally discovered the solution that worked for me in Retrofit 2.x and OkHttp 3.x
I had to implement two Interceptors, one of them is responsible to rewrite the Request headers and the other to rewrite the Response headers.
First, make sure you delete any old cache. (root explorer /data/data/com.yourapp/cache
Instantiate the client builder:
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(new RewriteRequestInterceptor())
.addNetworkInterceptor(new RewriteResponseCacheControlInterceptor())
Create the RewriteRequestInterceptor
public class RewriteRequestInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
int maxStale = 60 * 60 * 24 * 5;
Request request;
if (NetworkUtils.isNetworkAvailable()) {
request = chain.request();
} else {
request = chain.request().newBuilder().header("Cache-Control", "max-stale=" + maxStale).build();
}
return chain.proceed(request);
}
}
Create the RewriteResponseCacheControlInterceptor
public class RewriteResponseCacheControlInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
int maxStale = 60 * 60 * 24 * 5;
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder().header("Cache-Control", "public, max-age=120, max-stale=" + maxStale).build();
}
}
It's important to make sure you add the ResponseCacheControlInterceptor as a Network Interceptor, and the RewriteRequestInterceptor as a Interceptor (as I did in the 2nd step).
OkHttpClient client = new OkHttpClient.Builder().cache(cache).build();

Enabling and Disabling Cache : Retrofit ,Okhttp Implementation

I have an Implementation of Retrofit with OkHTTP as a client configured with an Interceptor to enable Cache Feature. Well the cache Works fine , But I have trouble disabling Cache using the Mechanisms I already have. I do have a solution for this : Not using the Code Containing cache mechanism and use a normal retrofit implementation each time I want to disable Cache.
Here Is the Full Code . Service Generator to turn a retrofit Interface to a reusable client. and Request A Request/response Interceptor
package com.example.infinite.adapter;
import android.content.Context;
import android.util.Log;
import com.example.infinite.settings.Config;
import com.example.infinite.utils.ConnectionDetector;
import com.example.infinite.utils.SharedPreferenceEasy;
import com.squareup.okhttp.Cache;
import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import retrofit.RequestInterceptor;
import retrofit.RestAdapter;
import retrofit.android.MainThreadExecutor;
import retrofit.client.OkClient;
/**
* Created by user on 5/18/2015.
* Create REST Adapter for Given Class
*/
public class ServiceGenerator {
protected static RestAdapter mRestAdapter;
static String secret = "Schhh!!";
private static Context mContext;
private static final Interceptor mCacheControlInterceptor = new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// Add Cache Control only for GET methods
if (request.method().equals("GET")) {
//see if cache is enabled
if (enableCache) {
if (new ConnectionDetector(mContext).isConnectingToInternet()) {
// 1 day
request.newBuilder()
.header("Cache-control", "only-if-cached")
.header("Accept", "application/json")
.header("Authorization", "Basic " + secret)
.build();
Log.d("REQUEST CACHE", "Only-if-Cached");
} else {
// upto 4 weeks stale
request.newBuilder()
.header("Cache-control", "public, max-stale=2419200")
.header("Accept", "application/json")
.header("Authorization", "Basic " + secret)
.build();
Log.d("REQUEST CACHE", "MAX STALE ");
}
}else
{
//NO CACHE
request.newBuilder()
.header("Cache-control" ,"public,max-age=0")
.header("Accept", "application/json")
.header("Authorization", "Basic " + secret)
.build();
Log.d("REQUEST CACHE","NO CACHE ");
}
}
//s-maxage for requests with authentications
Response response = chain.proceed(request);
// Re-write response CC header to force use of cache & set header such that it is cached.
if(enableCache) {
Log.d("RESPONSE CACHE","MAXAGE-CACHE ");
return response.newBuilder()
.header("Cache-control", "public, max-age=86400") // 1 day
.header("Accept", "application/json")
//Authentication Header , do you want to see it in the response
.header("Authorization", "Basic " + secret)
.build();
}else{
Log.d("RESPONSE CACHE","NO-CACHE ");
//Say that you don't want the result to be storedcontinue
return response.newBuilder()
.header("Cache-control", " public,max-age=0, no-cache, no-store,must-revalidate")
.header("Pragma","no-cache")
.build();
}
}
};
/**
* #param serviceClass Service class To create the adaptor for
* #param baseUrl Base URL of the API
* #param context Used to create a Cache
* #param <S>
* #return Rest Adapter
*/
public static <S> S createService(Class<S> serviceClass, String baseUrl, Context context) {
mContext = context;
enableCache = new SharedPreferenceEasy(context).RetreiveBoolean(Config.KEY_PREF_NETWK_CACHE,true);
// Create Cache
Cache cache = null;
cache = new Cache(new File(mContext.getCacheDir(), "http"), SIZE_OF_CACHE);
// Create OkHttpClient
OkHttpClient okHttpClient = new OkHttpClient();
if(enableCache) {
okHttpClient.setCache(cache);
}
okHttpClient.setConnectTimeout(30, TimeUnit.SECONDS);
okHttpClient.setReadTimeout(30, TimeUnit.SECONDS);
// Add Cache-Control Interceptor
okHttpClient.networkInterceptors().add(mCacheControlInterceptor);
// Create Executor
Executor executor = Executors.newCachedThreadPool();
RestAdapter.Builder builder = new RestAdapter.Builder()
.setEndpoint(baseUrl)
.setExecutors(executor, new MainThreadExecutor())
.setClient(new OkClient(okHttpClient))
.setLogLevel(RestAdapter.LogLevel.FULL);
RestAdapter adapter = builder.build();
return adapter.create(serviceClass);
}
}
I want to acheive a compulsive request from Network when cache is disabled from my app's settings. if(enableCache)

How to specify a default user agent for okhttp 2.x requests

I am using okhttp 2.0 in my Android app and didn't find a way to set some common User Agent for all outgoing requests.
I thought I could do something like
OkHttpClient client = new OkHttpClient();
client.setDefaultUserAgent(...)
...but there's no such method or similar.
Of course I could provide some extension utility method which would wrap a RequestBuilder to attach .header("UserAgent") and then I would use it for building all my requests, but I thought maybe I missed some existing and simpler way?
You can use an interceptor to add the User-Agent header to all your requests.
For more information about okHttp interceptors see http://square.github.io/okhttp/interceptors/
Example implementation of this interceptor:
/* This interceptor adds a custom User-Agent. */
public class UserAgentInterceptor implements Interceptor {
private final String userAgent;
public UserAgentInterceptor(String userAgent) {
this.userAgent = userAgent;
}
#Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request requestWithUserAgent = originalRequest.newBuilder()
.header("User-Agent", userAgent)
.build();
return chain.proceed(requestWithUserAgent);
}
}
Test for the UserAgentInterceptor:
public void testUserAgentIsSetInRequestHeader() throws Exception {
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setBody("OK"));
server.play();
String url = server.getUrl("/").toString();
OkHttpClient client = new OkHttpClient();
client.networkInterceptors().add(new UserAgentInterceptor("foo/bar"));
Request testRequest = new Request.Builder().url(url).build()
String result = client.newCall(testRequest).execute().body().string();
assertEquals("OK", result);
RecordedRequest request = server.takeRequest();
assertEquals("foo/bar", request.getHeader("User-Agent"));
}
In case anyone is looking for this working with OkHttp 3 and in Kotlin:
val client = OkHttpClient.Builder()
.addNetworkInterceptor { chain ->
chain.proceed(
chain.request()
.newBuilder()
.header("User-Agent", "COOL APP 9000")
.build()
)
}
.build()
OkHttp v2.1 which is set to be released in the next few weeks will automatically set a User-Agent header if one is not already set.
As of now there isn't a good way to add this header to every request in a centralized way. The only workaround is to include the header manually for every Request that is created.
Based on #josketres answer, here is a similar Interceptor for OkHttp version 3
public class UserAgentInterceptor implements Interceptor {
private final String mUserAgent;
public UserAgentInterceptor(String userAgent) {
mUserAgent = userAgent;
}
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
Request request = chain.request()
.newBuilder()
.header("User-Agent", mUserAgent)
.build();
return chain.proceed(request);
}
}
Plus the updated test:
#Test
public void testUserAgentIsSetInRequestHeader() throws IOException, InterruptedException {
final String expectedUserAgent = "foo/bar";
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setBody("OK"));
server.start();
OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();
okHttpBuilder.addInterceptor(new UserAgentInterceptor(expectedUserAgent));
Request request = new Request.Builder().url(server.url("/").url()).build();
ResponseBody result = okHttpBuilder.build().newCall(request).execute().body();
assertNotNull(result);
assertEquals("OK", result.string());
assertEquals(expectedUserAgent, server.takeRequest().getHeader("User-Agent"));
}
You have to use builder in newer versions. (Sep 2021)
client = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
#NotNull
#Override
public Response intercept(#NotNull Chain chain) throws IOException {
Request originalRequest = chain.request();
Request requestWithUserAgent = originalRequest.newBuilder()
.header("User-Agent", "My Agent is so cool")
.build();
return chain.proceed(requestWithUserAgent);
}
})
.build();
Using an intercepter is no longer required in the newer versions of OkHttp. Adding a user agent is as simple as:
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
Source: OkHttp wiki.

Categories

Resources