Server sends me json object, expiration and ETAg.. I want to Voley save this object in cache and in next request for this object use request to the server including ETag in the header. If response will be 304 Not Modified, then it should use cached resource and if it will be 200 OK, it should use new resource from the server.
Volley doesn't send request at all (if the cache isn't expired) or if it is expired it sends new request with If-None-Match + etag string.. and server always response with 200
Volley will not issue a request to server at all if it detects that cache entry has not expired yet. Here are some code excerpts to prove it:
// If it is completely expired, just send it to the network.
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
And:
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// Mark the response as intermediate.
response.intermediate = true;
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
mDelivery.postResponse(request, response, new Runnable() {
#Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
You might want to control resource expiration on your server by setting max-age at Cache-Control or Expires headers in order to tweak cache on client side.
Related
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;
}
The Task
I have a situation in which if a refresh token request comes back with a 401, I would force a logout by clearing the user from the room and from memory. I would want to achieve this from inside of the Authenticator OR the Interceptor so I don't have to check for 401 as a response to each call.
The Authenticator
public Request authenticate(Route route, Response response) throws IOException {
// if (responseCount(response) >= 3) {
// return null;
// }
//
UserCredentials userCredentials = new
UserCredentials().
CreateRefreshTokenRequest("refresh_token",
requestHeaders.getAccessToken().getRefreshToken(),
oAuthClient.getClientId(),
oAuthClient.getClientSecret());
Request.Builder builder = response.request().newBuilder();
if (accountsServicesHolder.getAccountsServices() == null)
return null;
//this HAS TO BE a Synchronous process. The usual RXjava shenanigans can't be applied here.
try {
//Synchronous call to get new token
AccessToken accessToken = accountsServicesHolder.
getAccountsServices().
refreshToken(userCredentials.toMap()).
blockingGet();
//persist the token in the db
//userDao.save()
//persist the token in memory
//send an updated version to the backend
builder.header("Authorization", "Bearer " + accessToken.getAccessToken());
} catch (Exception ex) {
if (ex instanceof HttpException) {
return builder.
url(linksProvider.getProductionUrl().concat(logoutEndpoint)).
build(); //Redirecting this authenticator to a logout ws so that we do not get a 401 . Something goes wrong here
}
}
return builder.build();
}
As you can see, if we do not get a token on basic of the refresh token, I am cunningly inserting a new URL into the builder. This is so that I do not get a 401 being bubbled up to where I made the initial call. I do not want a user to get a "401 You have not authorized" message rather I would display a toast saying "Session Expired"
The Request Interceptor
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request.Builder builder = original.newBuilder().
header("Authorization", "Bearer " + requestHeaders.getAccessToken().getAccessToken()).
header("Accept-Language", requestHeaders.getLanguage()).
method(original.method(), original.body());
Request newRequest = builder.build();
Response response = chain.proceed(newRequest);
if (response.code() == 200
&& response.request().url().toString().contains("logout")) {
forceUserOut();
}
return response;
}
The forceUserOut method Broadcasts to the system that a logout has happened.
Since we have protected against a 401 now no errors would be displayed to the user.
The Problem
Okay, now we come to the problem. The place where I thought I was being cunned is perhaps not so smart after all. The authenticator runs an extra time after the logout API is called which runs after a thing that is supposed to happen afterward twice.
As far as I understand, the authenticator should have just executed the logout service and the interceptor should have started doing its job then.
The authenticator, however, does the following
1) Tries to get the new access token
2) Fails and calls the logout API (invokes the Interceptor and broadcasts the logout)
3) Fails and calls the logout API (invokes the interceptor and broadcasts the logout)
4) End
I know I might be breaking something inside the OkHttps authentication mechanism, but what other methods could I use in order to achieve a forced logout in a way I do not have to write a 401 check on all the APIs?
Extremely apologetic for the long question.
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);
}
}
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
Just like a normal web browser, e.g, chrome,firefox, where cache is enabled, I want to send a http get request from my android activity using HTTPGET which will handle caching. How to do it?
I would suggest you to use Volley Library for all Networking Calls to make it more easier, efficient and fast.
Use it for caching the Requests and Memory Management.
Loading request from cache
Like below you can check for a cached response of an URL before making a network call.
Cache cache = AppController.getInstance().getRequestQueue().getCache();
Entry entry = cache.get(url);
if(entry != null){
try {
String data = new String(entry.data, "UTF-8");
// handle data, like converting it to xml, json, bitmap etc..
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
else{
// Cached response doesn't exists. Make network call here
}
Hope this helps !!