Serve cache on timeout in okhttp3 - android

In okhttp3, if my connection times out in CONNECT or READ, is there some way I can get the cache from okhttp? Instead of the connection failing, I want to serve the user from the offline cache in case the request is taking too long.

I did experience a similar issue. I wanted to fallback to cache whenever my request was timing out (I don't mind in which state) or when connection is disrupted, or when there is no connection available. To do this I made an interceptor that would first check for connectivity and after that also catch exceptions when making the request. If there is a timeout then it will throw an exception, after which we fallback to an aggressive caching.
So basically, you first need to set up your okhttp client to use cache and then use an interceptor to use that cache in a better way.
public OkHttpClient getOkHttpClient() {
File cacheFile = new File(context.getCacheDir(), "okHttpCache");
Cache cache = new Cache(cacheFile, CACHE_SIZE);
ConnectivityInterceptor connectivityInterceptor = new ConnectivityInterceptor(networkStateHelper);
OkHttpClient.Builder builder = new OkHttpClient.Builder().cache(cache).addInterceptor(connectivityInterceptor);
return builder.build();
}
After that you can use this simple interceptor to force the usage of the cache. Normally the cache is used when the server responds with 340 which means there are no changes so we can take responses that are cached, but this of course needs an active internet connection. We can however force the cache usage so it will directly take any respond from the cache if possible, which comes in handy when you are offline or when you have timeouts
public class ConnectivityInterceptor implements Interceptor {
// NetworkStateHelper is some class we have that checks if we are online or not.
private final NetworkStateHelper networkStateHelper;
public ConnectivityInterceptor(NetworkStateHelper networkStateHelper) {
this.networkStateHelper = networkStateHelper;
}
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
// You can omit this online check or use your own helper class
if (networkStateHelper.isNotOnline()) {
return getResponseFromCache(chain, request);
}
try {
Response response = chain.proceed(request);
return new Pair<>(request, response);
}
catch (Exception exception) {
Log.w(exception, "Network failure discovered, trying cache fallback");
return getResponseFromCache(chain, request);
}
}
private Response getResponseFromCache(Interceptor.Chain chain,
Request request) throws IOException {
// We just create a new request out of the old one and set cache headers to it with the cache control.
// The CacheControl.FORCE_CACHE is already provided by OkHttp3
request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
// Now we proceed with the request and OkHttp should automatically fetch the response from cache or return
// a failure if it is not there, some 5xx status code
return chain.proceed(request);
}
}

Related

The issue with modifiable request on OkHttp silent retry

In my scenario, our backend wants to get a unique ID with any request but I read that "OkHttp will potentially repeat your requests on a slow/unreliable connection 'aggressively' until it succeeds." from here and some OkHttp issues. I know I can disable the retry mechanism with retryOnConnectionFailure(false) but I want to enable it to handle connectivity problems. Exactly what I want is, modify the request before silent retry. Can I intercept before sending a silent request?
If you add a networkInterceptor then you should have roughly 1:1 with the calls made to your backend. A normal interceptor may not involve an actual request if you get a cache hit, and it won't see all the retries. So add a networkInterceptor which will get called for each route chosen.
The multiple attempts come from both alternative routes (multiple DNS results) and safely retrying some calls based on the HTTP request or server response code that indicate it is safe to do so.
See https://square.github.io/okhttp/interceptors/ for information.
Choosing between application and network interceptors
Each interceptor chain has relative merits.
Application interceptors
Don’t need to worry about intermediate responses like redirects and retries.
Are always invoked once, even if the HTTP response is served from the cache.
Observe the application’s original intent. Unconcerned with OkHttp-injected headers like If-None-Match.
Permitted to short-circuit and not call Chain.proceed().
Permitted to retry and make multiple calls to Chain.proceed().
Can adjust Call timeouts using withConnectTimeout, withReadTimeout, withWriteTimeout.
Network Interceptors
Able to operate on intermediate responses like redirects and retries.
Not invoked for cached responses that short-circuit the network.
Observe the data just as it will be transmitted over the network.
Access to the Connection that carries the request.
I think you can solve this process with Interceptor. When a server cannot be connected, you can connect to another server.
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// try the request
Response response = doRequest(chain,request);
int tryCount = 0;
while (response == null && tryCount <= RetryCount) {
String url = request.url().toString();
url = switchServer(url);
Request newRequest = request.newBuilder().url(url).build();
tryCount++;
// retry the request
response = doRequest(chain,newRequest);
}
if(response == null){//important ,should throw an exception here
throw new IOException();
}
return response;
}
private Response doRequest(Chain chain,Request request){
Response response = null;
try{
response = chain.proceed(request);
}catch (Exception e){
}
return response;
}

How to use cache response for Retrofit with OkHttp when network response returns error

I have a simple use case: Use network response when success. Else use cached response.
But the problem is that when network response is an error, the cache is also written with that response.
One of the suggestions I read is to do FORCE_CACHE in the Interceptor when networkResponse is not successful.
But since the networkResponse overrides cache with error, next time when you request (and the server still returns an error), the cache will have an error.
Below is my current snippet. I need to add logic for returning cached value when networkResponse is an error. Any suggestion will be greatly helpful.
private void setup() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(REWRITE_CACHE_CONTROL);
File httpCacheDirectory = new File(context.getCacheDir(), "responses");
Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024); // 10 MB
builder.cache(cache);
}
private static final Interceptor REWRITE_CACHE_CONTROL = new Interceptor() {
#Override
public okhttp3.Response intercept(#NonNull Chain chain) throws IOException {
Request request = chain.request();
if (!hasAConnection()) {
request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
}
return chain.proceed(request);
}
};

Put all ongoing requests to queue retrofit and okhttp

I'm working with the authenticator of OKHttp that will retry to get new access token if we got 401 status error, but my app have to call many APIs in the same time, resulting in corrupted data, because existed refresh token will be removed when request - but the other API caller still depend on this token to use. So my question : is there anyway to put the request in queue (or at least cancel) all other api request when we got 401 error status code?
This is my authenticator:
public Request authenticate(Route route, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
access_token = getNewAccessTokenHere();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header("Authorization", "Bearer " + access_token)
.build();
} else {
ToastUtil.toast("login again");
return null;
}
}
My goal is let other api waiting for the response of first request and use the new access_token.
I know this question is quite old, but I found myself having the same problem lately and I came by a solution which has worked for me and I believe it could help someone else in the same situation.
The solution to attach a Dispatcher to the OkHttp client and limit the amount of max requests does not seem to work on retrofit out of the box, as stated by Jake Wharthon .
So my solution was to synchronize the authenticate method on my custom Authenticator, making concurrent calls, to my singleton Retrofit instance, wait until the authenticate routine is finished for each thread. This way, the first call with an unauthorized response can refresh the token and inform to the next calls, which also got an unauthorized response, that a new access token is already available.
public class MyAuthenticator implements Authenticator {
private boolean isRefreshed = false;
// Call when a new request is being made. Concurrent request should call this method to enable the refresh routine
public void newRequest(){
isRefreshed = false;
}
#Nullable
#Override
public synchronized Request authenticate(#NonNull Route route, #NonNull Response response) throws IOException { // Synchronize the method to avoid refreshing thread overlapping
if (responseCount(response) > 3) {
return null;
}
if (!isRefreshed){
// Refresh your access_token using a synchronous api request
String accessToken = getNewAccessTokenHere();
// Saves the new access token
saveNewAccessToken(accessToken);
isRefreshed = true;
// Add new header to rejected request and retry it
return response.request().newBuilder()
.removeHeader("Authorization") // removes the old header, avoiding duplications
.addHeader("Authorization", "Bearer " + accessToken)
.build();
}
else{ // Access token already refreshed so retry with the new one
// Get the saved access token
String accessToken = getAccessToken();
return response.request()
.newBuilder()
.removeHeader("Authorization")
.addHeader("Authorization", accessToken)
.build();
}
}
private int responseCount(Response response) {
int result = 1;
while ((response = response.priorResponse()) != null) {
result++;
}
return result;
}
}
You can use the Dispatcher to access all in-flight calls and cancel them.
https://square.github.io/okhttp/3.x/okhttp/okhttp3/Dispatcher.html

Android : Volley 1.0.18 how to disable request cache?

I'm using volley library:
compile 'com.mcxiaoke.volley:library:1.0.18'
In http helper class i have the following method.
public static JsonRequest createRequest(String responseType, int requestMethod, String scheme,
String url, final String requestParams, final HttpResponseListener listener,
Request.Priority priority) throws UnsupportedEncodingException {
// Start to prepare request URL
Uri.Builder builder = new Uri.Builder();
builder.scheme(scheme).encodedAuthority(url);
// GET REQUESTS - append the params into URL
if (requestMethod == Request.Method.GET && requestParams != null) {
boolean append = appendParamsToUrl(requestParams, builder);
if(!append) return null;
}
url = URLDecoder.decode(builder.build().toString(), Constants.Request.DEFAULT_ENCODING);
// Get response as JSON object
JsonRequest request;
if (responseType.equals(Constants.Request.JSON_OBJECT)) {
// Prepare request and set the Callbacks
request = new CustomJsonObjectRequest(requestMethod, url, requestParams,
priority, responseListener(listener), errorListener(listener), listener);
}else { // Get response as JSON array of objects
// Prepare request and set the Callbacks
request = new CustomJsonArrayRequest(requestMethod, url, requestParams,
priority, responseArrayListener(listener), errorListener(listener), listener);
}
request.setTag(REQUEST_TAG);
request.setShouldCache(false);
return request;
}
When i using the option:
request.setShouldCache(false);
To force disabling cache.
But when i get the response from server from the POSTMAN (Chrome extension for API testing) i got different values in response than on the Android device.
I tried also use the:
queue.getCache().clear();
But with the same results.
How can i force disable the cache from response?
Many thanks for any advice.
request.setShouldCache(false);
does not seem to be enough for GET requests.
however, clearing the cache before adding to the queue seems to help
myRequestQueue.getCache().clear();
I put this in my getRequestQueue() method in my Volley singleton before returning requestQueue.
Call the following:
myRequestQueue.getCache().remove(url);
To use Volley without response caching, instead of using Volley.newRequestQueue(), you can create your own RequestQueue as follows:
HttpStack stack;
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
Network network = new BasicNetwork(stack);
queue = new RequestQueue(new NoCache(), network);
The key is the NoCache object which implements the Volley Cache interface but does nothing.
Bonus: If you want, you can also implement the HttpStack using OkHttp (shipped with the app). The good thing about that approach is that since you ship the OkHttp library with your app, you can rest assured that your HttpStack implementation will always work on all Android versions since you're not dependent on the platform's HttpStack implementation. Plus OkHttp has all sorts of goodies like the interceptor mechanism and a very simple API.

Make a synchronous Retrofit call from inside an OkHttp Interceptor

I am trying to automatically refresh an auth token if it is expired. I am using the new Interceptor class that was introduced in OkHttp 2.2. In the intercept method I am trying the original request with chain.proceed(request), checking the response code, and if the token is expired I am making a call to a separate Retrofit service, synchronously, to obtain a new token.
The strange thing is, no code past the synchronous call seems to run. If I try debugging with a breakpoint on the synchronous call's line, then do a step-over, I am stopped in Dispatcher.java at :
if (!executedCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
Any idea as to what I might be doing wrong here? I could probably just craft a new request by hand, but I am just kind of curious why a Retrofit call doesn't seem to work here.
My Interceptor:
public class ReAuthInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// try the request
Response response = chain.proceed(request);
// if we receive a "401 - Not Authorized" then refresh the auth token and try again
if (response.code() == 401) {
// get a new auth token and store it
UserToken userToken = MobileClient.getOkraService().login(AuthUtil.getUserToken(MSWorksApplication.getContext()));
AuthUtil.authenticate(MSWorksApplication.getContext(), userToken);
Log.d("TEST", "TEST TEST TEST");
// use the original request with the new auth token
Request newRequest = request.newBuilder().header("Authorization", AuthUtil.getAuthToken(MSWorksApplication.getContext())).build();
return chain.proceed(newRequest);
}
else {
// the auth token is still good
return response;
}
}
}
I had this problem - if you're getting an HTTP response code that would cause Retrofit to call failure() instead of success() an exception will be thrown.
If you wrap
UserToken userToken = MobileClient.getOkraService().login(AuthUtil.getUserToken(MSWorksApplication.getContext()));
in a try/catch(RetrofitError e) you'll be able to execute code after a failure, however I've found this to be quite cumbersome and am still in the process of finding a nicer solution.

Categories

Resources